/*
Input: (5)
pH, Suhu, TDS, Kekeruhan, Jarak Ultrasonik
Proses: (2)
Fuzzy
Output: (2)
Non-fuzzy: (2)
Kirim Data Sensor ke NodeMCU
Kontrol Relay DC
Fuzzy: (2)
BTS7960 (2 Pompa, 1 Peltier)
Dimmer (Heater) via PWM
*/
// simulation
class Simulation {
public:
// Method
float noiseFloat(int lower, int upper){ // example: simulation.noise(5,7);
int range = upper-lower;
int acakan = random(0, range) + lower;
float decimal = random(0, 999)*0.001;
float ret = acakan + decimal;
return ret;
}
int noiseInt(int lower, int upper){ // example: simulation.noise(5,7);
int range = upper-lower;
int acakan = random(0, range) + lower;
return acakan;
}
};
Simulation simulation;
// Library
// Definisi: Indeks Input
#define PH 0
#define SUHU 1
#define TDS 2
#define KEKERUHAN 3
#define ULTRASONIK 4
// INPUT & OUTPUT: Definisi: Pin Input & Output
// Pin Input
#define PIN_SENSOR_PH A0
#define PIN_SENSOR_SUHU A13
#define PIN_SENSOR_TDS A1
#define PIN_SENSOR_KEKERUHAN A2
#define PIN_SENSOR_ULTRASONIK A3
#define PIN_PUSHBUTTON 23
// Pin Output
#define PIN_RELAY_1 31
#define PIN_RELAY_2 33
#define PIN_RELAY_3 35
#define PIN_RELAY_4 37
#define PIN_BTS_1 10
#define PIN_BTS_2 11
#define PIN_BTS_3 12
#define PIN_AC_LIGHT_DIMMER 5
#define PIN_SERVO_1 6
#define PIN_SERVO_2 7
#define PIN_SERVO_3 8
#define PIN_SERVO_4 9
// INPUT: Library, Konfigurasi, Inisialisasi
// -- PH
#define PH_OFFSET 0.00
#define PH_SAMPLING_INTERVAL 20
#define PH_AVERAGING_LENGTH 40
// -- Suhu
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h> //library I2C
OneWire wireSuhu(PIN_SENSOR_SUHU); // SUHU
DallasTemperature sensorSuhu(&wireSuhu); // SUHU
// -- TDS
#define TDS_VREF 5.0
#define TDS_SAMPLE_COUNT 30
#define TDS_TEMPERATURE 25
// -- Ultrasonik
#define ULTRASONIK_NUM_READINGS 10
#define ULTRASONIK_MAX_RANGE (520)
#define ULTRASONIK_ADC_SOLUTION (1023.0)
// -- Pushbutton
#define PUSHBUTTON_DILEPAS LOW // default: LOW
#define PUSHBUTTON_DITEKAN HIGH
// -- RTC
#include "RTClib.h"
RTC_DS1307 rtc;
// [OUTPUT] Library, Konfigurasi, Inisialisasi
// Output - AC Light Dimmer
//#include <RBDdimmer.h>
//dimmerLamp dimmer(PIN_AC_LIGHT_DIMMER)
// Output - Servo
#include <Servo.h>
Servo myservo1; // Pakan
Servo myservo2; // Garam
Servo myservo3; // Pestisida
Servo myservo4; // Obat PK
// Output - LCD I2C
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
#define LCD_MODE_TAMPILAN_UTAMA LOW
#define LCD_MODE_TAMPILAN_SAMPINGAN HIGH
#define LCD_MODE_TAMPILAN_GANTI true
#define LCD_MODE_TAMPILAN_TETAP false
// Output - Serial Communication
#include <SoftwareSerial.h>
SoftwareSerial serkom(1, 0); // RX, TX
class Input {
private:
// PH - Variabel
int phAveragingArray[PH_AVERAGING_LENGTH];
int phAveragingIndex = 0;
float phValue;
unsigned long phSamplingTime;
// PH - Method
double averageArray(int* arr, int number) {
int i;
int max, min;
double avg;
long amount = 0;
if (number <= 0) {
Serial.println("Error number for the array to averaging!/n");
return 0;
}
if (number < 5) { //less than 5, calculated directly statistics
for (i = 0; i < number; i++) {
amount += arr[i];
}
avg = amount / number;
return avg;
} else {
if (arr[0] < arr[1]) {
min = arr[0];
max = arr[1];
} else {
min = arr[1];
max = arr[0];
}
for (i = 2; i < number; i++) {
if (arr[i] < min) {
amount += min; //arr<min
min = arr[i];
} else {
if (arr[i] > max) {
amount += max; //arr>max
max = arr[i];
} else {
amount += arr[i]; //min<=arr<=max
}
} //if
} //for
avg = (double)amount / (number - 2);
} //if
return avg;
}
// TDS - Variabel
int TdsAnalogBuffer[TDS_SAMPLE_COUNT];
int TdsAnalogBufferTemp[TDS_SAMPLE_COUNT];
int TdsAnalogBufferIndex = 0;
float TdsValue;
unsigned long TdsSampleTimepoint, TdsPrintTimepoint;
// TDS - Method
int getMedianNum(int bArray[], int iFilterLen) {
int bTab[iFilterLen];
for (byte i = 0; i < iFilterLen; i++)
bTab[i] = bArray[i];
int i, j, bTemp;
for (j = 0; j < iFilterLen - 1; j++) {
for (i = 0; i < iFilterLen - j - 1; i++) {
if (bTab[i] > bTab[i + 1]) {
bTemp = bTab[i];
bTab[i] = bTab[i + 1];
bTab[i + 1] = bTemp;
}
}
}
if ((iFilterLen & 1) > 0)
bTemp = bTab[(iFilterLen - 1) / 2];
else
bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
return bTemp;
}
// Ultrasonik - Variabel
float UltrasonikReadingArray[ULTRASONIK_NUM_READINGS];
int UltrasonikReadingIndex;
float UltrasonikReadingTotal, UltrasonikReadingAverage, UltrasonikJarak;
public:
// Variable Utama
float dataSensor[5] = {0,0,0,0,0};
bool kondisiPushButton = LOW, kondisiPushButtonSebelumnya = LOW;
bool kondisiPushButtonAktif = false;
// Method
void inisialisasi(){
pinMode(PIN_SENSOR_PH, INPUT); // Sensor PH
pinMode(PIN_SENSOR_SUHU, INPUT); // Sensor Suhu
pinMode(PIN_SENSOR_TDS, INPUT); // Sensor TDS
pinMode(PIN_SENSOR_KEKERUHAN, INPUT); // Sensor Kekeruhan
pinMode(PIN_SENSOR_ULTRASONIK, INPUT); // Sensor Ultrasonik
pinMode(PIN_PUSHBUTTON, INPUT); // Pushbutton
rtc.begin();
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
float bacaSensor(unsigned int jenisSensor){ // Pembacaan Sensor
if(jenisSensor == PH){
if (millis() - phSamplingTime > PH_SAMPLING_INTERVAL) {
phAveragingArray[phAveragingIndex++] = analogRead(PIN_SENSOR_PH);
if (phAveragingIndex == PH_AVERAGING_LENGTH) phAveragingIndex = 0;
float phAverageVoltage = averageArray(phAveragingArray, PH_AVERAGING_LENGTH) * 5.0 / 1024;
phValue = ((phAverageVoltage - 0.056) / 0.274) + PH_OFFSET;
phSamplingTime = millis();
}
return phValue; // input.dataSensor[PH]
} else if(jenisSensor == SUHU){
sensorSuhu.requestTemperatures();
float suhu = sensorSuhu.getTempCByIndex(0);
return suhu; // input.dataSensor[SUHU]
} else if(jenisSensor == TDS){
// Pembacaan Sensor TDS
if (millis() - TdsSampleTimepoint > 40U){ // Read ADC every 40 milliseconds
TdsSampleTimepoint = millis();
TdsAnalogBuffer[TdsAnalogBufferIndex] = analogRead(PIN_SENSOR_TDS); //read the analog value and store into the buffer
TdsAnalogBufferIndex++;
if (TdsAnalogBufferIndex == TDS_SAMPLE_COUNT)
TdsAnalogBufferIndex = 0;
}
if (millis() - TdsPrintTimepoint > 800U) {
TdsPrintTimepoint = millis();
for (int TdsAnalogBufferCopyIndex = 0; TdsAnalogBufferCopyIndex < TDS_SAMPLE_COUNT; TdsAnalogBufferCopyIndex++)
TdsAnalogBufferTemp[TdsAnalogBufferCopyIndex] = TdsAnalogBuffer[TdsAnalogBufferCopyIndex];
float TdsAverageVoltage = getMedianNum(TdsAnalogBufferTemp, TDS_SAMPLE_COUNT) * (float)TDS_VREF / 1024.0; // read the analog value more stable by the median filtering algorithm, and convert to voltage value
float compensationCoefficient = 1.0 + 0.02 * (TDS_TEMPERATURE - 25.0); //temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0));
float compensationVoltage = TdsAverageVoltage / compensationCoefficient; //temperature compensation
TdsValue = (133.42 * compensationVoltage * compensationVoltage * compensationVoltage - 255.86 * compensationVoltage * compensationVoltage + 857.39 * compensationVoltage) * 0.5; //convert voltage value to tds value
}
return TdsValue; // input.dataSensor[TDS]
} else if(jenisSensor == KEKERUHAN){
int TurbidityADC = analogRead(PIN_SENSOR_KEKERUHAN);
float TurbidityTegangan = TurbidityADC * (5.0/1024.0);
return TurbidityTegangan; // input.dataSensor[KEKERUHAN]
} else if(jenisSensor == ULTRASONIK){
int UltrasonikReading = analogRead(PIN_SENSOR_ULTRASONIK);
UltrasonikReadingTotal = UltrasonikReadingTotal-UltrasonikReadingArray[UltrasonikReadingIndex];
UltrasonikReadingArray[UltrasonikReadingIndex] = UltrasonikReading;
UltrasonikReadingTotal = UltrasonikReadingTotal + UltrasonikReadingArray[UltrasonikReadingIndex];
UltrasonikReadingIndex = (UltrasonikReadingIndex+1) % ULTRASONIK_NUM_READINGS;
UltrasonikReadingAverage = UltrasonikReadingTotal/ULTRASONIK_NUM_READINGS;
UltrasonikJarak = UltrasonikReadingAverage * ULTRASONIK_MAX_RANGE / ULTRASONIK_ADC_SOLUTION;
return UltrasonikJarak; // input.dataSensor[ULTRASONIK]
}
}
bool bacaKondisiPushButton(){
kondisiPushButtonSebelumnya = kondisiPushButton;
kondisiPushButton = digitalRead(PIN_PUSHBUTTON);
if(kondisiPushButton==PUSHBUTTON_DITEKAN && kondisiPushButtonSebelumnya==PUSHBUTTON_DILEPAS){
kondisiPushButtonAktif = true;
} else {
kondisiPushButtonAktif = false;
}
return kondisiPushButton;
}
};
Input input;
class Output {
private:
bool LcdModeTampilan = false;
byte LcdSegmenTampilan, LcdSegmenTampilanSebelumnya;
unsigned long LcdWaktuTampilanTerakhir;
const unsigned int LcdDurasiTampilanPerSegmen = 3000;
public:
// Method
void inisialisasi(){
pinMode(PIN_RELAY_1, OUTPUT);
pinMode(PIN_RELAY_2, OUTPUT);
pinMode(PIN_RELAY_3, OUTPUT);
pinMode(PIN_RELAY_4, OUTPUT);
pinMode(PIN_BTS_1, OUTPUT);
pinMode(PIN_BTS_2, OUTPUT);
pinMode(PIN_BTS_3, OUTPUT);
pinMode(PIN_AC_LIGHT_DIMMER, OUTPUT);
pinMode(PIN_SERVO_1, OUTPUT);
pinMode(PIN_SERVO_2, OUTPUT);
pinMode(PIN_SERVO_3, OUTPUT);
pinMode(PIN_SERVO_4, OUTPUT);
myservo1.attach(PIN_SERVO_1);
myservo2.attach(PIN_SERVO_2);
myservo3.attach(PIN_SERVO_3);
myservo4.attach(PIN_SERVO_4);
myservo1.write(0);
myservo2.write(0);
myservo3.write(0);
myservo4.write(0);
lcd.init();
lcd.backlight();
lcd.home();
lcd.clear();
serkom.begin(9600);
}
void kontrolRelay(){
if(input.dataSensor[SUHU]<30.00){
digitalWrite(PIN_RELAY_2, HIGH);
digitalWrite(PIN_RELAY_3, HIGH);
} else {
digitalWrite(PIN_RELAY_2, LOW);
digitalWrite(PIN_RELAY_3, LOW);
}
digitalWrite(PIN_RELAY_1, HIGH);
digitalWrite(PIN_RELAY_4, HIGH);
}
void kontrolBTS7960(){
if(input.dataSensor[SUHU]<30.00){
analogWrite(PIN_BTS_1, 0);
analogWrite(PIN_BTS_2, 0);
analogWrite(PIN_BTS_3, 0);
} else {
digitalWrite(PIN_RELAY_2, LOW);
digitalWrite(PIN_RELAY_3, LOW);
}
digitalWrite(PIN_RELAY_1, HIGH);
digitalWrite(PIN_RELAY_4, HIGH);
}
void tampilanLcd(bool gantiModeTampilan){
if(gantiModeTampilan){
LcdModeTampilan = !LcdModeTampilan;
lcd.clear();
LcdWaktuTampilanTerakhir = millis();
}
if(LcdModeTampilan == LCD_MODE_TAMPILAN_UTAMA){
_tampilanLcdUtama();
} else if(LcdModeTampilan == LCD_MODE_TAMPILAN_SAMPINGAN){
_tampilanLcdSampingan();
}
}
void _tampilanLcdUtama(){
lcd.setCursor(0, 0); lcd.print(F("P:")); lcd.print(input.dataSensor[PH], 2); lcd.print(F(" "));
lcd.setCursor(10, 0); lcd.print(F("S:")); lcd.print(input.dataSensor[SUHU], 2);
lcd.setCursor(0, 1); lcd.print(F("T:")); lcd.print(input.dataSensor[TDS], 2);
lcd.setCursor(10, 1); lcd.print(F("K:")); lcd.print(input.dataSensor[KEKERUHAN], 2);
//lcd.setCursor(14, 1); lcd.print(F("H:")); lcd.print(dimmer.getPower()); lcd.print(F("%"));
lcd.setCursor(0, 2); lcd.print(F("U:")); lcd.print(input.dataSensor[ULTRASONIK], 2);
lcd.print(F(" ")); //lcd.setCursor(11, 2);
if(input.dataSensor[ULTRASONIK]>=0 && input.dataSensor[ULTRASONIK]<8){
lcd.print(F("(Error!)"));
} else if(input.dataSensor[ULTRASONIK]>=8 && input.dataSensor[ULTRASONIK]<10){
lcd.print(F("(Kosong)"));
} else if(input.dataSensor[ULTRASONIK]>=10 && input.dataSensor[ULTRASONIK]<15){
lcd.print(F("(Rendah)"));
} else if(input.dataSensor[ULTRASONIK]>=25 && input.dataSensor[ULTRASONIK]<35){
lcd.print(F("(Sedang)"));
} else if(input.dataSensor[ULTRASONIK]>=35 && input.dataSensor[ULTRASONIK]<65){
lcd.print(F("(Tinggi)"));
} else if(input.dataSensor[ULTRASONIK]>=65 && input.dataSensor[ULTRASONIK]<1000){
lcd.print(F("(Penuh) "));
}
lcd.print(F(" "));
lcd.setCursor(3, 3);
if (input.dataSensor[PH] >= 6.5 && input.dataSensor[PH] <= 8.5
&& input.dataSensor[SUHU] >= 25.00 && input.dataSensor[SUHU] <= 30.00
&& input.dataSensor[TDS] <= 400.00 && input.dataSensor[KEKERUHAN] <= 400.00
) {
lcd.print(F(" (Ideal) "));
} else {
lcd.print(F("(Tidak Ideal)"));
}
}
void _tampilanLcdSampingan(){
const byte LcdJumlahSegmenTampilan = 2;
LcdSegmenTampilan = ((millis()-LcdWaktuTampilanTerakhir)%(LcdDurasiTampilanPerSegmen*LcdJumlahSegmenTampilan))/LcdDurasiTampilanPerSegmen;
if(LcdSegmenTampilanSebelumnya != LcdSegmenTampilan){ lcd.clear(); }
if(LcdSegmenTampilan == 0){
lcd.setCursor(0, 0); lcd.print(F(" Ketinggian Air "));
lcd.setCursor(0, 1); lcd.print(F("Ultrasonic:")); lcd.print(input.dataSensor[ULTRASONIK], 2);
} else if(LcdSegmenTampilan == 1){
DateTime currentTime = rtc.now();
String tanggal = currentTime.timestamp(DateTime::TIMESTAMP_DATE);
String jam = currentTime.timestamp(DateTime::TIMESTAMP_TIME);
lcd.setCursor(0, 0); lcd.print(tanggal);
lcd.setCursor(0, 1); lcd.print(" " + jam + " ");
}
LcdSegmenTampilanSebelumnya = LcdSegmenTampilan;
}
void kirimDataSensorKeNodeMCU(){
String payload = String(input.dataSensor[PH], 2) + "# "
+ String(input.dataSensor[SUHU]) + "# "
+ String(input.dataSensor[TDS]) + "# "
+ String(input.dataSensor[KEKERUHAN]) + "# "
+ String(input.dataSensor[ULTRASONIK]);
serkom.println(payload);
}
};
Output output;
void setup() {
Serial.begin(9600);
input.inisialisasi();
output.inisialisasi();
}
void loop() {
// [INPUT]
// Input - Pembacaan Kondisi Pushbutton
input.kondisiPushButton = input.bacaKondisiPushButton();
// Input - Pembacaan Sensor
input.dataSensor[PH] = input.bacaSensor(PH);
input.dataSensor[SUHU] = input.bacaSensor(SUHU);
input.dataSensor[TDS] = input.bacaSensor(TDS);
input.dataSensor[KEKERUHAN] = input.bacaSensor(KEKERUHAN);
input.dataSensor[ULTRASONIK] = input.bacaSensor(ULTRASONIK);
// [PROSES]
// [OUTPUT]
// Output: LCD (berdasarkan pushbutton)
if(input.kondisiPushButtonAktif){
output.tampilanLcd(LCD_MODE_TAMPILAN_GANTI);
} else {
output.tampilanLcd(LCD_MODE_TAMPILAN_TETAP);
}
myservo1.write(simulation.noiseInt(0,45));
myservo2.write(simulation.noiseInt(45,90));
myservo3.write(simulation.noiseInt(90,135));
myservo4.write(simulation.noiseInt(135,180));
//
// output.kirimDataSensorKeNodeMCU()
// Output: Data Sensor -> Serial Monitor
/*
Serial.print("PH:"); Serial.println(input.dataSensor[PH]);
Serial.print("Suhu:"); Serial.println(input.dataSensor[SUHU]);
Serial.print("TDS:"); Serial.println(input.dataSensor[TDS]);
Serial.print("Kekeruhan:"); Serial.println(input.dataSensor[KEKERUHAN]);
Serial.print("Ultrasonik:"); Serial.println(input.dataSensor[ULTRASONIK]);
*/
}