// Fuel + Temperature Gauge using WS2812 (Adafruit NeoPixel)
// Termasuk animasi permainan (startup) yang berjalan sekali saat power on
#include <Adafruit_NeoPixel.h>
#define PIN_STRIP 6 // data pin untuk WS2812
#define FUEL_LEDS 7 // jumlah LED untuk fuel bar (indeks 0..FUEL_LEDS-1)
#define TEMP_LEDS 7 // jumlah LED untuk temperature bar (indeks FUEL_LEDS..FUEL_LEDS+TEMP_LEDS-1)
#define NUM_LEDS (FUEL_LEDS + TEMP_LEDS)
Adafruit_NeoPixel strip(NUM_LEDS, PIN_STRIP, NEO_GRB + NEO_KHZ800);
// Pilihan
const bool ENABLE_STARTUP_ANIMATION = true; // set false jika tidak ingin animasi
uint8_t globalBrightness = 150; // 0..255
// Analog inputs
const int fuelAnalogPin = A0;
const int tempAnalogPin = A1;
// Smoothing parameters (moving average)
const int SAMPLES = 8;
int fuelSamples[SAMPLES];
int tempSamples[SAMPLES];
int fuelIndex = 0;
int tempIndex = 0;
long fuelSum = 0;
long tempSum = 0;
// Calibration (sesuaikan setelah pengukuran)
int fuelSensorMin = 50; // ADC saat tank kosong
int fuelSensorMax = 980; // ADC saat tank penuh
// Temperature calibration (TMP36)
float tempC_min = 110.0; // suhu yang memetakan ke 0% LED
float tempC_max = 0.0; // suhu yang memetakan ke 100% LED
// Low fuel threshold
const int lowFuelThresholdPercent = 10; // < ini => berkedip
// Helper: color gradient from red->yellow-green>(percent 0..100)
uint32_t colorGradient(float pct) {
pct = constrain(pct, 0.0, 100.0) / 100.0;
if (pct <= 0.5) {
float t = pct / 0.5;
uint8_t r = 255;
uint8_t g = (uint8_t)(t * 255.0);
return strip.Color(r, g, 0);
} else {
float t = (pct - 0.5) / 0.5;
uint8_t r = (uint8_t)((1.0 - t) * 255.0);
uint8_t g = 255;
return strip.Color(r, g, 0);
}
}
// --- Startup animations (simple "game" effects) ---
void startup_chase(uint32_t color, int repeats, int speedMs) {
for (int r = 0; r < repeats; r++) {
for (int i = 0; i < NUM_LEDS; i++) {
strip.clear();
strip.setPixelColor(i, color);
if (i+1 < NUM_LEDS) strip.setPixelColor(i+1, color); // double dot
strip.show();
delay(speedMs);
}
}
}
void startup_wipe_rainbow(int speedMs) {
for (int i = 0; i < NUM_LEDS; i++) {
uint32_t c = Wheel(map(i, 0, NUM_LEDS-1, 0, 255));
strip.setPixelColor(i, c);
strip.show();
delay(speedMs);
}
}
void startup_bounce(uint32_t color, int loops, int speedMs) {
for (int l = 0; l < loops; l++) {
// forward
for (int i = 0; i < NUM_LEDS; i++) {
strip.clear();
strip.setPixelColor(i, color);
strip.show();
delay(speedMs);
}
// backward
for (int i = NUM_LEDS-1; i >= 0; i--) {
strip.clear();
strip.setPixelColor(i, color);
strip.show();
delay(speedMs);
}
}
}
// helper wheel for rainbow
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
} else if (WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
} else {
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
}
void play_startup_sequence() {
strip.setBrightness(globalBrightness);
strip.clear();
strip.show();
// 1) Quick green chase (like "ready")
startup_chase(strip.Color(0,200,0), 1, 30);
// 2) Rainbow wipe
startup_wipe_rainbow(25);
delay(100);
// 3) Bounce red and blue like "game"
startup_bounce(strip.Color(0,0,200), 1, 25);
startup_bounce(strip.Color(200,0,0), 1, 25);
// 4) Short all-ON flash and fade out
for (int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, strip.Color(255,255,255));
strip.show();
delay(200);
for (int b = globalBrightness; b >= 40; b -= 30) {
strip.setBrightness(b);
strip.show();
delay(60);
}
strip.setBrightness(globalBrightness);
strip.clear();
strip.show();
delay(150);
}
// --- Setup & Loop ---
void setup() {
Serial.begin(115200);
strip.begin();
strip.setBrightness(globalBrightness);
strip.show();
// initial smoothing fill
for (int i = 0; i < SAMPLES; i++) {
int vFuel = analogRead(fuelAnalogPin);
int vTemp = analogRead(tempAnalogPin);
fuelSamples[i] = vFuel;
tempSamples[i] = vTemp;
fuelSum += vFuel;
tempSum += vTemp;
delay(5);
}
if (ENABLE_STARTUP_ANIMATION) play_startup_sequence();
}
void loop() {
// --- fuel smoothing ---
int rawFuel = analogRead(fuelAnalogPin);
fuelSum -= fuelSamples[fuelIndex];
fuelSamples[fuelIndex] = rawFuel;
fuelSum += fuelSamples[fuelIndex];
fuelIndex = (fuelIndex + 1) % SAMPLES;
int avgFuel = fuelSum / SAMPLES;
// clamp & percent
int sMin = min(fuelSensorMin, fuelSensorMax);
int sMax = max(fuelSensorMin, fuelSensorMax);
int clippedFuel = constrain(avgFuel, sMin, sMax);
float fuelPercent = 0.0;
if (sMax != sMin) fuelPercent = (float)(clippedFuel - sMin) / (float)(sMax - sMin) * 100.0;
fuelPercent = constrain(fuelPercent, 0.0, 100.0);
int fuelOn = map((int)fuelPercent, 0, 100, 0, FUEL_LEDS);
// --- temp smoothing ---
int rawTempADC = analogRead(tempAnalogPin);
tempSum -= tempSamples[tempIndex];
tempSamples[tempIndex] = rawTempADC;
tempSum += tempSamples[tempIndex];
tempIndex = (tempIndex + 1) % SAMPLES;
int avgTempADC = tempSum / SAMPLES;
// TMP36 conversion: Vout = ADC/1023 * Vref(=5V) ; TempC = (Vout - 0.5) * 100
float voltage = (avgTempADC / 1023.0) * 5.0;
float tempC = (voltage - 0.5) * 100.0;
float tempPercent = (tempC - tempC_min) / (tempC_max - tempC_min) * 100.0;
tempPercent = constrain(tempPercent, 0.0, 100.0);
int tempOn = map((int)tempPercent, 0, 100, 0, TEMP_LEDS);
// --- draw fuel bar on strip [0 .. FUEL_LEDS-1] ---
for (int i = 0; i < FUEL_LEDS; i++) {
if (i < fuelOn) {
float ledPct = ((float)(i + 1) / (float)FUEL_LEDS) * 100.0;
strip.setPixelColor(i, colorGradient(ledPct));
} else {
strip.setPixelColor(i, 0);
}
}
// low fuel blinking: override two leftmost LEDs (index 0,1)
static unsigned long lastToggle = 0;
static bool flashState = false;
unsigned long now = millis();
if (fuelPercent <= lowFuelThresholdPercent) {
if (now - lastToggle >= 400) {
flashState = !flashState;
lastToggle = now;
}
uint32_t warnColor = strip.Color(255, 0, 0);
strip.setPixelColor(0, flashState ? warnColor : 0);
if (FUEL_LEDS > 1) strip.setPixelColor(0, flashState ? warnColor : 0);
}
// --- draw temperature bar on strip [FUEL_LEDS .. NUM_LEDS-1] ---
for (int i = 0; i < TEMP_LEDS; i++) {
int idx = FUEL_LEDS + i;
if (i < tempOn) {
uint32_t c;
if (tempPercent <= 50.0) {
float t = tempPercent / 50.0;
uint8_t r = 0;
uint8_t g = (uint8_t)(0 * (1 - t) + 255 * t);
uint8_t b = (uint8_t)(255 * (1 - t) + 0 * t);
c = strip.Color(r, g, b);
} else {
float t = (tempPercent - 50.0) / 50.0;
uint8_t r = (uint8_t)(0 * (1 - t) + 255 * t);
uint8_t g = (uint8_t)(255 * (1 - t) + 0 * t);
c = strip.Color(r, g, 0);
}
strip.setPixelColor(idx, c);
} else {
strip.setPixelColor(idx, 0);
}
}
strip.show();
// --- debug print ---
Serial.print("FuelADC="); Serial.print(avgFuel);
Serial.print(" Fuel%="); Serial.print(fuelPercent,1);
Serial.print(" FuelLEDs="); Serial.print(fuelOn);
Serial.print(" | TempADC="); Serial.print(avgTempADC);
Serial.print(" TempC="); Serial.print(tempC,2);
Serial.print(" Temp%="); Serial.print(tempPercent,1);
Serial.print(" TempLEDs="); Serial.println(tempOn);
delay(120);
}