// DCCControllerSimple V1 Software Ben Forster 2023

// Import Libraries
#include <WiFi.h>
#include <WiFiMulti.h>
#include <WiFiClientSecure.h>
#include <WebSocketsClient.h>
#include <SocketIOclient.h>

WiFiMulti WiFiMulti;
SocketIOclient socketIO;

// Define The Input Pins
#define ADDRESS_BIT_1 1
#define ADDRESS_BIT_2 2
#define ADDRESS_BIT_3 3
#define ADDRESS_BIT_4 4
#define SPEED_INCREASE_BTN 8
#define CUSTOM_BTN_3 9
#define FORWARD_BTN 10
#define LED_REGISTER_CLOCK 11
#define LED_SHIFT_CLOCK 12
#define LED_SERIAL_IN 13
#define LED_OUTPUT_ENABLE_ACTIVE_LOW 14
#define DIGIT_2_LATCH_ENABLE 15
#define DIGIT_2_BLANKING_ACTIVE_LOW 16
#define BCD_BIT_1 17
#define BCD_BIT_4 21
#define BCD_BIT_3 33
#define BCD_BIT_2 34
#define DIGIT_1_LATCH_ENABLE 35
#define DIGIT_1_BLANKING_ACTIVE_LOW 36
#define REVERSE_BTN 37
#define CUSTOM_BTN_1 38
#define CUSTOM_BTN_2 39
#define SPEED_DECREASE_BTN 40
#define EMERGENCY_STOP_BTN 41
#define ROTARY_A 42
#define ROTARY_B 45
#define ROTARY_SWITCH 46

// then define which led is at which index in the shift register
#define EMERGENCY_STOP_BTN_LED 0
#define SPEED_DECREASE_BTN_LED 1
#define CUSTOM_BTN_1_LED 2
#define REVERSE_BTN_LED 3
#define CUSTOM_BTN_2_LED 4
#define FORWARD_BTN_LED 5
#define CUSTOM_BTN_3_LED 6
#define SPEED_INCREASE_BTN_LED 7

// then define the wifi credentials
#define WIFI_SSID "DCC_CONTROLLERS"
#define WIFI_PASSWORD "dcc_controllers"
#define SERVER_IP_ADDRESS "ws://192.168.0.2"
#define SERVER_PORT 81
#define SERVER_URL "/socket.io"
#define RECONNECTION_TIME 5000

// then define the variables for locomotives
volatile int speed = 0; // locomotive speed from 0 to +28
volatile bool customBtn1On = false;
volatile bool customBtn2On = false;
volatile bool customBtn3On = false;
volatile bool isForward = true;
volatile bool localEmergencyStop = false;

// then define the variables for the actual controller
bool emergencyStopFromServer = false;
volatile bool activateEmergencyStop = false;

// then define the loco address
int locoAddressBit1;
int locoAddressBit2;
int locoAddressBit3;
int locoAddressBit4;

volatile int lastStateRotaryA;
volatile int currentStateRotaryA;

volatile bool resendPacket = false;

// then define the array for use with the shift register
int shiftRegisterLedArray[8] = {1,0,0,0,0,1,0,0};

// error number 99 88 77 66 55
int errors[5] = {0, 0, 0, 0, 0};

// then define the errors function
void ShowError(int errorCode) {
    // then check what error code it is
    if(errorCode == 99) {
        // then we have the disconnection error to display
        errors[0] = 1;
    }
    else if(errorCode == 88) {
        // then we have the general error to display
        errors[1] = 1;
    }
    else if(errorCode == 77) {
        // then we have the not connecting error to display
        errors[2] = 1;
    }
    else if(errorCode == 66) {
        // then we have the emergency stop error to display
        errors[3] = 1;
    }
    else if(errorCode == 55) {
        // then we have the track short error to display 
        errors[4] = 1;
    }
}

void CancelError(int errorCode) {
    // then check what error code it is
    if(errorCode == 99) {
        // then we have the disconnection error to remove
        errors[0] = 1;
    }
    else if(errorCode == 88) {
        // then we have the general error to remove
        errors[1] = 1;
    }
    else if(errorCode == 77) {
        // then we have the not connecting error to remove
        errors[2] = 1;
    }
    else if(errorCode == 66) {
        // then we have the emergency stop error to remove
        errors[3] = 1;
    }
    else if(errorCode == 55) {
        // then we have the track short error to remove
        errors[4] = 1;
    }
}

// then define the shift register functions
void updateShiftRegisterArray(int index, int value) {
    shiftRegisterLedArray[index] = value;
    
}
void sendArrayToShiftRegister() {
    // then make a for loop that will run 8 times
    for(int i = 0; i < sizeof(shiftRegisterLedArray); i++) {
        // then pull the clock low
        digitalWrite(LED_SHIFT_CLOCK, LOW);
        // then write the data to the SR DATA IN line
        digitalWrite(LED_SERIAL_IN, shiftRegisterLedArray[i]);
        delay(0.5);
        // then pull the clock high to clock it in
        digitalWrite(LED_SHIFT_CLOCK, HIGH);
    }
    // then once it has finished, pull the clock low and then latch the data by pulling the register clock high and low
    digitalWrite(LED_SHIFT_CLOCK, LOW);
    digitalWrite(LED_REGISTER_CLOCK, HIGH);
    delay(0.5);
    digitalWrite(LED_REGISTER_CLOCK, LOW);
    // then make sure the output enable is active so pull it low
    digitalWrite(LED_OUTPUT_ENABLE_ACTIVE_LOW, LOW);
}

void autoUpdateShiftRegisterArray() {
    // then find the data and the index and pass them to the updateShiftRegisterArray function
    updateShiftRegisterArray(CUSTOM_BTN_1_LED, customBtn1On);
    updateShiftRegisterArray(CUSTOM_BTN_2_LED, customBtn2On);
    updateShiftRegisterArray(CUSTOM_BTN_3_LED, customBtn3On);
    // then do the direction
    if(isForward == true) {
        updateShiftRegisterArray(FORWARD_BTN_LED, 1);
        updateShiftRegisterArray(REVERSE_BTN_LED, 0);
    }
    else {
        updateShiftRegisterArray(FORWARD_BTN_LED, 0);
        updateShiftRegisterArray(REVERSE_BTN_LED, 1);
    }

    // then send the array to the shift register
    sendArrayToShiftRegister();
    
    
}

// then define the 7 segment display function
void displayNumberOn7Segment(int number) {
    // then do a validity check on the number to make sure its not less than zero and not more than 99
    if(number >= 0 && number <= 99) {
        // then the number is in range so split it into digits
        int digit1;
        int digit2 = number % 10;
        if(number > 9) {
            digit1 = number / 10 % 10;
        }
        else {
            digit1 = 0;
        }
        // then do digit 1 first
        int digit1binary[4] = {0,0,0,0};
        // then divide the number into the array
        int newNumber1 = digit1;
        digit1binary[3] = newNumber1 % 2;
        newNumber1 = digit1 / 2;
        digit1binary[2] = newNumber1 % 2;
        newNumber1 = newNumber1 / 2;
        digit1binary[1] = newNumber1 % 2;
        newNumber1 = newNumber1 / 2;
        digit1binary[0] = newNumber1 % 2;
        // then do digit 2
        int digit2binary[4] = {0,0,0,0};
        // then divide the number into the array
        int newNumber2 = digit2;
        digit2binary[3] = newNumber2 % 2;
        newNumber2 = newNumber2 / 2;
        digit2binary[2] = newNumber2 % 2;
        newNumber2 = newNumber2 / 2;
        digit2binary[1] = newNumber2 % 2;
        newNumber2 = newNumber2 / 2;
        digit2binary[0] = newNumber2 % 2;

        // then update the 7 segment display
        // first pull both latch enable pins low
        digitalWrite(DIGIT_1_LATCH_ENABLE, LOW);
        digitalWrite(DIGIT_2_LATCH_ENABLE, LOW);

        // then put digit 1's data on the address lines
        digitalWrite(BCD_BIT_1, digit1binary[0]);
        digitalWrite(BCD_BIT_2, digit1binary[1]);
        digitalWrite(BCD_BIT_3, digit1binary[2]);
        digitalWrite(BCD_BIT_4, digit1binary[3]);

        // then latch the data into the 7 segment
        digitalWrite(DIGIT_1_LATCH_ENABLE, HIGH);
        delay(0.5);
        digitalWrite(DIGIT_1_LATCH_ENABLE, LOW);

        // then put digit 2's data on the address lines
        digitalWrite(BCD_BIT_1, digit2binary[0]);
        digitalWrite(BCD_BIT_2, digit2binary[1]);
        digitalWrite(BCD_BIT_3, digit2binary[2]);
        digitalWrite(BCD_BIT_4, digit2binary[3]);

        // then latch the data into the 7 segment
        digitalWrite(DIGIT_2_LATCH_ENABLE, HIGH);
        delay(0.5);
        digitalWrite(DIGIT_2_LATCH_ENABLE, LOW);

        // then pull the address lines low
        digitalWrite(BCD_BIT_1, LOW);
        digitalWrite(BCD_BIT_2, LOW);
        digitalWrite(BCD_BIT_3, LOW);
        digitalWrite(BCD_BIT_4, LOW);

    }
}

// readMessage function - takes the payload from the webSocketEvent
void readMessage(uint8_t payload[28], int length) {
    // then read off each bit from the char array and check if anything needs to be updated
    // global emergency stop
    if((int)payload[0] == 1) {
        // then flash the emergency stop error
        ShowError(66);
    }
    else {
        // cancel the error
        CancelError(66);
    }

    // global track short
    if((int)payload[1] == 1) {
        // then flash the track short error
        ShowError(55);
    }
    else {
        // cancel the error
        CancelError(55);
    }

    // then check the loco address bits to see if we need to read on
    // check the controller bit 1 matches the packet bit 1
    if((int)payload[5] == locoAddressBit1 && (int)payload[4] == locoAddressBit2 && (int)payload[3] == locoAddressBit3 && (int)payload[2] == locoAddressBit4) {
        // then the address matches so read the packet

        // then read the speed bcd bits and update the speed on the controller without sending another packet
        int newSpeed = 0;
        // 5th bit
        if((int)payload[7] == 1) {
            // then increment the speed by 16
            newSpeed = newSpeed + 16;
        }
        // 4th bit
        if((int)payload[8] == 1) {
            // then increment the speed by 8
            newSpeed = newSpeed + 8;
        }
        // 3rd bit
        if((int)payload[9] == 1) {
            // then increment the speed by 4
            newSpeed = newSpeed + 4;
        }
        // 2nd bit
        if((int)payload[10] == 1) {
            // then increment the speed by 2
            newSpeed = newSpeed + 2;
        }
        // 1st bit
        if((int)payload[11] == 1) {
            // then increment the speed by 1
            newSpeed = newSpeed + 1;
        }

        // then set the speed to the newSpeed
        speed = newSpeed;

        // then update the direction
        isForward = (int)payload[12];

        // then check the 3 functions that this device can handle
        customBtn1On = (int)payload[13];
        customBtn2On = (int)payload[14];
        customBtn3On = (int)payload[15];

        // then update the shift register
        autoUpdateShiftRegisterArray();

        // then update the 7 segment display
        displayNumberOn7Segment(speed);

    }
}


// then define the function that handles any events
void socketIOEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) {
    // then check what even happened
    switch(type) {
        case sIOtype_DISCONNECT:
            // disconnected so give the error 99
            ShowError(99);
            break;
        case sIOtype_CONNECT:
            // connected
            socketIO.send(sIOtype_CONNECT, "/");
            break;
        case sIOtype_ERROR:
            // error so give the error code 88
            ShowError(88);
            break;
        case sIOtype_EVENT:
            // then send the message to the readMessage function
            readMessage(payload, length);
            break;
    }
}


// ################### INTERRUPT FUNCTIONS ########################

void interrupt_speedIncreaseBtn() {
    // then increment the speed if speed is not 28 or more
    if(speed < 28) {
        // then increment the speed
        speed = speed + 1;
        // then set the resendPacket bool to true
        resendPacket = true;
    }
}

void interrupt_customBtn3() {
    // then flip the state
    customBtn3On = !customBtn3On;
    // then set the resendPacket bool to true
    resendPacket = true;
}

void interrupt_forwardBtn() {
    // then set the isForward boolean to true
    isForward = true;
    // then set the resendPacket bool to true
    resendPacket = true;
}

void interrupt_reverseBtn() {
    // then set the isForward boolean to false
    isForward = false;
    // then set the resendPacket bool to true
    resendPacket = true;
}

void interrupt_customBtn1() {
    // then flip the state
    customBtn1On = !customBtn1On;
    // then set the resendPacket bool to true
    resendPacket = true;
}

void interrupt_customBtn2() {
    // then flip the state
    customBtn2On = !customBtn2On;
    // then set the resendPacket bool to true
    resendPacket = true;
}

void interrupt_speedDecreaseBtn() {
    // then decrement the speed if speed is not 0 or below
    if(speed > 0) {
        // then decrement the speed
        speed = speed - 1;
        // then set the resendPacket bool to true
        resendPacket = true;
    }
}

void interrupt_emergencyStopBtn() {
    // then set the speed to 0
    speed = 0;
    // then set the localEmergencyStop to true
    localEmergencyStop = true;
    // then set the resendPacket bool to true
    resendPacket = true;
}

void interrupt_rotarySwitch() {
    // this function checks if the rotary switch has been pressed
    if(activateEmergencyStop == false) {
        // then set it to true
        activateEmergencyStop = true;
        // then set the resendPacket bool to true
        resendPacket = true;
    }
    else {
        // then cancel the emergency stop
        activateEmergencyStop = false;
        // then set the resendPacket bool to true
        resendPacket = true;
    }
}

// then the rotary encoder interrupt function
void interrupt_checkRotaryEncoder() {
    // check the state of ROTARY_A
    currentStateRotaryA = digitalRead(ROTARY_A);
    // then check if it is different to the previous state
    if(currentStateRotaryA != lastStateRotaryA && currentStateRotaryA == 0) {
        // then check if the Rotary B state is different than the Rotary A state
        if(digitalRead(ROTARY_B) != currentStateRotaryA) {
            // then it is rotating clockwise
            // so increment the speed if its not more than 27
            if(speed < 28) {
                speed = speed + 1;
                // then set the resendPacket bool to true
                resendPacket = true;
            }      
        }
        else {
            // then it is rotating counterclockwise
            // so decrement the speed if its not less than 1
            if(speed > 1) {
                speed = speed - 1;
                // then set the resendPacket bool to true
                resendPacket = true;
            }
        }
    }
    // then update the last Rotary A state
    lastStateRotaryA = currentStateRotaryA;
}

void setup() {

    // then initialise the wifi connection
    WiFiMulti.addAP(WIFI_SSID, WIFI_PASSWORD);

    // then make sure its connected
    while(WiFiMulti.run() != WL_CONNECTED) {
        delay(100);
        ShowError(77);
    }

    CancelError(77);

    // then start the websocket connection
    socketIO.begin(SERVER_IP_ADDRESS, SERVER_PORT, SERVER_URL);

    // then attach the event handler
    socketIO.onEvent(socketIOEvent);

    // then set the reconnection time
    //webSocket.setReconnectInterval(RECONNECTION_TIME);

    // PIN MODES
    // set all the pin modes
    pinMode(ADDRESS_BIT_1, INPUT_PULLUP);
    pinMode(ADDRESS_BIT_2, INPUT_PULLUP);
    pinMode(ADDRESS_BIT_3, INPUT_PULLUP);
    pinMode(ADDRESS_BIT_4, INPUT_PULLUP);
    pinMode(SPEED_INCREASE_BTN, INPUT_PULLUP);
    pinMode(CUSTOM_BTN_3, INPUT_PULLUP);
    pinMode(FORWARD_BTN, INPUT_PULLUP);
    pinMode(LED_REGISTER_CLOCK, OUTPUT);
    pinMode(LED_SHIFT_CLOCK, OUTPUT);
    pinMode(LED_SERIAL_IN, OUTPUT);
    pinMode(LED_OUTPUT_ENABLE_ACTIVE_LOW, OUTPUT);
    pinMode(DIGIT_2_LATCH_ENABLE, OUTPUT);
    pinMode(DIGIT_2_BLANKING_ACTIVE_LOW, OUTPUT);
    pinMode(BCD_BIT_1, OUTPUT);
    pinMode(BCD_BIT_4, OUTPUT);
    pinMode(BCD_BIT_3, OUTPUT);
    pinMode(BCD_BIT_2, OUTPUT);
    pinMode(DIGIT_1_LATCH_ENABLE, OUTPUT);
    pinMode(DIGIT_1_BLANKING_ACTIVE_LOW, OUTPUT);
    pinMode(REVERSE_BTN, INPUT_PULLUP);
    pinMode(CUSTOM_BTN_1, INPUT_PULLUP);
    pinMode(CUSTOM_BTN_2, INPUT_PULLUP);
    pinMode(SPEED_DECREASE_BTN, INPUT_PULLUP);
    pinMode(EMERGENCY_STOP_BTN, INPUT_PULLUP);
    pinMode(ROTARY_A, INPUT_PULLUP);
    pinMode(ROTARY_B, INPUT_PULLUP);
    pinMode(ROTARY_SWITCH, INPUT_PULLUP);

    // attach interrupts to all the input pins exept the address bits as these are not important
    attachInterrupt(SPEED_INCREASE_BTN, interrupt_speedIncreaseBtn, FALLING);
    attachInterrupt(CUSTOM_BTN_3, interrupt_customBtn3 , FALLING);
    attachInterrupt(FORWARD_BTN, interrupt_forwardBtn, FALLING);
    attachInterrupt(REVERSE_BTN, interrupt_reverseBtn, FALLING);
    attachInterrupt(CUSTOM_BTN_1, interrupt_customBtn1 , FALLING);
    attachInterrupt(CUSTOM_BTN_2, interrupt_customBtn2, FALLING);
    attachInterrupt(SPEED_DECREASE_BTN, interrupt_speedDecreaseBtn, FALLING);
    attachInterrupt(EMERGENCY_STOP_BTN, interrupt_emergencyStopBtn , FALLING);
    attachInterrupt(ROTARY_A, interrupt_checkRotaryEncoder, CHANGE);
    attachInterrupt(ROTARY_B, interrupt_checkRotaryEncoder, CHANGE);
    attachInterrupt(ROTARY_SWITCH, interrupt_rotarySwitch, FALLING);

    // then get the current state of rotary a and store it
    lastStateRotaryA = digitalRead(ROTARY_A);

    // then read the loco address from the address bits and flip them because pullup resistors have been used
    locoAddressBit1 = !digitalRead(ADDRESS_BIT_1);
    locoAddressBit2 = !digitalRead(ADDRESS_BIT_2);
    locoAddressBit3 = !digitalRead(ADDRESS_BIT_3);
    locoAddressBit4 = !digitalRead(ADDRESS_BIT_4);

    // then pull the 7 segment display blanking lines high
    digitalWrite(DIGIT_1_BLANKING_ACTIVE_LOW, HIGH);
    digitalWrite(DIGIT_2_BLANKING_ACTIVE_LOW, HIGH);


    // then send 7 more clock pulses to the led shift register
    digitalWrite(LED_SHIFT_CLOCK, HIGH);
    delay(0.5);
    digitalWrite(LED_SHIFT_CLOCK, LOW);
    digitalWrite(LED_SHIFT_CLOCK, HIGH);
    delay(0.5);
    digitalWrite(LED_SHIFT_CLOCK, LOW);
    digitalWrite(LED_SHIFT_CLOCK, HIGH);
    delay(0.5);
    digitalWrite(LED_SHIFT_CLOCK, LOW);
    digitalWrite(LED_SHIFT_CLOCK, HIGH);
    delay(0.5);
    digitalWrite(LED_SHIFT_CLOCK, LOW);
    digitalWrite(LED_SHIFT_CLOCK, HIGH);
    delay(0.5);
    digitalWrite(LED_SHIFT_CLOCK, LOW);
    digitalWrite(LED_SHIFT_CLOCK, HIGH);
    delay(0.5);
    digitalWrite(LED_SHIFT_CLOCK, LOW);
    digitalWrite(LED_SHIFT_CLOCK, HIGH);
    delay(0.5);
    digitalWrite(LED_SHIFT_CLOCK, LOW);

}

void sendMessage(char payload[16]) {
    socketIO.sendEVENT(payload);
}

void prepareMessage() {
    // then create a char array with all the bits in to send to the host
    char message[16];
    
    // global emergency stop
    message[0] = (int)activateEmergencyStop;
    // global track short - host to controller only
    message[1] = 0;

    // loco address 4th bit
    message[2] = locoAddressBit4;
    // loco address 3rd bit
    message[3] = locoAddressBit3;
    // loco address 2nd bit
    message[4] = locoAddressBit2;
    // loco address 1st bit
    message[5] = locoAddressBit1;

    // then do the local emergency stop (just the current loco)
    message[6] = (int)localEmergencyStop;

    // then do the current speed

    int currentSpeedArray[5] = {0,0,0,0,0};
    // then divide the number into the array
    int newNumber1 = speed;
    currentSpeedArray[4] = newNumber1 % 2;
    newNumber1 = newNumber1 / 2;
    currentSpeedArray[3] = newNumber1 % 2;
    newNumber1 = newNumber1 / 2;
    currentSpeedArray[2] = newNumber1 % 2;
    newNumber1 = newNumber1 / 2;
    currentSpeedArray[1] = newNumber1 % 2;
    newNumber1 = newNumber1 / 2;
    currentSpeedArray[0] = newNumber1 % 2;

    message[7] = currentSpeedArray[4];
    message[8] = currentSpeedArray[3];
    message[9] = currentSpeedArray[2];
    message[10] = currentSpeedArray[1];
    message[11] = currentSpeedArray[0];

    // then send the direction
    message[12] = (int)isForward;

    // then send the 3 custom buttons that this hardware is capable of

    message[13] = (int)customBtn1On;
    message[14] = (int)customBtn2On;
    message[15] = (int)customBtn3On;

    // then call the sendMessage function
    sendMessage(message);
}

void loop() {
    // then go in the webSocket loop
    socketIO.loop();
    // then check if there are any errors to display
    if(errors[4] == 1) {
        displayNumberOn7Segment(55);
    }
    if(errors[3] == 1) {
        displayNumberOn7Segment(66);
    }
    if(errors[2] == 1) {
        displayNumberOn7Segment(77);
    }
    if(errors[1] == 1) {
        displayNumberOn7Segment(88);
    }
    if(errors[0] == 1) {
        displayNumberOn7Segment(99);
    }

    // then check if there are no errors and the speed can be put on the display
    if(errors[0] == 0 && errors[1] == 0 && errors[2] == 0 && errors[3] == 0 && errors[4] == 0) {
        displayNumberOn7Segment(speed);
    }
    // then update the lights
    autoUpdateShiftRegisterArray();
    // then check if the packet needs resending
    if(resendPacket == true) {
        prepareMessage();
        resendPacket = false;
    }
}
Loading
esp32-s2-devkitm-1
74HC595