├── Arduino LocoNet Interface v1 ├── Eagle │ ├── LocoNet pca9685.brd │ └── LocoNet pca9685.sch └── README.md ├── Arduino LocoNet-DCC Interface v2 ├── Board.png ├── Connectors.png ├── Eagle │ ├── LocoNet-DCC Interface v2.0.brd │ └── LocoNet-DCC Interface v2.0.sch ├── Manual │ └── Arduino LocoNet-DCC Interface v2 Manual Rev 1.1.pdf ├── README.md └── Schematics.png ├── LocoNet_Railcom_Display └── LocoNet_Railcom_Display.ino ├── LocoNet_Servo_Decoder_LNCV ├── LocoNet_Servo_Decoder_LNCV.ino └── README.md ├── Loconet_6040_PCA9685 ├── Loconet_6040_PCA9685.ino └── README.md ├── Loconet_Master_Switch ├── Loconet_Master_Switch.ino └── README.md ├── Loconet_Pendelzug ├── Loconet_Pendelzug.ino └── README.md ├── Loconet_UDP_Monitor ├── Loconet_UDP_Monitor.ino └── README.md ├── Loconet_UDP_Signal_Decoder ├── Loconet_UDP_Signal_Decoder.ino └── README.md ├── Loconet_WS2812b_Signal_Decoder └── Loconet_WS2812b_Signal_Decoder.ino └── README.md /Arduino LocoNet Interface v1/README.md: -------------------------------------------------------------------------------- 1 | # Arduino Nano LocoNet Interface v1 2 | -------------------------------------------------------------------------------- /Arduino LocoNet-DCC Interface v2/Board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Merdeka/Arduino-ModelRail/42f6227a197c932f9279636d3972bf95c05f6a80/Arduino LocoNet-DCC Interface v2/Board.png -------------------------------------------------------------------------------- /Arduino LocoNet-DCC Interface v2/Connectors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Merdeka/Arduino-ModelRail/42f6227a197c932f9279636d3972bf95c05f6a80/Arduino LocoNet-DCC Interface v2/Connectors.png -------------------------------------------------------------------------------- /Arduino LocoNet-DCC Interface v2/Manual/Arduino LocoNet-DCC Interface v2 Manual Rev 1.1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Merdeka/Arduino-ModelRail/42f6227a197c932f9279636d3972bf95c05f6a80/Arduino LocoNet-DCC Interface v2/Manual/Arduino LocoNet-DCC Interface v2 Manual Rev 1.1.pdf -------------------------------------------------------------------------------- /Arduino LocoNet-DCC Interface v2/README.md: -------------------------------------------------------------------------------- 1 | # Arduino Nano LocoNet & DCC Interface v2 -------------------------------------------------------------------------------- /Arduino LocoNet-DCC Interface v2/Schematics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Merdeka/Arduino-ModelRail/42f6227a197c932f9279636d3972bf95c05f6a80/Arduino LocoNet-DCC Interface v2/Schematics.png -------------------------------------------------------------------------------- /LocoNet_Railcom_Display/LocoNet_Railcom_Display.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // LocoNet Packet Monitor 6 | // Demonstrates the use of the: 7 | // 8 | // LocoNet.processSwitchSensorMessage(LnPacket) 9 | // 10 | // function and examples of each of the notifyXXXXXXX user call-back functions 11 | 12 | LiquidCrystal_I2C lcd(0x20, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); 13 | 14 | lnMsg *LnPacket; 15 | 16 | void setup() { 17 | // First initialize the LocoNet interface 18 | LocoNet.init(); 19 | 20 | // Configure the serial port for 57600 baud 21 | Serial.begin(57600); 22 | Serial.println("LocoNet Monitor"); 23 | 24 | lcd.begin(16,2); // initialize the lcd 25 | lcdPrint("Loconet", "Railcom Monitor"); 26 | } 27 | 28 | void loop() { 29 | // Check for any received LocoNet packets 30 | LnPacket = LocoNet.receive() ; 31 | if( LnPacket ) { 32 | 33 | unsigned char opcode = (int)LnPacket->sz.command; 34 | 35 | if (opcode == OPC_GPON) { // GLOBAL power ON request 0x83 36 | lcdPrint("GLOBAL power ON", ""); 37 | } else if (opcode == OPC_GPOFF) { // GLOBAL power OFF req 0x82 38 | lcdPrint("GLOBAL power OFF", ""); 39 | } 40 | 41 | // First print out the packet in HEX 42 | Serial.print("RX: "); 43 | uint8_t msgLen = getLnMsgSize(LnPacket); 44 | for (uint8_t x = 0; x < msgLen; x++) 45 | { 46 | uint8_t val = LnPacket->data[x]; 47 | // Print a leading 0 if less than 16 to make 2 HEX digits 48 | if(val < 16) 49 | Serial.print('0'); 50 | 51 | Serial.print(val, HEX); 52 | Serial.print(' '); 53 | } 54 | 55 | // If this packet was not a Switch or Sensor Message then print a new line 56 | if(!LocoNet.processSwitchSensorMessage(LnPacket)) { 57 | Serial.println(); 58 | } 59 | } 60 | } 61 | 62 | void lcdPrint(String line1, String line2) { 63 | lcd.clear(); 64 | lcd.home (); 65 | lcd.print(line1); 66 | lcd.setCursor(0,1); 67 | lcd.print(line2); 68 | } 69 | 70 | // This call-back function is called from LocoNet.processSwitchSensorMessage 71 | // for all Sensor messages 72 | void notifySensor( uint16_t Address, uint8_t State ) { 73 | Serial.print("Sensor: "); 74 | Serial.print(Address, DEC); 75 | Serial.print(" - "); 76 | Serial.println( State ? "Active" : "Inactive" ); 77 | } 78 | 79 | // This call-back function is called from LocoNet.processSwitchSensorMessage 80 | // for all Switch Request messages 81 | void notifySwitchRequest( uint16_t Address, uint8_t Output, uint8_t Direction ) { 82 | Serial.print("Switch Request: "); 83 | Serial.print(Address, DEC); 84 | Serial.print(':'); 85 | Serial.print(Direction ? "Closed" : "Thrown"); 86 | Serial.print(" - "); 87 | Serial.println(Output ? "On" : "Off"); 88 | } 89 | 90 | // This call-back function is called from LocoNet.processSwitchSensorMessage 91 | // for all Switch Report messages 92 | void notifySwitchReport( uint16_t Address, uint8_t Output, uint8_t Direction ) { 93 | Serial.print("Switch Report: "); 94 | Serial.print(Address, DEC); 95 | Serial.print(':'); 96 | Serial.print(Direction ? "Closed" : "Thrown"); 97 | Serial.print(" - "); 98 | Serial.println(Output ? "On" : "Off"); 99 | } 100 | 101 | // This call-back function is called from LocoNet.processSwitchSensorMessage 102 | // for all Switch State messages 103 | void notifySwitchState( uint16_t Address, uint8_t Output, uint8_t Direction ) { 104 | Serial.print("Switch State: "); 105 | Serial.print(Address, DEC); 106 | Serial.print(':'); 107 | Serial.print(Direction ? "Closed" : "Thrown"); 108 | Serial.print(" - "); 109 | Serial.println(Output ? "On" : "Off"); 110 | } 111 | 112 | // This call-back function is called from LocoNet.processSwitchSensorMessage 113 | // for OPC_MULTI_SENSE 0xD0 114 | void notifyMultiSenseTransponder( uint16_t Address, uint8_t Zone, uint16_t LocoAddress, uint8_t Present ) { 115 | Serial.print("Railcom Sensor "); 116 | Serial.print(Address); 117 | Serial.print(" reports "); 118 | Serial.print(Present? "present" : "absent"); 119 | Serial.print(" of decoder address "); 120 | Serial.print(LocoAddress, DEC); 121 | Serial.print(" in zone "); 122 | Serial.println(Zone, HEX); 123 | 124 | String line1 = "Decoder "; 125 | line1 = line1 + LocoAddress; 126 | String line2 = Present ? "present" : "absent"; 127 | line2 = line2 + " zone " + Address; 128 | 129 | lcdPrint(line1, line2); 130 | } 131 | -------------------------------------------------------------------------------- /LocoNet_Servo_Decoder_LNCV/LocoNet_Servo_Decoder_LNCV.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define DEBUG 7 | 8 | #define NrServos 16 // Number of Servos Connected 9 | const uint8_t baseAddr = 2; // EEProm byte 0 and 1 are used for the decoder address so start storing servo settings from byte 2 10 | 11 | #define ARTNR 5001 // Item Number (Art.-Nr.): 50010 12 | uint16_t decoderAddress; 13 | uint16_t lncv[(NrServos*3)+1]; 14 | boolean programmingMode; 15 | 16 | typedef struct servoValues { 17 | uint16_t address; 18 | uint16_t servoMin; 19 | uint16_t servoMax; 20 | }; 21 | 22 | servoValues settings[NrServos]; 23 | 24 | Adafruit_PWMServoDriver servo = Adafruit_PWMServoDriver(0x40); 25 | 26 | // Loconet 27 | #define LOCONET_TX_PIN 7 28 | static lnMsg *LnPacket; 29 | LocoNetCVClass lnCV; 30 | 31 | /****************************************************************************** 32 | * Setup 33 | ******************************************************************************/ 34 | void setup() { 35 | 36 | // Read Settings from EEProm 37 | readEEProm(); 38 | 39 | // First initialize the LocoNet interface 40 | LocoNet.init(LOCONET_TX_PIN); 41 | 42 | Serial.begin(57600); 43 | 44 | Serial.print("\nLoconet Servo Decoder #"); 45 | Serial.println(decoderAddress, DEC); 46 | 47 | for(int i=0; i> 8) & 0xFF ); 115 | EEPROM.write(1, decoderAddress & 0xFF); 116 | 117 | // write servoValue array to eeprom 118 | // servoValue array takes 3x2 bytes 119 | // addresses are baseAddr + address every 6 bytes 120 | for (uint8_t i=0; i < NrServos; i++){ 121 | eeprom_write_block((const void*)&settings[i], (void*) (i*6)+baseAddr, sizeof(settings[i])); // const void * data, void * dest = i*6 + baseAddr, size_t size 122 | } 123 | } 124 | 125 | /****************************************************************************** 126 | * Pulse Length in seconds routine 127 | * setServoPulse(0, 0.001) is a ~1 millisecond pulse width. its not precise! 128 | ******************************************************************************/ 129 | void setServoPulse(uint8_t n, double pulse) { 130 | double pulselength; 131 | 132 | pulselength = 1000000; // 1,000,000 us per second 133 | pulselength /= 60; // 60 Hz 134 | //Serial.print(pulselength); Serial.println(" us per period"); 135 | pulselength /= 4096; // 12 bits of resolution 136 | //Serial.print(pulselength); Serial.println(" us per bit"); 137 | pulse *= 1000; 138 | pulse /= pulselength; 139 | //Serial.println(pulse); 140 | servo.setPWM(n, 0, pulse); 141 | } 142 | 143 | /****************************************************************************** 144 | * Routine to Set Servo 145 | ******************************************************************************/ 146 | void setServo(uint8_t servoNum, uint8_t Direction) { 147 | 148 | if( Direction == 1 ) { 149 | for (uint16_t pulselen = settings[servoNum].servoMin; pulselen < settings[servoNum].servoMax; pulselen++) { 150 | servo.setPWM(servoNum, 0, pulselen); 151 | } 152 | } else { 153 | for (uint16_t pulselen = settings[servoNum].servoMax; pulselen > settings[servoNum].servoMin; pulselen--) { 154 | servo.setPWM(servoNum, 0, pulselen); 155 | } 156 | } 157 | } 158 | 159 | /****************************************************************************** 160 | * This call-back function is called from LocoNet.processSwitchSensorMessage 161 | * for all Switch Request messages 162 | ******************************************************************************/ 163 | void notifySwitchRequest( uint16_t Address, uint8_t Output, uint8_t Direction ) { 164 | Serial.print("Switch Request: "); 165 | Serial.print(Address, DEC); 166 | Serial.print(':'); 167 | Serial.print(Direction ? "Closed" : "Thrown"); 168 | Serial.print(" - "); 169 | Serial.println(Output ? "On" : "Off"); 170 | 171 | // check if the Address is meant for us 172 | for (uint8_t i=0; i 0 && lncvAddress < (NrServos*3)+1 ) { 261 | lncv[lncvAddress] = lncvValue; 262 | Serial.print("LNCV "); 263 | Serial.print(lncvAddress, DEC); 264 | Serial.print(" -> "); 265 | Serial.println(lncvValue); 266 | return LNCV_LACK_OK; 267 | } 268 | else { 269 | Serial.println("LACK ERROR"); 270 | return LNCV_LACK_ERROR_UNSUPPORTED; 271 | } 272 | 273 | } 274 | else { 275 | Serial.print("Artnr Invalid.\n"); 276 | return -1; 277 | } 278 | } 279 | 280 | /** 281 | * Notifies the code on the reception of a request to end programming mode 282 | */ 283 | void notifyLNCVprogrammingStop(uint16_t ArtNr, uint16_t ModuleAddress) { 284 | Serial.print("notifyLNCVprogrammingStop "); 285 | if (programmingMode) { 286 | if (ArtNr == ARTNR && ModuleAddress == lncv[0]) { 287 | programmingMode = false; 288 | Serial.print("End Programing Mode.\n"); 289 | Serial.print("Module Address is now: "); 290 | Serial.println(lncv[0], DEC); 291 | 292 | int Adr = 1; 293 | int Min = 2; 294 | int Max = 3; 295 | 296 | LNCVtoSettings(); 297 | 298 | writeEEProm(); 299 | Serial.println("EEProm Updated"); 300 | 301 | // wait 250msec and reboot 302 | delay(250); 303 | resetFunc(); 304 | } 305 | else { 306 | if (ArtNr != ARTNR) { 307 | Serial.print("Wrong Artnr.\n"); 308 | return; 309 | } 310 | if (ModuleAddress != lncv[0]) { 311 | Serial.print("Wrong Module Address: "); 312 | Serial.print(ModuleAddress, DEC); 313 | Serial.print(" : "); 314 | Serial.println(lncv[0], DEC); 315 | return; 316 | } 317 | } 318 | } 319 | else { 320 | Serial.print("Ignoring Request.\n"); 321 | } 322 | } 323 | 324 | void LNCVtoSettings() { 325 | 326 | decoderAddress = lncv[0]; 327 | 328 | int Adr = 1; 329 | int Min = 2; 330 | int Max = 3; 331 | 332 | for(int i=0; i. 17 | * 18 | ************************************************************************************************************ 19 | 20 | DESCRIPTION: 21 | This is a Marklin 6040 Keyboard converted to Loconet. 22 | - The original i2c PCB is removed and replaced with an Arduino's and a 16bit i2c MCP23016 port expander. 23 | - The PCA9685 handles all the LED's. The Arduino the Keypresses and LocoNet 24 | - The top Marklin PCB wth 32 buttons and LEDs is reused. 25 | - There are 2 vesions of the top PCB. The older version has 2 shift registers and does not need the MCP23016. 26 | - The 4 pin switch on the back is reused for address selection. 27 | 28 | - LNCV programming is on the todo list. 29 | 30 | Loconet: 31 | - TX pin D7 32 | - RX pin D8 33 | You MUST connect the RX input to the AVR ICP pin which on an Arduino UNO is digital pin 8. 34 | The TX output can be any Arduino pin, but the LocoNet library defaults to digital pin 6 for TX 35 | 36 | i2c: 37 | - SDA pin A4 38 | - SCL pin A5 39 | 40 | Address Switch: (On Master) 41 | - switch 1 pin D5 42 | - switch 2 pin D4 43 | - switch 3 pin D3 44 | - switch 4 pin D2 45 | 46 | LEDs: 47 | - top row - PCA9685 01-08 48 | - bottom row - PCA9685 09-16 49 | /************************************************************************************************************/ 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | // Debug Mode 58 | boolean debugMode = true; 59 | 60 | // Loconet 61 | #define LOCONET_TX_PIN 7 62 | static lnMsg *LnPacket; 63 | static LnBuf LnTxBuffer ; 64 | 65 | // Keypad 66 | #define I2CADDR 0x20 67 | const byte ROWS = 4; //4 rows 68 | const byte COLS = 8; //8 columns 69 | 70 | //define the cymbols on the buttons of the keypads 71 | char hexaKeys[ROWS][COLS] = { 72 | { 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 }, 73 | { 0x09 , 0x0A , 0x0B , 0x0C , 0x0D , 0x0E , 0x0F , 0x10 }, 74 | { 0x11 , 0x12 , 0x13 , 0x14 , 0x15 , 0x16 , 0x17 , 0x18 }, 75 | { 0x19 , 0x1A , 0x1B , 0x1C , 0x1D , 0x1E , 0x1F , 0x20 }, 76 | }; 77 | 78 | byte buttonRowPins[ROWS] = {8, 9, 10, 11}; //connect to the row pinouts of the keypad 79 | byte buttonColPins[COLS] = {7, 6, 5, 4, 3, 2, 1, 0}; //connect to the column pinouts of the keypad 80 | 81 | int ADDRESSES[16]; 82 | 83 | boolean buttonState[16]; 84 | boolean waitForLack; 85 | uint16_t LackAddress; 86 | 87 | //initialize an instance of class NewKeypad 88 | Keypad_MC16 MarklinKeypad( makeKeymap(hexaKeys), buttonRowPins, buttonColPins, ROWS, COLS, I2CADDR); 89 | 90 | // Adress Switch on the Back 91 | int adrr_switch[4] = { 5, 4, 3, 2 }; 92 | byte address = 0; // Byte to store Address (Only the 4 LSB are used) 93 | 94 | // you can also call it with a different address you want 95 | Adafruit_PWMServoDriver led = Adafruit_PWMServoDriver(0x43); 96 | 97 | /*****************************************************************************/ 98 | /* Setup */ 99 | /*****************************************************************************/ 100 | void setup() { 101 | 102 | // start the serial port 103 | if (debugMode) 104 | Serial.begin(57600); 105 | 106 | // Read Address Pins 107 | for (int i = 0; i < 4; i++) { 108 | pinMode(adrr_switch[i], INPUT_PULLUP); 109 | boolean reading = !digitalRead(adrr_switch[i]); 110 | bitWrite(address, i, reading); 111 | } 112 | address++; 113 | 114 | if(EEPROM.read(0) == address) { 115 | Serial.println("Address matches EEPROM"); 116 | } else { 117 | Serial.println("Address Changed"); 118 | EEPROM.write(0, address); 119 | } 120 | 121 | // Allocate the Addresses to the Buttons 122 | for(int i = 0; i < 16; i++) { 123 | ADDRESSES[i] = (((address - 1) * 16) + (i + 1)); 124 | 125 | // Load button states from EEPROM 126 | byte state = EEPROM.read(i+16); 127 | switch(state) { 128 | case 0x00: 129 | buttonState[i] = 1; 130 | break; 131 | case 0xFF: 132 | buttonState[i] = 0; 133 | break; 134 | } 135 | } 136 | 137 | // Initialize Keypad 138 | MarklinKeypad.begin( ); 139 | MarklinKeypad.addEventListener(keypadEvent); 140 | MarklinKeypad.setHoldTime(1000); 141 | MarklinKeypad.setDebounceTime(100); 142 | 143 | // Initialize the LED Driver 144 | led.begin(); 145 | led.setPWMFreq(500); 146 | 147 | // Initialize the LocoNet interface 148 | LocoNet.init(LOCONET_TX_PIN); // The TX output in LocoNet library defaults to digital pin 6 for TX 149 | 150 | if (debugMode) { 151 | Serial.print("6040 Loconet. Address: "); 152 | Serial.println(address); 153 | Serial.println("Buttons:"); 154 | for (int i = 0; i < 8; i++) { 155 | if(ADDRESSES[i] < 10) Serial.print("0"); 156 | Serial.print(ADDRESSES[i]); 157 | Serial.print(" "); 158 | } 159 | Serial.println(); 160 | for (int i = 8; i < 16; i++) { 161 | if(ADDRESSES[i] < 10) Serial.print("0"); 162 | Serial.print(ADDRESSES[i]); 163 | Serial.print(" "); 164 | } 165 | Serial.println(); 166 | } 167 | 168 | // Blink Leds One Time then blink address LED 3 times and reset Leds 169 | // Test All LEDS 170 | for (int i=0;i<16;i++){ 171 | setLed(i, 1); 172 | } 173 | delay(750); 174 | for (int i=0;i<16;i++){ 175 | setLed(i, 0); 176 | } 177 | delay(250); 178 | 179 | // Bink Led Matching Address 180 | for (int i=0;i<3;i++){ 181 | setLed(address-1, 1); 182 | delay(250); 183 | setLed(address-1, 0); 184 | delay(250); 185 | } 186 | 187 | // Reset LEDS 188 | for (int i=0;i<16;i++){ 189 | setLed(i, 0); 190 | } 191 | 192 | initLnBuf(&LnTxBuffer); 193 | 194 | getUpdate(); 195 | } 196 | 197 | /*****************************************************************************/ 198 | /* Program Loop */ 199 | /*****************************************************************************/ 200 | void loop() { 201 | 202 | // Check for any received LocoNet packets 203 | LnPacket = LocoNet.receive() ; 204 | if( LnPacket ) 205 | { 206 | // Request status on GLOBAL power ON request 0x83 207 | if ( LnPacket -> data[0] == OPC_GPON ) { 208 | //getUpdate(); 209 | } 210 | 211 | // First print out the packet in HEX 212 | Serial.print("RX: "); 213 | uint8_t msgLen = getLnMsgSize(LnPacket); 214 | for (uint8_t x = 0; x < msgLen; x++) 215 | { 216 | uint8_t val = LnPacket->data[x]; 217 | // Print a leading 0 if less than 16 to make 2 HEX digits 218 | if(val < 16) 219 | Serial.print('0'); 220 | 221 | Serial.print(val, HEX); 222 | Serial.print(' '); 223 | } 224 | 225 | 226 | // If this packet was not a Switch or Sensor Message then print a new line 227 | if(!LocoNet.processSwitchSensorMessage(LnPacket)) 228 | Serial.println(); 229 | } 230 | 231 | // Check Keypad for Button Press 232 | char button = MarklinKeypad.getKey(); 233 | 234 | updateLeds(); 235 | 236 | } 237 | 238 | 239 | /*****************************************************************************/ 240 | /* Callback functions to Check Keypad */ 241 | /*****************************************************************************/ 242 | void keypadEvent(KeypadEvent button) { 243 | 244 | int data = (int) button; 245 | int buttonAddress = 0; 246 | boolean DIRECTION = 0; 247 | 248 | // Red Top Row 249 | if (data >= 1 && data <= 8) { 250 | buttonAddress = ADDRESSES[data - 1]; 251 | DIRECTION = 0; 252 | } 253 | // Green Top Row 254 | if (data >= 9 && data <= 16) { 255 | buttonAddress = ADDRESSES[data - 9]; 256 | DIRECTION = 1; 257 | } 258 | // Red Bottom Row 259 | if (data >= 17 && data <= 24) { 260 | buttonAddress = ADDRESSES[data - 9]; 261 | DIRECTION = 0; 262 | } 263 | // Green Botton Row 264 | if (data >= 25 && data <= 32) { 265 | buttonAddress = ADDRESSES[data - 17]; 266 | DIRECTION = 1; 267 | } 268 | 269 | switch (MarklinKeypad.getState()) { 270 | case PRESSED: 271 | if (debugMode) { 272 | Serial.print("Button "); 273 | Serial.print(data); 274 | Serial.println(" PRESSED"); 275 | } 276 | LocoNet.requestSwitch(buttonAddress, 1, DIRECTION); 277 | processKeypad(button); 278 | break; 279 | 280 | case HOLD: 281 | if (debugMode) { 282 | Serial.print("Button "); 283 | Serial.print(data); 284 | Serial.println(" HOLD"); 285 | } 286 | break; 287 | 288 | case RELEASED: 289 | if (debugMode) { 290 | Serial.print("Button "); 291 | Serial.print(data); 292 | Serial.println(" RELEASED"); 293 | } 294 | LocoNet.requestSwitch(buttonAddress, 0, DIRECTION); 295 | processKeypad(button); 296 | break; 297 | } 298 | 299 | } 300 | 301 | /*****************************************************************************/ 302 | /* Subroutine to Process Keypad Data */ 303 | /*****************************************************************************/ 304 | void processKeypad(char button) { 305 | 306 | int data = (int) button; 307 | int e; 308 | byte state; 309 | 310 | if (data >= 1 && data <= 8) { buttonState[(data - 1)] = 1; e = data - 1; state = 0xff; } 311 | if (data >= 9 && data <= 16) { buttonState[(data - 9)] = 0; e = data - 9; state = 0x00; } 312 | 313 | if (data >= 17 && data <= 24) { buttonState[(data - 9)] = 1; e = data - 9; state = 0xff; } 314 | if (data >= 25 && data <= 32) { buttonState[(data - 17)] = 0; e = data - 17; state = 0x00; } 315 | 316 | e = e+16; 317 | EEPROM.write(e, state); 318 | } 319 | 320 | /*****************************************************************************/ 321 | /* Subroutine to Request Status Update */ 322 | /*****************************************************************************/ 323 | void getUpdate() { 324 | 325 | for(int i=0;i<16;i++) { 326 | LocoNet.requestSwitch(ADDRESSES[i], 0, buttonState[i]); 327 | } 328 | } 329 | 330 | /*****************************************************************************/ 331 | /* Subroutine to check and set LEDS */ 332 | /*****************************************************************************/ 333 | void updateLeds() { 334 | 335 | for (int i = 0; i < 16; i++) { 336 | setLed(i, buttonState[i]); 337 | } 338 | } 339 | 340 | /*****************************************************************************/ 341 | /* Subroutine to switch LED */ 342 | /*****************************************************************************/ 343 | void setLed(int LED, bool state) { 344 | if (state == true) { 345 | led.setPWM(LED, 0, 150); 346 | } else { 347 | led.setPWM(LED, 0, 4096); 348 | } 349 | } 350 | 351 | /*****************************************************************************/ 352 | /* Called to update Leds */ 353 | /*****************************************************************************/ 354 | void processUpdate( uint16_t Address, uint8_t Direction ) { 355 | for(int i = 0; i < 16; i++) { 356 | if( Address == ADDRESSES[i] ) { 357 | 358 | switch (Direction) { 359 | case 0x00: // thrown(red) 360 | buttonState[i] = 1; 361 | break; 362 | case 0x20: // closed(green) 363 | buttonState[i] = 0; 364 | break; 365 | default: 366 | Serial.println("Unknown"); 367 | break; 368 | } 369 | } 370 | } 371 | } 372 | 373 | /*****************************************************************************/ 374 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 375 | /* for all Sensor messages */ 376 | /*****************************************************************************/ 377 | void notifySensor( uint16_t Address, uint8_t State ) { 378 | 379 | Serial.print("Sensor: "); 380 | Serial.print(Address, DEC); 381 | Serial.print(" - "); 382 | Serial.println( State ? "Active" : "Inactive" ); 383 | } 384 | 385 | /*****************************************************************************/ 386 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 387 | /* for all Switch Request messages */ 388 | /*****************************************************************************/ 389 | void notifySwitchRequest( uint16_t Address, uint8_t Output, uint8_t Direction ) { 390 | 391 | Serial.print("Switch Request: "); 392 | Serial.print(Address, DEC); 393 | Serial.print(':'); 394 | Serial.print(Direction ? "Closed" : "Thrown"); 395 | Serial.print(" - "); 396 | Serial.println(Output ? "On" : "Off"); 397 | 398 | processUpdate(Address, Direction); 399 | } 400 | 401 | /*****************************************************************************/ 402 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 403 | /* for all Switch Report messages */ 404 | /*****************************************************************************/ 405 | void notifySwitchReport( uint16_t Address, uint8_t Output, uint8_t Direction ) { 406 | 407 | Serial.print("Switch Report: "); 408 | Serial.print(Address, DEC); 409 | Serial.print(':'); 410 | Serial.print(Direction ? "Closed" : "Thrown"); 411 | Serial.print(" - "); 412 | Serial.println(Output ? "On" : "Off"); 413 | } 414 | 415 | /*****************************************************************************/ 416 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 417 | /* for all Switch State messages */ 418 | /*****************************************************************************/ 419 | void notifySwitchState( uint16_t Address, uint8_t Output, uint8_t Direction ) { 420 | 421 | Serial.print("Switch State: "); 422 | Serial.print(Address, DEC); 423 | Serial.print(':'); 424 | Serial.print(Direction, HEX); //? "Closed" : "Thrown"); 425 | Serial.print(" - "); 426 | Serial.println(Output ? "On" : "Off"); 427 | 428 | waitForLack = 1; 429 | LackAddress = Address; 430 | } 431 | 432 | -------------------------------------------------------------------------------- /Loconet_6040_PCA9685/README.md: -------------------------------------------------------------------------------- 1 | # LocoNet 6040 Conversion 2 | This is code for the conversion of a Marklin 6040 to loconet using an Arduino a MCP23016 port expander and a PCA9685 3 | -------------------------------------------------------------------------------- /Loconet_Master_Switch/Loconet_Master_Switch.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************ 2 | * 3 | * Copyright (C) 2015-2016 Timo Sariwating 4 | * Edit by Septillion (Timo Engelgeer) January 2, 2016 5 | * 6 | * This program 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 program 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 GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, If not, see . 18 | * 19 | ************************************************************************************************************ 20 | 21 | DESCRIPTION: 22 | This is a Loconet OPC_GPON and OPC_GPOFF Switch 23 | - It uses an Arduino Pro Mini 5v 16MHz 24 | 25 | Loconet: 26 | - TX pin D7 27 | - RX pin D8 28 | You MUST connect the RX input to the AVR ICP pin which on an Arduino UNO is digital pin 8. 29 | The TX output can be any Arduino pin, but the LocoNet library defaults to digital pin 6 for TX 30 | 31 | Libraries: 32 | Uses the Bounce2 an Loconet libraries. They need to be installed in order to compile. 33 | Bounce2: https://github.com/thomasfredericks/Bounce2 34 | Loconet: https://github.com/mrrwa/LocoNet 35 | 36 | /************************************************************************************************************/ 37 | #include 38 | #include 39 | 40 | //Buttons 41 | const byte RedButtonPin = 3; //of NC type 42 | const byte GreenButtonPin = 2; //of NO type 43 | 44 | //LEDs 45 | const byte RedLed = 6; 46 | const byte GreenLed = 5; 47 | 48 | // Loconet 49 | #define LOCONET_TX_PIN 7 50 | static lnMsg *LnPacket; 51 | static LnBuf LnTxBuffer; 52 | 53 | // Variables will change: 54 | boolean OPCSTATE = 0; //For state control center (on/off) 55 | 56 | Bounce buttonRed, buttonGreen; //The button objects of Bounce2 library 57 | 58 | 59 | /*************************************************************************/ 60 | /* Setup */ 61 | /*************************************************************************/ 62 | void setup() { 63 | 64 | // Setup the buttons 65 | buttonRed.attach(RedButtonPin, INPUT_PULLUP); 66 | buttonGreen.attach(GreenButtonPin, INPUT_PULLUP); 67 | 68 | // Set up the outputs 69 | pinMode(RedLed, OUTPUT); 70 | pinMode(GreenLed, OUTPUT); 71 | 72 | // Initialize the LocoNet interface 73 | LocoNet.init(LOCONET_TX_PIN); // The TX output in LocoNet library defaults to digital pin 6 for TX 74 | 75 | } 76 | 77 | /*************************************************************************/ 78 | /* Send OPC_GP */ 79 | /*************************************************************************/ 80 | void sendOPC_GP(byte on) { 81 | lnMsg SendPacket; 82 | if (on) { 83 | SendPacket.data[ 0 ] = OPC_GPON; 84 | } else { 85 | SendPacket.data[ 0 ] = OPC_GPOFF; 86 | } 87 | LocoNet.send( &SendPacket ) ; 88 | } 89 | 90 | /*************************************************************************/ 91 | /* Program Loop */ 92 | /*************************************************************************/ 93 | void loop() { 94 | // Read the Buttons 95 | readButtons(); 96 | 97 | // Check LocoNet for a power state update 98 | checkLocoNet(); 99 | 100 | // Set the LEDs 101 | setLed(); 102 | } 103 | 104 | /*************************************************************************/ 105 | /* Read the Red and Green Buttons */ 106 | /*************************************************************************/ 107 | void readButtons() { 108 | //Read the actual buttons 109 | buttonRed.update(); 110 | buttonGreen.update(); 111 | 112 | //check for press of the red button (rose, because of the NC type) 113 | if(buttonRed.rose()){ 114 | sendOPC_GP(0); //Send new state to controle center 115 | OPCSTATE = 0; //and save the new state 116 | } 117 | //Check is the green button became pressed (fell, because of NO type) 118 | else if(buttonGreen.fell()){ 119 | sendOPC_GP(1); //Send new state to controle center 120 | OPCSTATE = 1; //and save the new state 121 | } 122 | } 123 | 124 | /*************************************************************************/ 125 | /* Read the Red and Green Buttons */ 126 | /*************************************************************************/ 127 | void checkLocoNet() { 128 | // Check for any received LocoNet packets 129 | LnPacket = LocoNet.receive() ; 130 | if( LnPacket ) 131 | { 132 | if (LnPacket->sz.command == OPC_GPON) { // GLOBAL power ON request 0x83 133 | OPCSTATE = 1; 134 | } else if (LnPacket->sz.command == OPC_GPOFF) { // GLOBAL power OFF req 0x82 135 | OPCSTATE = 0; 136 | } 137 | } 138 | } 139 | 140 | 141 | /*************************************************************************/ 142 | /* Set the Red and Green Leds */ 143 | /*************************************************************************/ 144 | void setLed() { 145 | digitalWrite(RedLed, !OPCSTATE); 146 | digitalWrite(GreenLed, OPCSTATE); 147 | } 148 | -------------------------------------------------------------------------------- /Loconet_Master_Switch/README.md: -------------------------------------------------------------------------------- 1 | # LocoNet Master Switch 2 | This is code for a LocoNet Master Switch using an Arduino 3 | 4 | Links: 5 | - Video 6 | https://www.youtube.com/edit?o=U&video_id=awdsWNFs_F0 7 | -------------------------------------------------------------------------------- /Loconet_Pendelzug/Loconet_Pendelzug.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************ 2 | * 3 | * Copyright (C) 2017 Timo Sariwating 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, If not, see . 17 | * 18 | ************************************************************************************************************ 19 | 20 | DESCRIPTION: 21 | This is a Loconet Pendelzugsteuerung 22 | - It uses an Arduino Pro Mini 5v 16MHz 23 | 24 | Loconet: 25 | - TX pin D7 26 | - RX pin D8 27 | You MUST connect the RX input to the AVR ICP pin which on an Arduino UNO is digital pin 8. 28 | The TX output can be any Arduino pin, but the LocoNet library defaults to digital pin 6 for TX 29 | 30 | Libraries: 31 | Uses the Loconet librarie. It needs to be installed in order to compile. 32 | Loconet: https://github.com/mrrwa/LocoNet 33 | 34 | * State 1 = Stop in Station A 35 | * State 2 = Running between A and B 36 | * State 3 = Break for Station B 37 | * State 4 = Stop in Station B 38 | * State 5 = Running between B and A 39 | * State 6 = Break for Station A 40 | 41 | /************************************************************************************************************/ 42 | #include 43 | 44 | // Loconet 45 | #define LOCONET_TX_PIN 7 46 | static lnMsg *LnPacket; 47 | uint8_t UB_SRC_MASTER = 0x00; 48 | 49 | // Variables will change: 50 | uint8_t mySlot; 51 | uint8_t runState = 0; 52 | boolean runPendel = false; 53 | boolean sendCommand = false; 54 | unsigned long waitTimer, updateTimer; 55 | 56 | // Change these wait timer to your preference 57 | uint16_t MIN_WAIT = 5; 58 | uint16_t MAX_WAIT = 20; 59 | 60 | // Change This to the Locomotive Address 61 | uint16_t Address = 28; 62 | 63 | // Define your feedback Addresses here 64 | #define Run 13 // Change This to feedback Address for the Run Button 65 | #define Stop 14 // Change This to feedback Address for the Stop Button 66 | #define SensorBrakeA 12 // Change This to feedback Address for Station A Breaking 67 | #define SensorStopA 11 // Change This to feedback Address for Station A Stop 68 | #define SensorBrakeB 15 // Change This to feedback Address for Station B Breaking 69 | #define SensorStopB 16 // Change This to feedback Address for Station B Stop 70 | 71 | /******************************************************************************/ 72 | /* Setup */ 73 | /******************************************************************************/ 74 | void setup() { 75 | 76 | // First initialize the LocoNet interface 77 | LocoNet.init(LOCONET_TX_PIN); 78 | 79 | // Configure the serial port for 115200 baud 80 | Serial.begin(115200); 81 | Serial.println(); 82 | Serial.println(F("Loconet Pendelzugsteuerung")); 83 | 84 | LocoNet.send( OPC_LOCO_ADR, (uint8_t) ( Address >> 7 ), (uint8_t) ( Address & 0x7F ) ); 85 | 86 | } //Setup 87 | 88 | 89 | /******************************************************************************/ 90 | /* Program Loop */ 91 | /******************************************************************************/ 92 | void loop() { 93 | 94 | if(runPendel == true) { 95 | 96 | //------------------------------------------------------------------------------------------- 97 | // Timed Code 98 | //------------------------------------------------------------------------------------------- 99 | // Every 1 Second 100 | if ((millis()-updateTimer)>1000) { // 1 Second 101 | 102 | if (waitTimer != 0) { 103 | Serial.print("Wait Time - "); 104 | Serial.print(waitTimer, DEC); 105 | Serial.println(" Seconds"); 106 | } 107 | 108 | if(waitTimer == 0) { 109 | if(runState == 1) { 110 | runState = 2; // Starting from Station A 111 | sendCommand = true; 112 | } else if (runState == 4) { 113 | runState = 5; // Starting from Station B 114 | sendCommand = true; 115 | } 116 | } else { 117 | waitTimer--; 118 | } 119 | updateTimer = millis(); 120 | } 121 | 122 | if(sendCommand == true) { 123 | 124 | switch (runState) { 125 | case 0: // Train not in Station A or B 126 | Serial.println(F("Train not in Station A or B")); 127 | sendCommand = false; 128 | break; 129 | case 1: // Stop in Station A 130 | LocoNet.send( OPC_LOCO_DIRF, mySlot, 0x00); // Locomotive Forward Direction & Lights Off 131 | LocoNet.send( OPC_LOCO_SPD, mySlot, 0 ); // Locomotive Speed 0 132 | Serial.println(F("Stop in Station A")); 133 | waitTimer = getNewWaitTimer(); 134 | sendCommand = false; 135 | break; 136 | case 2: // Running between A and B 137 | LocoNet.send( OPC_LOCO_DIRF, mySlot, 0x10); // Locomotive Forware Direction & Lights On 138 | LocoNet.send( OPC_LOCO_SPD, mySlot, 50 ); // Locomotive Speed 50 139 | Serial.println(F("Running from Station A to B")); 140 | sendCommand = false; 141 | break; 142 | case 3: // Break for Station B 143 | LocoNet.send( OPC_LOCO_SPD, mySlot, 15 ); 144 | Serial.println(F("Breaking for Station B")); 145 | sendCommand = false; 146 | break; 147 | case 4: // Stop in Station B 148 | LocoNet.send( OPC_LOCO_DIRF, mySlot, 0x20); // Locomotive Reverse Direction & Lights Off 149 | LocoNet.send( OPC_LOCO_SPD, mySlot, 0 ); // Locomotive Speed 0 150 | Serial.println(F("Stop in Station B")); 151 | waitTimer = getNewWaitTimer(); 152 | sendCommand = false; 153 | break; 154 | case 5: // Running between B and A 155 | LocoNet.send( OPC_LOCO_DIRF, mySlot, 0x30); // Locomotive Reverse Direction & Lights On 156 | LocoNet.send( OPC_LOCO_SPD, mySlot, 50 ); // Locomotive Speed 50 157 | Serial.println(F("Running from Station B to A")); 158 | sendCommand = false; 159 | break; 160 | case 6: // Break for Station A 161 | LocoNet.send( OPC_LOCO_SPD, mySlot, 15 ); 162 | Serial.println(F("Breaking for Station A")); 163 | sendCommand = false; 164 | break; 165 | } 166 | 167 | } else { 168 | // Pendel not Running 169 | } 170 | } 171 | 172 | // Check for any received LocoNet packets 173 | LnPacket = LocoNet.receive() ; 174 | if ( LnPacket ) { 175 | 176 | switch (LnPacket -> data[0]) { 177 | case OPC_GPON: // Request status on GLOBAL power ON request 0x83 178 | Serial.println(F("[LOCONET]: OPC_GPON")); 179 | digitalWrite(LED_BUILTIN, HIGH); 180 | runState = 0; 181 | sendIBRequest(); 182 | runPendel = true; 183 | sendCommand = true; 184 | break; 185 | case OPC_GPOFF: 186 | Serial.println(F("[LOCONET]: OPC_GPOFF")); 187 | digitalWrite(LED_BUILTIN, LOW); 188 | runPendel = false; 189 | break; 190 | case OPC_PEER_XFER: 191 | if( LnPacket -> data[1] == 0x0F && LnPacket -> data[2] == UB_SRC_MASTER && LnPacket -> data[3] == 0x49 && LnPacket -> data[4] == 0x4B ) { 192 | int saddr = 0; 193 | byte pxct = LnPacket -> data[6]; 194 | byte d6 = LnPacket -> data[12]; 195 | byte d7 = LnPacket -> data[13]; 196 | uint16_t addr = LnPacket -> data[7]*16; 197 | 198 | /* move in the high bit: */ 199 | d6 = d6 | ((pxct&0x20)?0x80:0x00); 200 | d7 = d7 | ((pxct&0x40)?0x80:0x00); 201 | 202 | // S88 Port 01-08 203 | for( saddr = 0; saddr < 8; saddr++ ) { 204 | notifySensor( addr+(7-saddr)+1, d6 & (0x01 << saddr) ); 205 | } 206 | 207 | // S88 Port 09-16 208 | addr = LnPacket -> data[7]*16 + 8; 209 | for( saddr = 0; saddr < 8; saddr++ ) { 210 | notifySensor( addr+(7-saddr)+1, d7 & (0x01 << saddr) ); 211 | } 212 | } 213 | default: 214 | // If this packet was not a Switch or Sensor Message then print a new line 215 | if (!LocoNet.processSwitchSensorMessage(LnPacket)) { 216 | 217 | // Check if Received Address in Slot is Same as our Address 218 | if( LnPacket->sd.command == OPC_SL_RD_DATA && Address == (uint16_t) (( LnPacket->sd.adr2 << 7 ) + LnPacket->sd.adr )) { 219 | mySlot = LnPacket->sd.slot; 220 | Serial.print(F("Loco Address: ")); 221 | Serial.print(Address); 222 | Serial.print(F(" | Loconet Slot: ")); 223 | Serial.println(mySlot); 224 | } 225 | 226 | } 227 | } 228 | 229 | } 230 | } //Loop 231 | 232 | /******************************************************************************/ 233 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 234 | /* for all Sensor messages */ 235 | /******************************************************************************/ 236 | void notifySensor( uint16_t Address, uint8_t State ) { 237 | 238 | switch (Address) { 239 | case Run: 240 | if(State != 0) { 241 | runPendel = true; 242 | Serial.println(F("Run Pendelzug")); 243 | } 244 | break; 245 | case Stop: 246 | if(State != 0) { 247 | runPendel = false; 248 | Serial.println(F("Stop Pendelzug")); 249 | break; 250 | } 251 | case SensorStopA: 252 | if(State != 0) { 253 | runState = 1; 254 | sendCommand = true; 255 | } 256 | break; 257 | case SensorBrakeB: 258 | if(State != 0 && runState == 2) { 259 | runState = 3; 260 | sendCommand = true; 261 | } 262 | break; 263 | case SensorStopB: 264 | if(State != 0) { 265 | runState = 4; 266 | sendCommand = true; 267 | } 268 | break; 269 | case SensorBrakeA: 270 | if(State != 0 && runState == 5) { 271 | runState = 6; 272 | sendCommand = true; 273 | } 274 | break; 275 | default: 276 | break; 277 | } 278 | 279 | /* 280 | Serial.print("Sensor: "); 281 | Serial.print(Address, DEC); 282 | Serial.print(" - "); 283 | Serial.println( State ? "Active" : "Inactive" ); 284 | */ 285 | 286 | } //notifySensor 287 | 288 | /*************************************************************************/ 289 | /* Send Intellibox Sensor Status Request */ 290 | /*************************************************************************/ 291 | void sendIBRequest() { 292 | 293 | // Rocrail http://bazaar.launchpad.net/~rocrail-project/rocrail/Rocrail/view/head:/rocdigs/impl/loconet.c 294 | // Line 477 295 | 296 | /* 297 | SRC = 1 (KPU) 298 | DSTL/H = "I"/"B" = Intellibox (SPU) (i.e., 0x49 and 0x42) 299 | ReqId = 19 0x13 (same as you write) 300 | PXCT1 = 0 (unless you have an s88 module # higher than 128!) 301 | D1 = s88 module # (minus 1), i.e. 0 for the 1st s88 module 302 | D2..D7 = 0 303 | */ 304 | 305 | lnMsg SendPacket; 306 | 307 | SendPacket.data[ 0 ] = OPC_IMM_PACKET; 308 | SendPacket.data[ 1 ] = 0xF; 309 | SendPacket.data[ 2 ] = 0x1; /* SRC */ 310 | SendPacket.data[ 3 ] = 0x49; /* DSTL */ 311 | SendPacket.data[ 4 ] = 0x42; /* DSTH */ 312 | SendPacket.data[ 5 ] = 0x13; /* ReqID */ 313 | SendPacket.data[ 6 ] = 0x0; /* PXCT1 */ 314 | SendPacket.data[ 7 ] = 0x0; /* D1 */ // Change This to Your own feedback if needed 315 | SendPacket.data[ 8 ] = 0x0; /* D2 */ 316 | SendPacket.data[ 9 ] = 0x0; /* D3 */ 317 | SendPacket.data[ 10 ] = 0x0; /* D4 */ 318 | SendPacket.data[ 11 ] = 0x0; /* D5 */ 319 | SendPacket.data[ 12 ] = 0x0; /* D6 */ 320 | SendPacket.data[ 13 ] = 0x0; /* D7 */ 321 | 322 | LocoNet.send( &SendPacket ) ; 323 | } //sendIBRequest 324 | 325 | /*************************************************************************/ 326 | /* Get New Wait Timer */ 327 | /*************************************************************************/ 328 | uint16_t getNewWaitTimer(){ 329 | return random(MIN_WAIT, MAX_WAIT); 330 | } //getNewWaitTimer 331 | 332 | -------------------------------------------------------------------------------- /Loconet_Pendelzug/README.md: -------------------------------------------------------------------------------- 1 | # LocoNet Pendelzug 2 | This is code for a LocoNet Pendelzug / Shuttle train using an Arduino 3 | 4 | Links: 5 | - Video 6 | https://youtu.be/31Jurpu8cXE 7 | -------------------------------------------------------------------------------- /Loconet_UDP_Monitor/Loconet_UDP_Monitor.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************ 2 | * 3 | * Copyright (C) 2015 Timo Sariwating 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, If not, see . 17 | * 18 | ************************************************************************************************************ 19 | DESCRIPTION: 20 | This is a LocoNet Packet Monitor 21 | - It uses Ethernet UDP Multicast using a W5100 Etheret Card. 22 | 23 | Demonstrates the use of the: 24 | - LocoNet.processSwitchSensorMessage(LnPacket) function and examples 25 | of each of the notifyXXXXXXX user call-back functions. 26 | 27 | /************************************************************************************************************/ 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | // Ethernet 34 | // The MAC address of your Ethernet board (or Ethernet Shield) is located on the back of the circuit board. 35 | byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0x02 }; // Arduino Ethernet 36 | EthernetClient client; 37 | 38 | // Change this to your own network 39 | byte ip[] = { 192,168,1,10 }; 40 | 41 | // Multicast 42 | IPAddress multicast(224,0,0,1); 43 | unsigned int port = 1235; 44 | 45 | // An EthernetUDP instance to let us send and receive packets over UDP 46 | EthernetUDP Udp; 47 | 48 | // Loconet 49 | static LnBuf LnTxBuffer; 50 | static lnMsg *LnPacket; 51 | 52 | /*************************************************************************/ 53 | /* Setup */ 54 | /*************************************************************************/ 55 | void setup() { 56 | 57 | // start the Ethernet and UDP: 58 | Ethernet.begin(mac,ip); 59 | Udp.beginMulticast(multicast ,port); 60 | 61 | // Initialize a LocoNet packet buffer to buffer bytes from the PC 62 | initLnBuf(&LnTxBuffer); 63 | 64 | // Configure the serial port for 57600 baud 65 | Serial.begin(57600); 66 | Serial.println("LocoNet UDP Multicast Monitor"); 67 | } 68 | 69 | /*************************************************************************/ 70 | /* Program Loop */ 71 | /*************************************************************************/ 72 | void loop() { 73 | 74 | // Check for any received LocoNet packets 75 | LoconetRX(); 76 | } 77 | 78 | /*************************************************************************/ 79 | /* Check for any received LocoNet packets */ 80 | /*************************************************************************/ 81 | void LoconetRX() { 82 | 83 | // Parse and display incoming packets 84 | int packetSize = Udp.parsePacket(); 85 | 86 | if(packetSize) { 87 | char packetBuffer[packetSize]; //buffer to hold incoming packet, 88 | 89 | Serial.print("Received packet of size "); 90 | Serial.print(packetSize); 91 | Serial.print(" From: "); 92 | IPAddress remote = Udp.remoteIP(); 93 | for (int i =0; i < 4; i++) { 94 | Serial.print(remote[i], DEC); 95 | if (i < 3) 96 | { 97 | Serial.print("."); 98 | } 99 | } 100 | Serial.print(":"); 101 | Serial.print(Udp.remotePort()); 102 | Serial.print(" - "); 103 | 104 | // read the packet into packetBufffer 105 | Udp.read(packetBuffer,packetSize); 106 | 107 | // move it to LnTXBuffer 108 | for (int i=0; i < packetSize; i++) { 109 | // Add it to the buffer 110 | addByteLnBuf( &LnTxBuffer, packetBuffer[i] & 0xFF); 111 | } 112 | 113 | } 114 | // Check to see if we have received a complete packet yet 115 | LnPacket = recvLnMsg( &LnTxBuffer ); 116 | 117 | if( LnPacket ) { 118 | 119 | // First print out the packet in HEX 120 | Serial.print("Loconet: "); 121 | uint8_t msgLen = getLnMsgSize(LnPacket); 122 | for (uint8_t x = 0; x < msgLen; x++) 123 | { 124 | uint8_t val = LnPacket->data[x]; 125 | // Print a leading 0 if less than 16 to make 2 HEX digits 126 | if(val < 16) 127 | Serial.print('0'); 128 | 129 | Serial.print(val, HEX); 130 | Serial.print(' '); 131 | } 132 | 133 | // If this packet was not a Switch or Sensor Message then print a new line 134 | if(!LocoNet.processSwitchSensorMessage(LnPacket)) 135 | Serial.println(); 136 | } 137 | } 138 | 139 | /*************************************************************************/ 140 | /* Transmit 4 Byte LocoNet packet */ 141 | /*************************************************************************/ 142 | void LoconetTX4Byte (byte type, byte firstbyte, byte secondbyte) { 143 | 144 | byte checksum = 0xFF - (0xB2^ firstbyte ^ secondbyte); 145 | 146 | // Add bytes to the buffer 147 | addByteLnBuf( &LnTxBuffer, type ) ; 148 | addByteLnBuf( &LnTxBuffer, firstbyte ) ; 149 | addByteLnBuf( &LnTxBuffer, secondbyte ) ; 150 | addByteLnBuf( &LnTxBuffer, checksum ) ; 151 | 152 | // Check to see if we have a complete packet... 153 | LnPacket = recvLnMsg( &LnTxBuffer ); 154 | 155 | if( LnPacket ) { 156 | // Get the length of the packet 157 | uint8_t LnPacketSize = getLnMsgSize( LnPacket ); 158 | uint8_t locoTX[LnPacketSize]; 159 | 160 | for(int i = 0; i < LnPacketSize; i++) { 161 | locoTX[i] = LnPacket->data[i]; 162 | Serial.println(locoTX[i], HEX); 163 | } 164 | 165 | Udp.beginPacket(multicast, port); 166 | Udp.write(locoTX, LnPacketSize); 167 | Udp.endPacket(); 168 | } 169 | } 170 | 171 | /*****************************************************************************/ 172 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 173 | /* for all Sensor messages */ 174 | /*****************************************************************************/ 175 | void notifySensor( uint16_t Address, uint8_t State ) { 176 | 177 | Serial.print("Sensor: "); 178 | Serial.print(Address, DEC); 179 | Serial.print(" - "); 180 | Serial.println( State ? "Active" : "Inactive" ); 181 | } 182 | 183 | /*****************************************************************************/ 184 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 185 | /* for all Switch Request messages */ 186 | /*****************************************************************************/ 187 | void notifySwitchRequest( uint16_t Address, uint8_t Output, uint8_t Direction ) { 188 | 189 | Serial.print("Switch Request: "); 190 | Serial.print(Address, DEC); 191 | Serial.print(':'); 192 | Serial.print(Direction ? "Closed" : "Thrown"); 193 | Serial.print(" - "); 194 | Serial.println(Output ? "On" : "Off"); 195 | } 196 | 197 | /*****************************************************************************/ 198 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 199 | /* for all Switch Report messages */ 200 | /*****************************************************************************/ 201 | void notifySwitchReport( uint16_t Address, uint8_t Output, uint8_t Direction ) { 202 | 203 | Serial.print("Switch Report: "); 204 | Serial.print(Address, DEC); 205 | Serial.print(':'); 206 | Serial.print(Direction ? "Closed" : "Thrown"); 207 | Serial.print(" - "); 208 | Serial.println(Output ? "On" : "Off"); 209 | } 210 | 211 | /*****************************************************************************/ 212 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 213 | /* for all Switch Report messages */ 214 | /*****************************************************************************/ 215 | void notifySwitchOutputsReport( uint16_t Address, uint8_t Output, uint8_t Direction ) { 216 | 217 | Serial.print("Switch Outputs Report: "); 218 | Serial.print(Address, DEC); 219 | Serial.print(':'); 220 | Serial.print(Direction ? "Closed" : "Thrown"); 221 | Serial.print(" - "); 222 | Serial.println(Output ? "On" : "Off"); 223 | } 224 | 225 | /*****************************************************************************/ 226 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 227 | /* for all Switch State messages */ 228 | /*****************************************************************************/ 229 | void notifySwitchState( uint16_t Address, uint8_t Output, uint8_t Direction ) { 230 | 231 | Serial.print("Switch State: "); 232 | Serial.print(Address, DEC); 233 | Serial.print(':'); 234 | Serial.print(Direction, HEX); //? "Closed" : "Thrown"); 235 | Serial.print(" - "); 236 | Serial.println(Output ? "On" : "Off"); 237 | } 238 | -------------------------------------------------------------------------------- /Loconet_UDP_Monitor/README.md: -------------------------------------------------------------------------------- 1 | # LocoNet over Ethernet UDP using an Arduino 2 | A test for LocoNet over Ethernet UDP using an Arduino and Multicast -------------------------------------------------------------------------------- /Loconet_UDP_Signal_Decoder/Loconet_UDP_Signal_Decoder.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************ 2 | * 3 | * Copyright (C) 2015 Timo Sariwating 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program; if not, If not, see . 17 | * 18 | ************************************************************************************************************ 19 | DESCRIPTION: 20 | This is a LocoNet Signal Decoder 21 | - It uses Ethernet UDP Multicast using a W5100 Ethernet Card. 22 | 23 | Demonstrates the use of the: 24 | - LocoNet.processSwitchSensorMessage(LnPacket) function to switch signals 25 | - Using WS2812b as simple signals witch Adafruit NeoPixel library 26 | 27 | In this example 4 WS2812b are combined into two DB Ausfahrsignals. It uses two addresses per signal always 28 | starting from the odd/uneven address. 29 | Addr1 Aaddr2| Aspect | Signalbegriff 30 | ------------------------------------ 31 | R - R | 00 | HP0 -> RED/RED 32 | G - R | 01 | HP1 -> GREEN 33 | R - G | 02 | HP2 -> GREEN/YELLOW 34 | G - G | 03 | SH1 -> RED/WHITE 35 | 36 | /************************************************************************************************************/ 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | // Ethernet 44 | // The MAC address of your Ethernet board (or Ethernet Shield) is located on the back of the circuit board. 45 | byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0x02 }; // Arduino Ethernet 46 | EthernetClient client; 47 | 48 | // Change this to your own network 49 | byte ip[] = { 192,168,1,10 }; 50 | 51 | // Multicast 52 | IPAddress multicast(224,0,0,1); 53 | unsigned int port = 1235; 54 | 55 | // An EthernetUDP instance to let us send and receive packets over UDP 56 | EthernetUDP Udp; 57 | 58 | // Loconet 59 | static LnBuf LnTxBuffer; 60 | static lnMsg *LnPacket; 61 | 62 | uint16_t signalStartAddress[] = { 1, 17 }; 63 | uint8_t numberOfSignals = sizeof(signalStartAddress) / 2; 64 | 65 | byte signalState[sizeof(signalStartAddress) / 2]; 66 | 67 | // WS2812b 68 | // Which pin on the Arduino is connected to the NeoPixels? 69 | #define PIN 6 70 | 71 | // How many NeoPixels are attached to the Arduino? 72 | #define NUMPIXELS 4 73 | 74 | // When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals. 75 | Adafruit_NeoPixel Signals = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); 76 | 77 | /*************************************************************************/ 78 | /* Setup */ 79 | /*************************************************************************/ 80 | void setup() { 81 | 82 | // start the Ethernet and UDP: 83 | Ethernet.begin(mac,ip); 84 | Udp.beginMulticast(multicast ,port); 85 | 86 | // Initialize a LocoNet packet buffer to buffer bytes from the PC 87 | initLnBuf(&LnTxBuffer); 88 | 89 | Signals.begin(); // This initializes the NeoPixel library. 90 | Signals.setBrightness(32); 91 | 92 | // Configure the serial port for 57600 baud 93 | Serial.begin(57600); 94 | Serial.println("LocoNet UDP Multicast Signal Decoder"); 95 | 96 | Serial.print(numberOfSignals); 97 | Serial.print(" Signals: "); 98 | 99 | for (int i=0; iRED"); 122 | break; 123 | case 0x01: 124 | Serial.println("1->GREEN"); 125 | break; 126 | case 0x02: 127 | Serial.println("2->YELLOW"); 128 | break; 129 | case 0x03: 130 | Serial.println("3->WHITE"); 131 | break; 132 | } 133 | updateWS282bSignal(signalStartAddress[Signal], signalState); 134 | } 135 | 136 | 137 | /*************************************************************************/ 138 | /* Set WS2812b Signal to Aspect */ 139 | /*************************************************************************/ 140 | void updateWS282bSignal(uint16_t Address, uint8_t State) { 141 | 142 | for(int i=0;i "); 150 | Serial.println(State); 151 | 152 | int c = i*2; 153 | 154 | switch (State) { 155 | case 0: //RED 156 | Signals.setPixelColor(c , Signals.Color(255, 0, 0)); 157 | Signals.setPixelColor(c + 1, Signals.Color(255, 0, 0)); 158 | break; 159 | case 1: //GREEN 160 | Signals.setPixelColor(c , Signals.Color( 0, 150, 0)); 161 | Signals.setPixelColor(c + 1, Signals.Color( 0, 0, 0)); 162 | break; 163 | case 2: //YELLOW 164 | Signals.setPixelColor(c , Signals.Color( 0, 150, 0)); 165 | Signals.setPixelColor(c + 1, Signals.Color(255, 190, 0)); 166 | break; 167 | case 3: //WHITE 168 | Signals.setPixelColor(c , Signals.Color(255, 0, 0)); 169 | Signals.setPixelColor(c + 1, Signals.Color(255, 255, 255)); 170 | break; 171 | } 172 | Signals.show(); // Send update to WS2812b 173 | } 174 | } 175 | } 176 | 177 | /*************************************************************************/ 178 | /* Check for any received LocoNet packets */ 179 | /*************************************************************************/ 180 | void LoconetRX() { 181 | 182 | // Parse and display incoming packets 183 | int packetSize = Udp.parsePacket(); 184 | 185 | if(packetSize) { 186 | char packetBuffer[packetSize]; //buffer to hold incoming packet, 187 | 188 | Serial.print("Received packet of size "); 189 | Serial.print(packetSize); 190 | Serial.print(" From: "); 191 | IPAddress remote = Udp.remoteIP(); 192 | for (int i =0; i < 4; i++) { 193 | Serial.print(remote[i], DEC); 194 | if (i < 3) 195 | { 196 | Serial.print("."); 197 | } 198 | } 199 | Serial.print(":"); 200 | Serial.print(Udp.remotePort()); 201 | Serial.print(" - "); 202 | 203 | // read the packet into packetBufffer 204 | Udp.read(packetBuffer,packetSize); 205 | 206 | // move it to LnTXBuffer 207 | for (int i=0; i < packetSize; i++) { 208 | // Add it to the buffer 209 | addByteLnBuf( &LnTxBuffer, packetBuffer[i] & 0xFF); 210 | } 211 | 212 | } 213 | // Check to see if we have received a complete packet yet 214 | LnPacket = recvLnMsg( &LnTxBuffer ); 215 | 216 | if( LnPacket ) { 217 | 218 | // First print out the packet in HEX 219 | Serial.print("Loconet: "); 220 | uint8_t msgLen = getLnMsgSize(LnPacket); 221 | for (uint8_t x = 0; x < msgLen; x++) 222 | { 223 | uint8_t val = LnPacket->data[x]; 224 | // Print a leading 0 if less than 16 to make 2 HEX digits 225 | if(val < 16) 226 | Serial.print('0'); 227 | 228 | Serial.print(val, HEX); 229 | Serial.print(' '); 230 | } 231 | 232 | // If this packet was not a Switch or Sensor Message then print a new line 233 | if(!LocoNet.processSwitchSensorMessage(LnPacket)) 234 | Serial.println(); 235 | } 236 | } 237 | 238 | /*************************************************************************/ 239 | /* Program Loop */ 240 | /*************************************************************************/ 241 | void loop() { 242 | 243 | // Check for any received LocoNet packets 244 | LoconetRX(); 245 | } 246 | 247 | /*****************************************************************************/ 248 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 249 | /* for all Sensor messages */ 250 | /*****************************************************************************/ 251 | void notifySensor( uint16_t Address, uint8_t State ) { 252 | 253 | Serial.print("Sensor: "); 254 | Serial.print(Address, DEC); 255 | Serial.print(" - "); 256 | Serial.println( State ? "Active" : "Inactive" ); 257 | } 258 | 259 | /*****************************************************************************/ 260 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 261 | /* for all Switch Request messages */ 262 | /*****************************************************************************/ 263 | void notifySwitchRequest( uint16_t Address, uint8_t Output, uint8_t Direction ) { 264 | 265 | Serial.print("Switch Request: "); 266 | Serial.print(Address, DEC); 267 | Serial.print(':'); 268 | Serial.print(Direction ? "Closed" : "Thrown"); 269 | Serial.print(" - "); 270 | Serial.println(Output ? "On" : "Off"); 271 | 272 | for (int i=0; i. 17 | * 18 | ************************************************************************************************************ 19 | DESCRIPTION: 20 | This is a LocoNet Signal Decoder 21 | - It uses Ethernet UDP Multicast using a W5100 Ethernet Card. 22 | 23 | Demonstrates the use of the: 24 | - LocoNet.processSwitchSensorMessage(LnPacket) function to switch signals 25 | - Using WS2812b as simple signals witch Adafruit NeoPixel library 26 | 27 | In this example 4 WS2812b are combined into two DB Ausfahrsignals. It uses two addresses per signal always 28 | starting from the odd/uneven address. 29 | Addr1 Aaddr2| Aspect | Signalbegriff 30 | ------------------------------------ 31 | R - R | 00 | HP0 -> RED/RED 32 | G - R | 01 | HP1 -> GREEN 33 | R - G | 02 | HP2 -> GREEN/YELLOW 34 | G - G | 03 | SH1 -> RED/WHITE 35 | 36 | /************************************************************************************************************/ 37 | #include 38 | #include 39 | 40 | // Loconet 41 | #define LOCONET_TX_PIN 7 42 | static lnMsg *LnPacket ; 43 | static LnBuf LnTxBuffer ; 44 | 45 | uint16_t signalStartAddress[] = { 1, 3 }; // Only Even Numbers as Start Address!! 46 | uint8_t numberOfSignals = sizeof(signalStartAddress) / 2; 47 | 48 | byte signalState[sizeof(signalStartAddress) / 2]; 49 | 50 | // WS2812b 51 | // Which pin on the Arduino is connected to the NeoPixels? 52 | #define PIN 6 53 | 54 | // How many NeoPixels are attached to the Arduino? 55 | #define NUMPIXELS 4 56 | 57 | // When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals. 58 | Adafruit_NeoPixel Signals = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); 59 | 60 | /*************************************************************************/ 61 | /* Setup */ 62 | /*************************************************************************/ 63 | void setup() { 64 | 65 | pinMode(13, OUTPUT); 66 | 67 | Signals.begin(); // This initializes the NeoPixel library. 68 | Signals.setBrightness(32); 69 | 70 | // Configure the serial port for 57600 baud 71 | Serial.begin(57600); 72 | Serial.println("LocoNet UDP Multicast Signal Decoder"); 73 | 74 | Serial.print(numberOfSignals); 75 | Serial.print(" Signals: "); 76 | 77 | for (int i=0; iRED"); 104 | break; 105 | case 0x01: 106 | Serial.println("1->GREEN"); 107 | break; 108 | case 0x02: 109 | Serial.println("2->YELLOW"); 110 | break; 111 | case 0x03: 112 | Serial.println("3->WHITE"); 113 | break; 114 | } 115 | updateWS282bSignal(signalStartAddress[Signal], signalState); 116 | } 117 | 118 | 119 | /*************************************************************************/ 120 | /* Set WS2812b Signal to Aspect */ 121 | /*************************************************************************/ 122 | void updateWS282bSignal(uint16_t Address, uint8_t State) { 123 | 124 | for(int i=0;i "); 132 | Serial.println(State); 133 | 134 | int c = i*2; 135 | 136 | switch (State) { 137 | case 0: //RED 138 | Signals.setPixelColor(c , Signals.Color(255, 0, 0)); 139 | Signals.setPixelColor(c + 1, Signals.Color(255, 0, 0)); 140 | break; 141 | case 1: //GREEN 142 | Signals.setPixelColor(c , Signals.Color( 0, 150, 0)); 143 | Signals.setPixelColor(c + 1, Signals.Color( 0, 0, 0)); 144 | break; 145 | case 2: //YELLOW 146 | Signals.setPixelColor(c , Signals.Color( 0, 150, 0)); 147 | Signals.setPixelColor(c + 1, Signals.Color(255, 190, 0)); 148 | break; 149 | case 3: //WHITE 150 | Signals.setPixelColor(c , Signals.Color(255, 0, 0)); 151 | Signals.setPixelColor(c + 1, Signals.Color(255, 255, 255)); 152 | break; 153 | } 154 | Signals.show(); // Send update to WS2812b 155 | } 156 | } 157 | } 158 | 159 | /*************************************************************************/ 160 | /* Program Loop */ 161 | /*************************************************************************/ 162 | void loop() { 163 | 164 | // Check for any received LocoNet packets 165 | LnPacket = LocoNet.receive() ; 166 | if( LnPacket ) 167 | { 168 | unsigned char opcode = (int)LnPacket->sz.command; 169 | 170 | if (opcode == OPC_GPON) { // GLOBAL power ON request 0x83 171 | digitalWrite(13, HIGH); 172 | } else if (opcode == OPC_GPOFF) { // GLOBAL power OFF req 0x82 173 | digitalWrite(13, LOW); 174 | } else { 175 | if(!LocoNet.processSwitchSensorMessage(LnPacket)); 176 | } 177 | } 178 | } 179 | 180 | /*****************************************************************************/ 181 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 182 | /* for all Sensor messages */ 183 | /*****************************************************************************/ 184 | void notifySensor( uint16_t Address, uint8_t State ) { 185 | 186 | Serial.print("Sensor: "); 187 | Serial.print(Address, DEC); 188 | Serial.print(" - "); 189 | Serial.println( State ? "Active" : "Inactive" ); 190 | } 191 | 192 | /*****************************************************************************/ 193 | /* This call-back function is called from LocoNet.processSwitchSensorMessage */ 194 | /* for all Switch Request messages */ 195 | /*****************************************************************************/ 196 | void notifySwitchRequest( uint16_t Address, uint8_t Output, uint8_t Direction ) { 197 | 198 | Serial.print("Switch Request: "); 199 | Serial.print(Address, DEC); 200 | Serial.print(':'); 201 | Serial.print(Direction ? "Closed" : "Thrown"); 202 | Serial.print(" - "); 203 | Serial.println(Output ? "On" : "Off"); 204 | 205 | for (int i=0; i