Wednesday, 13 August 2025

PROJEK FAHRIN: Membangunkan Energy Saver System bagi sebuah bilik: lampu automatik menyala bila ada orang masuk dan padam bila bilik kosong, projek FYP pelajar



https://images.app.goo.gl/bDmsD2AJBdiDAjje8

 Berikut ialah pelan lengkap untuk membangunkan Energy Saver System bagi sebuah bilik: lampu automatik menyala bila ada orang masuk dan padam bila bilik kosong.

1) Objektif & Prinsip Kerja

  • Objektif: Kurangkan pembaziran tenaga dengan mengawal lampu secara automatik.

  • Logik asas:

    • Bila penghuni > 0 → lampu ON

    • Bila penghuni = 0 (selepas tempoh tunggu) → lampu OFF

  • Pilihan sensor:

    1. PIR (simple, murah) – kesan pergerakan; risiko salah padam bila orang statik terlalu lama.

    2. Radar gelombang mikro/ultrasonik – sensitif, tembus bahan nipis; boleh lebih “noisy”.

    3. Pengiraan orang (disyorkan untuk “seorang atau lebih”) – dua beam IR / ToF di pintu (kira IN/OUT).

    4. LDR/ToF untuk cahaya ambien – elak menyalakan lampu jika bilik sudah terang.

Untuk memenuhi syarat “menyala jika seorang atau lebih, padam bila tiada”, kaedah pengiraan orang (dual-beam IN/OUT) adalah paling tepat.

2) Senibina Sistem (cadangan)

  • Pengawal: Arduino Uno / Nano / ESP32 (jika mahu Wi-Fi & logging).

  • Sensor pintu (kira orang): 2 × IR break-beam (A & B) dipasang selari ±15–20 cm jarak antara pasangan LED pemancar & penerima.

  • Sensor cahaya: LDR + pembahagi voltan (atau modul BH1750).

  • Pemacu lampu AC: Relay 1-channel (opto-isolate) / SSR / contactor mini (jika beban tinggi).

  • Bekalan kuasa: SMPS 230VAC→5VDC (dalam kotak bertutup).

  • Butang manual override (optional): Togol ON, AUTO, OFF.

  • Penunjuk: LED status / paparan kecil (optional).

3) Aliran Logik (flow)

  1. Inisialisasi pembilang occupancy = 0.

  2. Pantau turutan beam:

    • A→B terputus berturutan = INoccupancy++

    • B→A terputus berturutan = OUToccupancy-- (bawah minimum 0 dikekang).

  3. Jika occupancy > 0 dan lux < thresholdLampu ON.

  4. Jika occupancy == 0 → mula timer idle (cth 3–5 min); tamat timer → Lampu OFF.

  5. Jika override: patuh mod override.

  6. Debounce & penapisan untuk elak bacaan palsu.

4) Susun Atur Perkakasan (ringkas)

  • Beam A & B dipasang pada bingkai pintu: penerima menghadap pemancar (garisan jelas).

  • LDR di dinding/ceiling yang menerima cahaya bilik, bukan cahaya luar secara terus.

  • Relay memutuskan talian Live ke lampu; Neutral kekal.

  • Semua sambungan AC dalam kotak junction dengan terminal berpenebat (Keselamatan!).

Malaysia: 230 V, 50 Hz – WAJIB patuhi standard pendawaian & MCB/ELCB sedia ada. Jika tidak mahir AC, minta juruelektrik bertauliah.

5) Bahan (BOM) ringkas

  • 1× Arduino/ESP32

  • 2× IR break-beam modules (pemancar+receiver)

  • 1× LDR + resistor 10–100 kΩ (atau modul lux BH1750)

  • 1× Relay module 1-channel (5 V, opto) / SSR

  • 1× SMPS 5 V 2 A

  • Wayar dupont, breadboard/kotak projek, fius kecil (optional), butang toggle (optional)

6) Parameter Disyorkan

  • Threshold lux: 200–300 lux (laras ikut suasana).

  • Idle timeout (tiada orang): 180–300 s (3–5 min).

  • Debounce beam: 30–80 ms.

  • Ketinggian beam: ~90–110 cm dari lantai (laras untuk kanak-kanak/dewasa).

7) Pseudokod

occupancy = 0 lamp_on = false last_empty_time = now() loop: read beamA, beamB, lux, mode_switch if sequence A then B broken within window -> occupancy++ if sequence B then A broken within window -> occupancy = max(0, occupancy-1) if mode = AUTO: if occupancy > 0 and lux < LUX_TH: lamp_on = true if occupancy == 0: if now() - last_change_to_zero >= IDLE_TIMEOUT: lamp_on = false if occupancy changed to 0: last_change_to_zero = now() if mode = ON: lamp_on = true if mode = OFF: lamp_on = false write relay = lamp_on

8) Contoh Kod Arduino (dual-beam + LDR + relay)

// Pin assignment const int BEAM_A_PIN = 2; // IR receiver A (use INPUT_PULLUP) const int BEAM_B_PIN = 3; // IR receiver B const int RELAY_PIN = 8; // Relay module (LOW/ACTIVE depends module) const int LDR_PIN = A0; // LDR analog const int MODE_SW1 = 4; // optional: ON const int MODE_SW2 = 5; // optional: OFF (else AUTO) volatile int occupancy = 0; bool lampOn = false; const unsigned long DEBOUNCE_MS = 60; const unsigned long SEQ_WINDOW_MS = 800; // max time between A and B for one pass const unsigned long IDLE_TIMEOUT = 240000UL; // 4 min const int LUX_THRESHOLD_ADC = 450; // kalibrasi ikut rintangan LDR unsigned long lastZeroTime = 0; unsigned long lastBreakA = 0, lastBreakB = 0; bool aBroken = false, bBroken = false; void setup() { pinMode(BEAM_A_PIN, INPUT_PULLUP); pinMode(BEAM_B_PIN, INPUT_PULLUP); pinMode(RELAY_PIN, OUTPUT); pinMode(MODE_SW1, INPUT_PULLUP); pinMode(MODE_SW2, INPUT_PULLUP); digitalWrite(RELAY_PIN, LOW); // relay OFF awal Serial.begin(115200); } bool beamBroken(int pin){ // Receiver biasanya LOW bila beam TERPUTUS (bergantung modul) return digitalRead(pin) == LOW; } void loop() { unsigned long now = millis(); // --- baca beam dengan debounce ringkas --- static unsigned long la = 0, lb = 0; static bool aState = false, bState = false; bool aNow = beamBroken(BEAM_A_PIN); if (aNow != aState && (now - la) > DEBOUNCE_MS) { aState = aNow; la = now; if (aState) { // baru terputus lastBreakA = now; aBroken = true; // check turutan A->B (IN) if (bBroken == false && (now - lastBreakB) > SEQ_WINDOW_MS) { // tunggu B } } } bool bNow = beamBroken(BEAM_B_PIN); if (bNow != bState && (now - lb) > DEBOUNCE_MS) { bState = bNow; lb = now; if (bState) { // baru terputus lastBreakB = now; bBroken = true; } } // --- logik turutan --- if (aBroken && bBroken) { if (lastBreakA < lastBreakB && (lastBreakB - lastBreakA) <= SEQ_WINDOW_MS) { occupancy++; // IN Serial.println("IN"); } else if (lastBreakB < lastBreakA && (lastBreakA - lastBreakB) <= SEQ_WINDOW_MS) { occupancy = max(0, occupancy - 1); // OUT Serial.println("OUT"); } aBroken = bBroken = false; // reset if (occupancy == 0) lastZeroTime = now; } // --- baca mode --- bool forceOn = digitalRead(MODE_SW1) == LOW; // aktif bila ditekan (ke GND) bool forceOff = digitalRead(MODE_SW2) == LOW; int ldr = analogRead(LDR_PIN); // --- kawal lampu --- if (forceOn) { lampOn = true; } else if (forceOff) { lampOn = false; } else { // AUTO if (occupancy > 0 && ldr > LUX_THRESHOLD_ADC) { lampOn = false; // bilik terang: tak perlu lampu } else if (occupancy > 0) { lampOn = true; } else { // occupancy == 0 if (now - lastZeroTime >= IDLE_TIMEOUT) lampOn = false; } } // Sesuaikan logik relay: sesetengah modul aktif LOW digitalWrite(RELAY_PIN, lampOn ? HIGH : LOW); // Debug ringkas static unsigned long lastPrint = 0; if (now - lastPrint > 1000) { lastPrint = now; Serial.print("Occ="); Serial.print(occupancy); Serial.print(" LDR="); Serial.print(ldr); Serial.print(" Lamp="); Serial.println(lampOn ? "ON" : "OFF"); } }

Nota: Nilai LUX_THRESHOLD_ADC perlu ditala mengikut rintangan LDR & suasana cahaya. Jika guna modul BH1750, tukar kepada bacaan lux sebenar (cth if (lux < 250)).

9) Pengujian & Penalaan

  1. Uji turutan: melintas dari luar→dalam (A→B) bertambah; dari dalam→luar (B→A) berkurang.

  2. Uji pintu sempit/ramai serentak: kecilkan SEQ_WINDOW_MS jika kiraan berganda palsu.

  3. Uji statik: duduk tanpa bergerak; lampu kekal ON kerana occupancy > 0.

  4. Uji cahaya siang: laras LUX_THRESHOLD.

  5. Uji override: pastikan ON/AUTO/OFF berfungsi.

10) Keselamatan & Kepatuhan

  • Pisahkan ruang AC dan DC dalam kotak projek.

  • Gunakan fius kecil pada sisi AC untuk relay/contactor jika sesuai.

  • Pastikan modul relay berlabel 250VAC dan arus melebihi arus lampu.

  • Ikuti piawaian MS IEC & amalan pendawaian tempatan; guna konduit/terminal berpenutup.


........................................................................................................................................

kod penuh ESP32 + BH1750 + Web UI dalam canvas di sebelah.

Ringkas cara guna:

  1. Wiring ikut nota dalam komen kod (pin dinyatakan).

  2. Flash ke ESP32 (Arduino IDE): pasang library BH1750.

  3. ESP32 akan buka hotspot EnergySaver-AP (password: esp32setup). Sambung Wi-Fi dan buka http://192.168.4.1/.

  4. Di paparan web, anda boleh lihat Occupancy, Lux, Lamp status serta ubah Mode (AUTO/ON/OFF), Lux threshold, dan Idle timeout.

Tip pemasangan:

  • Pastikan beam A dahulu kemudian B untuk masuk; BA untuk keluar.

  • Lindungi sensor IR daripada cahaya matahari terus.

  • Relay mestilah sesuai dengan beban lampu; kerja AC 230 V wajib oleh juruelektrik bertauliah.

/*
ESP32 Energy Saver System — Dual-Beam People Counter + Ambient Light (BH1750) + Web UI
*/

#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <BH1750.h>

const int PIN_BEAM_A = 18;
const int PIN_BEAM_B = 19;
const int PIN_RELAY  = 23;
const bool RELAY_ACTIVE_HIGH = true;

const char* AP_SSID = "EnergySaver-AP";
const char* AP_PASS = "esp32setup";
WebServer server(80);

BH1750 lightMeter;

volatile int occupancy = 0;
unsigned long lastZeroTime = 0;
int LUX_THRESHOLD = 250;
unsigned long IDLE_TIMEOUT_MS = 240000UL;
const unsigned long DEBOUNCE_MS = 60;
const unsigned long SEQ_WINDOW_MS = 800;

enum Mode {AUTO=0, FORCE_ON=1, FORCE_OFF=2};
Mode mode = AUTO;

bool lampOn = false;
unsigned long lastBreakA = 0, lastBreakB = 0;
bool aBroken = false, bBroken = false;
bool aState = false, bState = false;
unsigned long la = 0, lb = 0;

bool beamBroken(int pin){ return digitalRead(pin) == LOW; }
float readLux(){ return lightMeter.measurementReady() ? lightMeter.readLightLevel() : -1.0f; }
void setRelay(bool on){ digitalWrite(PIN_RELAY, RELAY_ACTIVE_HIGH ? (on ? HIGH : LOW) : (on ? LOW : HIGH)); }
String modeToString(Mode m){ return m==AUTO?"AUTO":(m==FORCE_ON?"ON":"OFF"); }

void handleRoot(){
  float lux = readLux();
  String html = "<html><body><h2>ESP32 Energy Saver</h2>";
  html += "Mode: "+modeToString(mode)+"<br>Occupancy: "+String(occupancy)+"<br>Lamp: "+(lampOn?"ON":"OFF")+"<br>Lux: "+String(lux,1)+"<br>";
  html += "<form action='/set' method='get'>Mode <select name='mode'><option value='auto'"+(mode==AUTO?" selected":"")+">AUTO</option><option value='on'"+(mode==FORCE_ON?" selected":"")+">ON</option><option value='off'"+(mode==FORCE_OFF?" selected":"")+">OFF</option></select><br>Lux threshold <input type='number' name='lux' value='"+String(LUX_THRESHOLD)+"'><br>Idle timeout (s) <input type='number' name='idle' value='"+String(IDLE_TIMEOUT_MS/1000)+"'><br><button type='submit'>Save</button></form></body></html>";
  server.send(200, "text/html", html);
}

void handleSet(){
  if (server.hasArg("mode")){
    String m = server.arg("mode"); m.toLowerCase();
    if (m=="auto") mode = AUTO; else if (m=="on") mode = FORCE_ON; else if (m=="off") mode = FORCE_OFF;
  }
  if (server.hasArg("lux")) LUX_THRESHOLD = server.arg("lux").toInt();
  if (server.hasArg("idle")) IDLE_TIMEOUT_MS = server.arg("idle").toInt()*1000UL;
  server.sendHeader("Location", "/"); server.send(302, "text/plain", "Updated");
}

void handleApi(){
  float lux = readLux();
  String json = "{\"mode\":\""+modeToString(mode)+"\",\"occupancy\":"+String(occupancy)+",\"lamp\":\""+(lampOn?"ON":"OFF")+"\",\"lux\":"+String(lux,1)+",\"threshold\":"+String(LUX_THRESHOLD)+",\"idle_s\":"+String(IDLE_TIMEOUT_MS/1000)+"}";
  server.send(200, "application/json", json);
}

void setup(){
  Serial.begin(115200);
  pinMode(PIN_BEAM_A, INPUT_PULLUP);
  pinMode(PIN_BEAM_B, INPUT_PULLUP);
  pinMode(PIN_RELAY, OUTPUT);
  setRelay(false);
  Wire.begin(21,22);
  lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
  WiFi.mode(WIFI_AP);
  WiFi.softAP(AP_SSID, AP_PASS);
  server.on("/", handleRoot);
  server.on("/set", handleSet);
  server.on("/api/status", handleApi);
  server.begin();
  lastZeroTime = millis();
}

void loop(){
  unsigned long now = millis();
  server.handleClient();
  bool aNow = beamBroken(PIN_BEAM_A);
  if (aNow != aState && (now - la) > DEBOUNCE_MS){ aState = aNow; la = now; if (aState){ lastBreakA = now; aBroken = true; }}
  bool bNow = beamBroken(PIN_BEAM_B);
  if (bNow != bState && (now - lb) > DEBOUNCE_MS){ bState = bNow; lb = now; if (bState){ lastBreakB = now; bBroken = true; }}
  if (aBroken && bBroken){
    if (lastBreakA < lastBreakB && (lastBreakB - lastBreakA) <= SEQ_WINDOW_MS) occupancy++;
    else if (lastBreakB < lastBreakA && (lastBreakA - lastBreakB) <= SEQ_WINDOW_MS) occupancy = max(0, occupancy-1);
    aBroken = bBroken = false;
    if (occupancy == 0) lastZeroTime = now;
  }
  float lux = readLux();
  if (mode == FORCE_ON) lampOn = true;
  else if (mode == FORCE_OFF) lampOn = false;
  else {
    bool darkEnough = (lux < 0) ? true : (lux < LUX_THRESHOLD);
    if (occupancy > 0 && darkEnough) lampOn = true;
    else if (occupancy == 0 && (now - lastZeroTime >= IDLE_TIMEOUT_MS)) lampOn = false;
    else if (occupancy > 0 && !darkEnough) lampOn = false;
  }
  setRelay(lampOn);
  static unsigned long lastPrint = 0;
  if (now - lastPrint > 1000){
    lastPrint = now;
    Serial.printf("Occ=%d Lux=%.1f Mode=%s Lamp=%s\n", occupancy, lux, modeToString(mode).c_str(), lampOn?"ON":"OFF");
  }
}


No comments:

Post a Comment