/*
File : OutsealToESP32 ATS Only.ino
Author : leveletech.com / wa 081214584114
Date : 2024 04 19
Description : Translate Ladder Outseal with ATS Application to ESP32
reference file ATS KANDANG AYA0 BOYLER.plc
*/
//--------------------------------- Include Libraries-----------------------------------------------
#include <LiquidCrystal_I2C.h>
//#include <Wire.h>
//--------------------------------- Include Libraries-----------------------------------------------
//--------------------------------- Hardware Setting------------------------------------------------
//#define BLYNK_TEMPLATE_ID "TMPL6lKH9OEui"
//#define BLYNK_TEMPLATE_NAME "ES32 WIFI"
//#define BLYNK_AUTH_TOKEN "Cm5Ey0s-iNYuHsky2xu6Wx5o7E9mhKUF"
//#define BLYNK_PRINT Serial
//#include <WiFi.h>
//#include <BlynkSimpleEsp32.h>
//char auth[] = BLYNK_AUTH_TOKEN ; //Auth Token
//char ssid[] = "Wokwi-GUEST"; //nama hotspot yang digunakan
//char pass[] = ""; //password hotspot yang digunakan
int value0, value1, value2, value3, value6;
//--------------------------------- Hardware Setting------------------------------------------------
//--------------------------------- Define and Constant---------------------------------------------
// Assign push button pins
const int buttonPins[7] = {2, 3, 4, 5, 6, 7, 8}; // Array of pins for buttons
const int numButtons = 7; // Number of buttons
bool lastButtonState[numButtons] = {false, false, false, false, false, false, false};
// Assign relay pins
const int relayPins[13] = {22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34}; // Array of pins for relays
const int numRelays = 13; // Number of relays
const int lcdAddress1 = 0x27;
//--------------------------------- Define and Constant---------------------------------------------
//--------------------------------- User Define Data Type-------------------------------------------
struct timer_data {
bool IN;
bool Q;
bool TT;
bool BP1; //button pressed
bool BP2; //button pressed
int PT; //preset time
int ET; //elapsed time
unsigned long TN;//time now
};
struct counter_data {
bool IN;
bool R; //Reset
bool Q;
bool BP; //button pressed
int PV; //preset value
int CV; //current value
};
struct spwm_data {
bool IN;
bool Q;
bool BP; //button pressed
int PT_on; //preset time for ON state
int PT_off; //preset time for OFF state
int ET; //elapsed time
unsigned long TN;//time now
};
struct osr_data {
bool IN;
bool Q;
bool BP;
};
//--------------------------------- User Define Data Type-------------------------------------------
//----------------------------------Global Variable-------------------------------------------------
bool S[8] = {false, false, false, false, false, false, false, false};
bool S_P[8] = {false, false, false, false, false, false, false, false};
bool R[18] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
bool R_P[14] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false};
bool B[8] = {false, false, false, false, false, false, false, false};
bool BLYNKING[8] = {false, false, false, false, false, false, false, false}; //blynking flag
unsigned long lastDebounceTime[numButtons]; // Array to store the last debounce time for each button
const unsigned long debounceDelay = 50; // Debounce delay in milliseconds
timer_data T[9]; //TON
counter_data CTU_data[2];
spwm_data spwm_dat[4];
osr_data OSR_dat[10];
LiquidCrystal_I2C lcd1 (lcdAddress1, 20, 4);
//----------------------------------Global Variable-------------------------------------------------
//----------------------------------User Defined Function Stage 1-----------------------------------
void TON(timer_data & Var) { //Timer On delay function
if (Var.IN)
{
if (!Var.BP1)
{
Var.TN = millis();
Var.BP1 = true;
Var.TT = true;
}
if (!Var.Q)
{
Var.ET = (millis() - Var.TN) / 1000;
if (Var.ET >= Var.PT) {
Var.Q = true;
Var.TT = false;
}
}
} else
{
Var.Q = false;
Var.BP1 = false;
Var.ET = 0;
Var.TT = false;
}
}
void TOF(timer_data & Var) { //Timer Off delay function
if (Var.IN && !Var.BP1) {
Var.Q = true;
Var.BP1 = true;
Var.BP2 = false;
}
if (!Var.IN && Var.BP1 && !Var.BP2) {
Var.TN = millis();
Var.BP1 = false;
Var.BP2 = true;
}
if (Var.Q == true && Var.BP2 == true) Var.ET = (millis() - Var.TN) / 1000;
if (millis() - Var.TN > Var.PT && Var.BP2)
{
Var.Q = false;
Var.BP2 = false;
Var.ET = 0;
}
}
void CTU(counter_data & Var) { //Counter Up
if (Var.IN) {
if (!Var.BP && !Var.Q)
{
Var.CV++;
Var.BP = true;
if (Var.CV >= Var.PV) Var.Q = true;
}
} else {
Var.BP = false;
}
if (Var.R) {
Var.CV = 0;
Var.Q = false;
}
}
void spwm(spwm_data & Var) { // Software PWM or Timer ON OFF
if (Var.IN)
{
if (!Var.BP)
{
Var.TN = millis();
Var.BP = true;
Var.Q = true; // Initially turn ON the output
}
Var.ET = (millis() - Var.TN) / 1000;
if (Var.Q && Var.ET >= Var.PT_on) {
Var.Q = false; // If ON time is over, turn OFF the output
Var.TN = millis(); // Reset the timer for the next state
}
else if (!Var.Q && Var.ET >= Var.PT_off) {
Var.Q = true; // If OFF time is over, turn ON the output
Var.TN = millis(); // Reset the timer for the next state
}
}
else
{
Var.Q = false; // If input is inactive, turn OFF the output
Var.BP = false; // Reset button pressed flag
Var.ET = 0; // Reset elapsed time
}
}
void OSR(osr_data & Var) {
if (Var.IN) {
if (!Var.BP) {
Var.Q = true;
} else {
Var.Q = false;
}
} else {
Var.BP = false;
Var.Q = false;
}
}
void DI_Update() { //Update Digital Input Value
for (int i = 0; i < numButtons; i++) {
// Read the current state of the button
int currentState = !digitalRead(buttonPins[i]);
// Check if the button state has changed and debounce it
if (currentState != lastButtonState[i]) {
// Reset the debounce timer
lastDebounceTime[i] = millis();
}
// Check if enough time has passed to consider it a valid state change
if ((millis() - lastDebounceTime[i]) > debounceDelay) {
// If enough time has passed, update the state
S[i + 1] = !currentState;
}
// Store the current state for the next iteration
lastButtonState[i] = currentState;
}
}
void DO_Update() { //Update Digital Output Value
for (int i = 1; i < numRelays + 1; i++) {
digitalWrite(relayPins[i - 1], R[i]);
}
}
String R_Status(bool status) {
if (status) {
return "ON ";
} else {
return "OFF";
}
}
void LCD1_Update(bool R[], bool R_P[], int size) {
bool same = true;
// Check if every member of R and R_P is the same
for (int i = 0; i < size; i++) {
if (R[i] != !R_P[i]) {
same = false;
break;
}
}
// If not same, print "ok" on serial and update R_P with R values
if (!same) {
lcd1.setCursor(3, 0);
lcd1.print(" ALI PLC");
lcd1.setCursor(0, 1);
lcd1.print("PLN :" + R_Status(R[1]) + " " + "GENSET:" + R_Status(R[6]) );
lcd1.setCursor(0, 2);
lcd1.print("CRANK:" + R_Status(R[3] || R[4] || R[5]) + " " + "STOP :" + R_Status(R[2]));
lcd1.setCursor(0, 3);
lcd1.print("LVMDP :" + R_Status(R[8]) + " " + "ALARM:" + R_Status(R[7]) );
// Update R_P with R values
for (int i = 0; i < size; i++) {
R_P[i] = R[i];
}
}
}
void blynking_LCD() {
if (S[1] && S[6]) {
T[8].IN = !T[8].Q; TON(T[8]);
if (T[8].Q) {
if (BLYNKING[1]) {
lcd1.setCursor(0, 1);
lcd1.print("PLN");
BLYNKING[1] = false;
} else {
lcd1.setCursor(0, 1);
lcd1.print(" ");
BLYNKING[1] = true;
}
}
} else {
lcd1.setCursor(0, 1);
lcd1.print("PLN");
BLYNKING[1] = false;
}
}
void ATS_System() { //ATS System
//Rung 0 - PLN ON ok2
//==================================================================================================
T[1].IN = S[1]; TON(T[1]); R[1] = T[1].Q; //Serial.println(T[1].ET);
//==================================================================================================
//Rung 1 ok2
//==================================================================================================
T[4].IN = R[1]; TON(T[4]); R[2] = T[4].TT; //Serial.println(T[2].ET);
//==================================================================================================
//Rung 2 - ACU 12 VOLT DC >5 V ok2
//==================================================================================================
T[2].IN = (S[5] && !R[1] && T[4].Q); TON(T[2]); B[1] = (T[2].TT);
//==================================================================================================
//Rung 3 - TRIGER DC CRENG 1 ok2
//==================================================================================================
T[3].IN = (S[5] && !R[1] && !B[1]); TON(T[3]); R[3] = T[3].TT;
//==================================================================================================
//Rung 4 - CRENGKING ON 1 ok
//==================================================================================================
R[10] = (S[6] && S[2] && T[3].Q && !R[1]);
//==================================================================================================
//Rung 5 - TRIGER DC CRENG 2 ok2
//==================================================================================================
T[5].IN = (!R[1] && !R[3] && S[5] && !R[10]); TON(T[5]); R[4] = T[5].TT;
//==================================================================================================
//Rung 6 - CRENGKING ON 2 ok2
//==================================================================================================
R[11] = (S[6] && S[3] && T[5].IN && !R[1]);
//==================================================================================================
//Rung 7- TR5GER DC CRENGKING 3 ok2
//==================================================================================================
T[7].IN = (S[5] && !R[1] && !R[4] && T[5].Q && !R[11]); TON(T[7]);
T[6].IN = (T[7].IN && !T[7].TT) ; TON(T[6]); R[5] = T[6].TT;
//==================================================================================================
//Rung 8 - CRENGKING ON 3 ok2
//==================================================================================================
R[12] = (S[6] && S[4] && S[5] && T[7].Q && !R[1]);
//==================================================================================================
//Rung 9 - ok2
//==================================================================================================
R[7] = (!R[1] && T[6].Q && !R[5] && !R[6]);
//==================================================================================================
//Rung 10 - KONDISI GENZED ok2
//==================================================================================================
R[6] = ((T[3].Q && R[10]) || (T[5].Q && !R[10] && R[11]) || (T[7].Q && !R[10] && !R[11] && R[12]) );
//==================================================================================================
//Rung 11 - SWITCENG ATS ok2
//==================================================================================================
R[8] = R[1] || R[6];
//==================================================================================================
//Rung 12 - LVMDP 1 ok2
//==================================================================================================
OSR_dat[0].IN = (S[7] && R[8] && B[2]); OSR(OSR_dat[0]); B[2] = OSR_dat[0].Q;
//==================================================================================================
//Rung 13 - ok2
//==================================================================================================
R[9] = (B[2] && !R[9]) || (R[9] && !B[2]);
//==================================================================================================
//Rung 14 - LVMDP 2
//==================================================================================================
OSR_dat[1].IN = (S[8] && B[3]); OSR(OSR_dat[1]); B[3] = OSR_dat[1].Q;
//==================================================================================================
//Rung 15 -
//==================================================================================================
R[13] = (B[3] && !R[13]) || (!B[3] && R[13]);
//==================================================================================================
//Rung 16 - SDP 1/1
//==================================================================================================
OSR_dat[2].IN = (R[9] && B[4]); OSR(OSR_dat[2]); B[4] = OSR_dat[2].Q;
//==================================================================================================
//Rung 17 -
//==================================================================================================
R[14] = (B[4] && !R[14]) || (!B[4] && R[14]);
//==================================================================================================
//Rung 18 - SDP 1/2
//==================================================================================================
OSR_dat[3].IN = (R[9] && B[5]); OSR(OSR_dat[3]); B[5] = OSR_dat[3].Q;
//==================================================================================================
//Rung 19 -
//==================================================================================================
R[15] = (R[9] && B[5] && !R[15]) || (R[9] && !B[5] && R[15]);
//==================================================================================================
//Rung 20 -
//==================================================================================================
OSR_dat[4].IN = (R[13] && B[6]); OSR(OSR_dat[4]); B[6] = OSR_dat[4].Q;
//==================================================================================================
//Rung 21 -
//==================================================================================================
R[16] = (R[13] && R[16] && !B[6]) || (R[13] && !R[16] && B[6]);
//==================================================================================================
//Rung 22 -
//==================================================================================================
OSR_dat[5].IN = (R[13] && B[7]); OSR(OSR_dat[5]); B[7] = OSR_dat[5].Q;
//==================================================================================================
//Rung 23 -
//==================================================================================================
R[17] = (R[13] && B[7] && !R[17]) || (R[13] && !B[7] && R[17]);
//==================================================================================================
}
//----------------------------------User Defined Function Stage 1-----------------------------------
//----------------------------------User Defined Function Stage 2-----------------------------------
//BLYNK_WRITE(V4) {
//value1 = param.asInt(); B[2] = value1;
//B3 juga
//}
//----------------------------------User Defined Function Stage 2-----------------------------------
//----------------------------------User Defined Function Stage 3-----------------------------------
//----------------------------------User Defined Function Stage 3-----------------------------------
//----------------------------------Testing Function------------------------------------------------
//----------------------------------Testing Function------------------------------------------------
//--------------------------------- Setup-----------------------------------------------------------
void setup() {
Serial.begin(115200);
// Initialize push button pins as inputs
for (int i = 0; i < numButtons; i++) {
pinMode(buttonPins[i], INPUT); //
}
// Initialize relay pins as outputs
for (int i = 0; i < numRelays; i++) {
pinMode(relayPins[i], OUTPUT);
digitalWrite(relayPins[i], LOW); // Turn off all relays initially Active Low
}
//Start Services
lcd1.init ();
lcd1.backlight ();
lcd1.clear();
//Timer Initialization (dalam detik)
T[1].PT = 1; //
T[2].PT = 1; //
T[3].PT = 1; //
T[4].PT = 1; //
T[5].PT = 1; //
T[6].PT = 1; //
T[7].PT = 1;
T[8].PT = 1; //timer switch display
CTU_data[1].PV = 2; //percobaan cranking sebelum alarm
spwm_dat[1].PT_on = 5; // in seconds (ON time)
spwm_dat[1].PT_off = 5; // in seconds (OFF time)
spwm_dat[2].PT_on = 10; // in seconds (ON time)
spwm_dat[2].PT_off = 5; // in seconds (OFF time)
spwm_dat[3].PT_on = 15; // in seconds (ON time)
spwm_dat[3].PT_off = 5; // in seconds (OFF time)
// Blynk.begin(auth, ssid, pass); //memulai Blynk
}
//Testing
//--------------------------------- Setup-----------------------------------------------------------
//--------------------------------- Loop------------------------------------------------------------
void loop() {
unsigned long startTime;
delay(10); // this speeds up the simulation
//Blynk.run(); //menjalankan blynk
//Input Update
DI_Update();
LCD1_Update(R, R_P, 13);
//blynking_LCD();
//Process
//ATS_System();
//Blynk.virtualWrite(V0, R[1]);
//Blynk.virtualWrite(V1, R[6]);
//Blynk.virtualWrite(V2, R[3] || R[4] || R[5]);
//Blynk.virtualWrite(V3, R[2]);
//Update Output
DO_Update();
}
//--------------------------------- Loop------------------------------------------------------------
//--------------------------------- Note------------------------------------------------------------
/*
I/O Maps
INPUT
S1 : MCB PLN
S2 : CRENG 1 0N
S3 : KRENG 2 ON
S4 : CRENG 3 ON
S5 : MCB 12 VOLT
S6 : OUT GENZED 220 V
S7 : ON/OFF ATS
Output
R1 : COIL PLN
R2 : OFF GENSED
R3 : CRENGKING 1
R4 : CRENGKING 2
R5 : CRENG 3
R6 : COIL KONTA2TOR GENZED
R7 : ALARM GAGAL STAR
R8 : OUT ATS
R9 : LVMDP1
R10 : OUT CRENG 1/220 V
R11 : OUT CRENG 2 220 V
R12 : OUT CRENG 3/220 V
R13 : OUT
Binary
B1 : Crank Fail 1
B2 : Control Remote LVMDP 1
B3 : Control Remote LVMDP 2
B4 : Control Remote LVMDP 3
B5 : Control Remote LVMDP 4
lcd1.setCursor(0, 1);
lcd1.print(" PLN:" + R_Status(S[1]) + " " + "GENSET:" + R_Status(S[6]) );
lcd1.setCursor(0, 2);
lcd1.print(" PLN:" + R_Status(R[1]) + " " + "GENSET:" + R_Status(R[6]));
lcd1.setCursor(0, 3);
lcd1.print(" CRANK:" + R_Status(R[3] || R[4] || R[5]) + " " + "STOP:" + R_Status(R[2]));
*/
//--------------------------------- Note------------------------------------------------------------
// UPDET MEGA
//S1 ;SW PLN
//S2 ;SW LFMDP1
//S3 ;SW LWMDP2
//S4 ;EMERGENSY
//S5 ;SW GENSET 22O VOLT
// Output
// R1 : COIL PLN
//R2 : OFF GENSED
//R3 : CRENGKING 1
// R4 : CRENGKING 2
// R5 : CRENG 3
// R6 : COIL KONTA2TOR GENZED
// R7 : ALARM GAGAL STAR
// R8 : OUT ATS
//R9 : LVMDP1
// R10 : OUT CRENG 1/220 V
// R11 : OUT CRENG 2 220 V
//R12 : OUT CRENG 3/220 V
// R13 : LVMDP 2
//R14 ; SDP 1/1
//R15 ;SDP 2 /1
//R16 ;EMERGENZY
//R17 ; SDP 1/2
//R18 ;SDP 2/2
//R19 ;SDP3/2
//R20 ; SDP 3/1