Translate

Timer Countdown ESP32 Input via Browser Penampil DMD P10 (Timer ujian atau Timer Futsal)

Timer Countdown ESP32 Input via Browser Penampil DMD P10 (Timer ujian atau Timer Futsal)
 

         Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat yang dapat digunakan untuk Timer ujian sekolah atau Timer Sepak Bola seperti Futsal atau sejenisnya. alat ini pengoperasiannya sangatlah mudah yaitu hanya perlu menyambungkan wifi esp32 lalu masukkan passwordnya setelah itu buka browser lalu ketikkan IP 192.168.4.1 sehingga akan tampil interface seperti dibawah. untuk lebih jelasnya berikut adalah koding dan skemanya.
 
  
1. Skema
 
 
 
2. Interface
 

 
3. Program ESP32
 
#include <WiFi.h>
#include <WebServer.h>

#include <DMD32.h>
#include "fonts/Arial_Black_16.h"
#include "fonts/SystemFont5x7.h"

// =====================================================
// PENGATURAN P10
// =====================================================

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN   1

DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

// =====================================================
// TIMER REFRESH P10
// =====================================================

hw_timer_t *timer = NULL;

void IRAM_ATTR triggerScan()
{
  dmd.scanDisplayBySPI();
}

// =====================================================
// WIFI
// =====================================================

const char* ssid = "P10-COUNTDOWN";
const char* password = "12345678";

WebServer server(80);

// =====================================================
// COUNTDOWN
// =====================================================

long setSeconds = 60;
long remainingSeconds = 60;

bool timerRunning = false;

unsigned long lastSecond = 0;

// =====================================================
// FORMAT WAKTU
// =====================================================

String getTimeString()
{
  int menit = remainingSeconds / 60;
  int detik = remainingSeconds % 60;

  char buffer[10];

  sprintf(buffer, "%02d:%02d", menit, detik);

  return String(buffer);
}

// =====================================================
// TAMPILKAN KE P10
// =====================================================

void displayTime()
{
  String waktu = getTimeString();

  dmd.clearScreen(true);

  dmd.selectFont(SystemFont5x7);

  /*
     P10 = 32 x 16 pixel

     Arial Black 16:
     "00:00" biasanya terlalu lebar untuk
     1 panel jika menggunakan font besar.

     Posisi awal dapat disesuaikan.
  */

  int panjang = waktu.length();

  // Untuk Arial Black 16
  // posisi kira-kira di tengah
  int x = 1;
  int y = 3;

  dmd.drawString(
    x,
    y,
    waktu.c_str(),
    panjang,
    GRAPHICS_NORMAL
  );
}

// =====================================================
// HALAMAN WEB
// =====================================================

void handleRoot()
{
  String html = R"rawliteral(

<!DOCTYPE html>

<html>

<head>

<meta name="viewport"
content="width=device-width, initial-scale=1">

<title>ESP32 P10 Countdown</title>

<style>

body {
  margin: 0;
  padding: 20px;

  background: #101010;
  color: white;

  font-family: Arial;
  text-align: center;
}

.container {
  max-width: 420px;

  margin: auto;

  background: #202020;

  padding: 25px;

  border-radius: 20px;
}

h1 {
  color: #00ff66;
}

.timer {
  font-size: 55px;

  font-weight: bold;

  color: #00ff66;

  margin: 25px 0;
}

input {
  width: 90%;

  padding: 15px;

  margin: 8px 0;

  font-size: 25px;

  text-align: center;

  border: none;

  border-radius: 10px;
}

button {
  width: 90%;

  padding: 15px;

  margin: 7px;

  border: none;

  border-radius: 10px;

  font-size: 20px;

  color: white;

  cursor: pointer;
}

.set {
  background: #3498db;
}

.start {
  background: #27ae60;
}

.stop {
  background: #e67e22;
}

.reset {
  background: #e74c3c;
}

</style>

</head>

<body>

<div class="container">

<h1>ESP32 P10</h1>

<h2>COUNTDOWN TIMER</h2>

<div class="timer" id="timer">
00:00
</div>

<form action="/set" method="GET">

<input
  type="number"
  name="seconds"
  min="1"
  placeholder="Masukkan detik"
  required
>

<br>

<button class="set" type="submit">
SET WAKTU
</button>

</form>

<button class="start"
onclick="location.href='/start'">
START
</button>

<button class="stop"
onclick="location.href='/stop'">
STOP / PAUSE
</button>

<button class="reset"
onclick="location.href='/reset'">
RESET
</button>

</div>

<script>

function updateTimer()
{
  fetch('/time')
  .then(response => response.text())
  .then(data => {

    document.getElementById("timer").innerHTML = data;

  });
}

setInterval(updateTimer, 500);

updateTimer();

</script>

</body>

</html>

)rawliteral";

  server.send(200, "text/html", html);
}

// =====================================================
// SET WAKTU
// =====================================================

void handleSet()
{
  if (server.hasArg("seconds"))
  {
    long value = server.arg("seconds").toInt();

    if (value > 0)
    {
      setSeconds = value;

      remainingSeconds = value;

      timerRunning = false;

      displayTime();
    }
  }

  server.sendHeader("Location", "/");

  server.send(303);
}

// =====================================================
// START
// =====================================================

void handleStart()
{
  if (remainingSeconds > 0)
  {
    timerRunning = true;

    lastSecond = millis();
  }

  server.sendHeader("Location", "/");

  server.send(303);
}

// =====================================================
// STOP / PAUSE
// =====================================================

void handleStop()
{
  timerRunning = false;

  server.sendHeader("Location", "/");

  server.send(303);
}

// =====================================================
// RESET
// =====================================================

void handleReset()
{
  timerRunning = false;

  remainingSeconds = setSeconds;

  displayTime();

  server.sendHeader("Location", "/");

  server.send(303);
}

// =====================================================
// DATA TIMER
// =====================================================

void handleTime()
{
  server.send(
    200,
    "text/plain",
    getTimeString()
  );
}

// =====================================================
// SETUP
// =====================================================

void setup()
{
  Serial.begin(115200);
   WiFi.mode(WIFI_AP);

  WiFi.softAP(ssid, password);

  Serial.println("WiFi AP aktif");
  Serial.print("SSID: ");
  Serial.println(ssid);

  Serial.print("IP ESP32: ");
  Serial.println(WiFi.softAPIP());
  // ---------------------------------------------------
  // P10
  // ---------------------------------------------------

  dmd.clearScreen(true);

  dmd.selectFont(Arial_Black_16);

  displayTime();

  // ---------------------------------------------------
  // TIMER REFRESH DMD32
  // ---------------------------------------------------

  /*
     DMD32 versi lama menggunakan hardware timer ESP32.

     80 = prescaler

     300 = interval refresh
  */

  timer = timerBegin(0, 80, true);

  timerAttachInterrupt(
    timer,
    &triggerScan,
    true
  );

  timerAlarmWrite(
    timer,
    300,
    true
  );

  timerAlarmEnable(timer);

  // ---------------------------------------------------
  // WIFI ACCESS POINT
  // ---------------------------------------------------

  WiFi.softAP(
    ssid,
    password
  );

  Serial.println();
  Serial.println("==============================");
  Serial.println("ESP32 P10 COUNTDOWN");
  Serial.println("==============================");

  Serial.print("SSID : ");
  Serial.println(ssid);

  Serial.print("IP   : ");
  Serial.println(WiFi.softAPIP());

  // ---------------------------------------------------
  // WEB SERVER
  // ---------------------------------------------------

  server.on("/", handleRoot);

  server.on("/set", handleSet);

  server.on("/start", handleStart);

  server.on("/stop", handleStop);

  server.on("/reset", handleReset);

  server.on("/time", handleTime);

  server.begin();

  Serial.println("Webserver aktif");
}

// =====================================================
// LOOP
// =====================================================

void loop()
{
  server.handleClient();

  // ---------------------------------------------------
  // COUNTDOWN
  // ---------------------------------------------------

  if (timerRunning)
  {
    if (millis() - lastSecond >= 1000)
    {
      lastSecond += 1000;

      if (remainingSeconds > 0)
      {
        remainingSeconds--;

        displayTime();
      }

      // ------------------------------------------------
      // SELESAI
      // ------------------------------------------------

      if (remainingSeconds <= 0)
      {
        remainingSeconds = 0;

        timerRunning = false;

        displayTime();
      }
    }
  }
 
  
4. VIDEO HASILNYA
 


ESP32 Monitoring Suhu Interface DMD P10 sensor DS18B20

ESP32 Monitoring Suhu Interface DMD P10 sensor DS18B20
 

         Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat dengan menggunakan Esp32 dengan penampil dmd p10. alat ini menggunakan sensor Ds18B20 sehingga bisa mengukur suhu ruangan. untuk lebih jelasnya berikut adalah koding dan skemanya.
 
 
1. Skema
 

 
2. Program ESP32
 
 /*
GND – GND
OE – D23
A – D19
B – D21
CLK – D18
LAT – D2
DR – D23
VCC – VP
GND – EN
*/

#include <DMD32.h>  //--> DMD32 by Qudor-Engineer (KHUDHUR ALFARHAN) : https://github.com/Qudor-Engineer/DMD32
#include "fonts/SystemFont5x7.h"
#include "fonts/Arial_black_16.h"
#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 13
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

int suhu;
int suhux;
unsigned long prevTimeDMD = 0;

// Timer setup.
// create a hardware timer  of ESP32.
hw_timer_t * timer = NULL;

char lineBuff[20];
char lineBuffx[20];

//________________________________________________________________________________IRAM_ATTR triggerScan()
//  Interrupt handler for Timer1 (TimerOne) driven DMD refresh scanning,
//  this gets called at the period set in Timer1.initialize();
void IRAM_ATTR triggerScan() {
  dmd.scanDisplayBySPI();
}
//________________________________________________________________________________

//________________________________________________________________________________VOID SETUP()
void setup() {
  // put your setup code here, to run once:
  
  Serial.begin(115200);
  sensors.begin();
  Serial.println();
  delay(500);
 
  Serial.println();
  Serial.println("return the clock speed of the CPU.");
  // return the clock speed of the CPU.
  uint8_t cpuClock = ESP.getCpuFreqMHz();
  delay(500);

  Serial.println();
  Serial.println("Timer Begin");
  // Use 1st timer of 4.
  // devide cpu clock speed on its speed value by MHz to get 1us for each signal  of the timer.
  timer = timerBegin(0, cpuClock, true);
  delay(500);

  Serial.println();
  Serial.println("Attach triggerScan function to our timer.");
  // Attach triggerScan function to our timer.
  timerAttachInterrupt(timer, &triggerScan, true);
  delay(500);

  Serial.println();
  Serial.println("Set alarm to call triggerScan function.");
  // Set alarm to call triggerScan function.
  // Repeat the alarm (third parameter).
  timerAlarmWrite(timer, 300, true);
  delay(500);

  Serial.println();
  Serial.println("Start an alarm.");
  // Start an alarm.
  timerAlarmEnable(timer);
  delay(500);
  
  dmd.clearScreen(true);
  
  delay(1000);
}

void loop() {

  sensors.requestTemperatures();
  suhu = sensors.getTempCByIndex(0);
             
  sprintf(lineBuff, "SUHU   ");
  sprintf(lineBuffx, "T:%d  ", suhu);

  dmd.selectFont(SystemFont5x7);
  dmd.drawString( 0,  0, lineBuff, strlen(lineBuff), GRAPHICS_NORMAL);  
  dmd.drawString( 0,  8, lineBuffx, strlen(lineBuffx), GRAPHICS_NORMAL);   

  delay(100);
}
 
 
3. VIDEO HASILNYA
 

 

ESP32 Monitoring ADC Penampil DMD P10

ESP32 Monitoring ADC Penampil DMD P10
 

           Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat yang menggunakan ESP32 untuk menampilkan ADC ke interface DMD P10. alat ini tanpa wifi atau internet sehingga bisa dengan mudah untuk mengendalikannya. untuk lebih jelasnya berikut kodingnya.
 
 
1. Skema
 

 
2. Program Arduino IDE
 
 /*
GND – GND
OE – D23
A – D19
B – D21
CLK – D18
LAT – D2
DR – D23
VCC – VP
GND – EN
*/

#include <DMD32.h>  //--> DMD32 by Qudor-Engineer (KHUDHUR ALFARHAN) : https://github.com/Qudor-Engineer/DMD32
#include "fonts/SystemFont5x7.h"
#include "fonts/Arial_black_16.h"

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

// Timer setup.
// create a hardware timer  of ESP32.
hw_timer_t * timer = NULL;

unsigned int suhu;
unsigned int suhux;
char lineBuff[20];
char lineBuffx[20];

//________________________________________________________________________________IRAM_ATTR triggerScan()
//  Interrupt handler for Timer1 (TimerOne) driven DMD refresh scanning,
//  this gets called at the period set in Timer1.initialize();
void IRAM_ATTR triggerScan() {
  dmd.scanDisplayBySPI();
}
//________________________________________________________________________________

//________________________________________________________________________________VOID SETUP()
void setup() {
  // put your setup code here, to run once:
  
  Serial.begin(115200);
  Serial.println();
  delay(500);
 
  Serial.println();
  Serial.println("return the clock speed of the CPU.");
  // return the clock speed of the CPU.
  uint8_t cpuClock = ESP.getCpuFreqMHz();
  delay(500);

  Serial.println();
  Serial.println("Timer Begin");
  // Use 1st timer of 4.
  // devide cpu clock speed on its speed value by MHz to get 1us for each signal  of the timer.
  timer = timerBegin(0, cpuClock, true);
  delay(500);

  Serial.println();
  Serial.println("Attach triggerScan function to our timer.");
  // Attach triggerScan function to our timer.
  timerAttachInterrupt(timer, &triggerScan, true);
  delay(500);

  Serial.println();
  Serial.println("Set alarm to call triggerScan function.");
  // Set alarm to call triggerScan function.
  // Repeat the alarm (third parameter).
  timerAlarmWrite(timer, 300, true);
  delay(500);

  Serial.println();
  Serial.println("Start an alarm.");
  // Start an alarm.
  timerAlarmEnable(timer);
  delay(500);
  
  dmd.clearScreen(true);
  delay(1000);
}

void loop() {

  suhu = analogRead(34);
  suhux = analogRead(35);
             
  sprintf(lineBuff, "%d  ", suhu);
  sprintf(lineBuffx, "%d  ", suhux);

  dmd.selectFont(SystemFont5x7);
  dmd.drawString( 0,  0, lineBuff, strlen(lineBuff), GRAPHICS_NORMAL);  
  dmd.drawString( 0,  8, lineBuffx, strlen(lineBuffx), GRAPHICS_NORMAL);   

  delay(100);
}
 
 
 
3. VIDEO HASILNYA
 

 

Menampilkan Suhu Kelembaban Interface DMD P10 Sensor DHT11

Menampilkan Suhu Kelembaban Interface DMD P10 Sensor DHT11


       Pada kesempatan kali ini saya akan mnejelaskan mengenai bagaimana cara membuat sebuah alat dengan tampilan dmd p10 untuk menampilkan nilai suhu kelembaban sensor dht11. jadi alat ini cuma butuh 3 item utama yaitu ARduino uno, DMD P10 dan sensor dht11 untuk deteksi suhu kelembaban. untuk lebih jelasnya berikut kodingnya.


1. Program Arduino IDE

#include <Wire.h>
#include <SPI.h>        //SPI.h must be included as DMD is written by SPI (the IDE complains otherwise)
#include <DMD.h>        //Library DMD yang menyediakan fungsi penampilan teks, gambar dsb
#include <TimerOne.h>   //Library peripheral Timer1 untuk menjalankan prosedur pindai panel DMD
#include <Time.h>     //Library waktu yang menyediakan tipe data, struktur, dan obyek waktu
#include "DHT.h"
#include "Arial_black_16.h"
#include "Arial_Black_16_ISO_8859_1.h"
#include "Arial14.h"
#include "DejaVuSans9.h"
#include "DejaVuSansBold9.h"
#include "DejaVuSansItalic9.h"
#include "Droid_Sans_12.h"
#include "Droid_Sans_16.h"
#include "Mono5x7.h"
#include "SystemFont5x7.h"

#define DHTPIN 2
#define DHTTYPE DHT11

#define WAKTU_TAMPIL_JAM      10    //detik
#define WAKTU_TAMPIL_KALENDAR 5     //detik

#define DISPLAY_COLUMN_COUNT  2
#define DISPLAY_ROW_COUNT     1

#define PIXELS_PER_COLUMN  32
#define PIXELS_PER_ROW    16

DMD dmd(DISPLAY_COLUMN_COUNT, DISPLAY_ROW_COUNT);
unsigned char show = 0;

unsigned int suhu;
unsigned int suhux;
unsigned int dataadc;

char lineBuff[20];
char lineBuffx[20];
DHT dht(DHTPIN, DHTTYPE);
  
void ScanDMD()
{
  dmd.scanDisplayBySPI();
}

void setup(void)
{
  dht.begin();
  dmd.clearScreen( true );   //true is normal (all pixels off), false is negative (all pixels on)
  Serial.begin(9600);
   //initialize TimerOne's interrupt/CPU usage used to scan and refresh the display
  Timer1.initialize( 1000 );           //period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.
  Timer1.attachInterrupt( ScanDMD );   //attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()

  //clear/init the DMD pixels held in RAM
  dmd.clearScreen( true ); 
 
}

void loop(void)
{
       int h = dht.readHumidity();  
       int t = dht.readTemperature();
       dataadc = analogRead(A0);
     
      sprintf(lineBuff, "%d|%d", t, h);
      sprintf(lineBuffx, "%d ", dataadc);

      dmd.selectFont(DejaVuSans9);
      dmd.selectFont(Mono5x7);
      dmd.drawString( 33,  0, lineBuff, strlen(lineBuff), GRAPHICS_NORMAL);  
      dmd.drawString( 33,  8, lineBuffx, strlen(lineBuffx), GRAPHICS_NORMAL);  


delay(1000);
}



2, Skema PIN 

a. Sensor dht11 : VCC ke 5v, GND ke GND , OUT ke pin 2 Arduino
b. P10 ke arduino seperti berikut.
 
 GND - GND
 PIN 6 - A
 PIN 7 - B
 PIN 8 - SCLK
 PIN 9 - OE
 PIN 11 - R
 PIN 13 - CLK




Running Text ESP32 Input Via WIFI HOTSPOT

Running Text ESP32 Input Via WIFI HOTSPOT
 

         Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat yang bisa digunakan untuk input text via wifi handphone. alat ini menggunakan wifi untuk input textnya sehingga meudahkan user dalam penggunaannya. untuk lebih jelasnya berikut adalah koding dan skemanya. jika ingin pesan modul esp32 yang sudah di program dan tinggal pakai bisa cek di link shopee berikut.  

 LINK PEMBELIAN SHOPEE : https://id.shp.ee/eVqaTRRi


 
1. Skema 
 

 
2. Program Arduino IDE
 
 /*
GND – GND
OE – D23
A – D19
B – D21
CLK – D18
LAT – D2
DR – D23
VCC – VP
GND – EN
*/

#include <WiFi.h>
#include <WebServer.h>
#include <DMD32.h>
#include "fonts/SystemFont5x7.h"
#include "fonts/Arial_black_16.h"
#include "PageIndex.h" //--> Include the contents of the User Interface Web page, stored in the same folder as the .ino file
#include <Preferences.h>
//----------------------------------------

//----------------------------------------Defining the key.
// "Key" functions like a password. In order to change the text on the P10, the user must know the "key".
// You can change it to another word.
#define key_Txt "123456789"
//----------------------------------------

// Fire up the DMD library as dmd.
#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

// Timer setup.
// create a hardware timer  of ESP32
hw_timer_t * timer = NULL;

const char* ssid = "ESP32_WS";  //--> access point name
const char* password = "123456789"; //--> access point password

IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
//----------------------------------------

String display_Modes = "";
String single_Row_Txt = "";
String double_Row_First_Txt = "";
int double_Row_First_Txt_Pos = 0;
String double_Row_Second_Txt = "";

// Server on port 80.
WebServer server(80);  

// Initialize Preferences.
Preferences preferences;

//________________________________________________________________________________IRAM_ATTR triggerScan()
// Interrupt handler for Timer1 (TimerOne) driven DMD refresh scanning, 
// this gets called at the period set in Timer1.initialize();
void IRAM_ATTR triggerScan() {
  dmd.scanDisplayBySPI();
}
//________________________________________________________________________________

//________________________________________________________________________________handleRoot()
// This routine is executed when you open ESP32 IP Address in browser.
void handleRoot() {
  server.send(200, "text/html", MAIN_page); //Send web page
}
//________________________________________________________________________________

//________________________________________________________________________________handleSettings().
// Subroutine to handle settings. The displayed text and others are set here.
void handleSettings() {
  timerAlarmDisable(timer);
  delay(1000);
  
  String incoming_Settings = server.arg("Settings");
  Serial.println();
  Serial.print("Incoming settings : ");
  Serial.println(incoming_Settings);
  
  // Example of incoming data from a client in double row display mode : 
  // "p10esp32wb,DR,ESP32,1,ESP32 P10 LED Display"
  // - p10esp32wb             = key.
  // - DR                     = Double Row (Two row display mode).
  // - ESP32                  = Text for the first row.
  // - 1                      = Position of text for first row.
  // - ESP32 P10 LED Display  = Text for the second row.
  //
  // - When using the "getValue" string function, the sequence is:
  //   "p10esp32wb,DR,ESP32,1,ESP32 P10 LED Display"
  //        |       |   |   |           |
  //        0       1   2   3           4
  //
  //   > p10esp32wb = getValue(incoming_Settings, ',', 0);
  //   > DR         = getValue(incoming_Settings, ',', 1);
  //   > and so on.

  if (getValue(incoming_Settings, ',', 0) == key_Txt) {
    display_Modes = getValue(incoming_Settings, ',', 1);

    if (display_Modes == "SR") {
      single_Row_Txt = getValue(incoming_Settings, ',', 2);
      
      // Save texts and modes to flash memory.
      preferences.begin("P10_SD", false);
      preferences.putString("DM", display_Modes);
      preferences.putString("SRT", single_Row_Txt);
      preferences.end();
      delay(500);
    }
  
    if (display_Modes == "DR") {
      double_Row_First_Txt = getValue(incoming_Settings, ',', 2);
      double_Row_First_Txt_Pos = getValue(incoming_Settings, ',', 3).toInt();
      double_Row_Second_Txt = getValue(incoming_Settings, ',', 4);
      
      // Save texts and modes to flash memory.
      preferences.begin("P10_SD", false);
      preferences.putString("DM", display_Modes);
      preferences.putString("DRFT", double_Row_First_Txt);
      preferences.putInt("DRFTP", double_Row_First_Txt_Pos);
      preferences.putString("DRST", double_Row_Second_Txt);
      preferences.end();
      delay(500);
    }

    server.send(200, "text/plane", "+OK");  //--> Sending replies to the client.
    delay(500);
  } else {
    server.send(200, "text/plane", "+ERR"); //--> Sending replies to the client.
    delay(500);
  }
  
  timerAlarmEnable(timer);
  delay(500);
}
//________________________________________________________________________________

//________________________________________________________________________________getValue()
// String function to split strings based on certain characters.
String getValue(String data, char separator, int index) {
  int found = 0;
  int strIndex[] = { 0, -1 };
  int maxIndex = data.length() - 1;
  
  for (int i = 0; i <= maxIndex && found <= index; i++) {
    if (data.charAt(i) == separator || i == maxIndex) {
      found++;
      strIndex[0] = strIndex[1] + 1;
      strIndex[1] = (i == maxIndex) ? i+1 : i;
    }
  }
  return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}
//________________________________________________________________________________ 

//________________________________________________________________________________Single_Row_Display_Mode()
// Subroutine for displaying "running text" on P10 in Single Row mode.
void Single_Row_Display_Mode() {
  char CA_single_Row_Txt[single_Row_Txt.length() + 1];
  single_Row_Txt.toCharArray(CA_single_Row_Txt, single_Row_Txt.length() + 1);

  dmd.clearScreen(true);
  dmd.selectFont(Arial_Black_16);
  dmd.drawMarquee(CA_single_Row_Txt, single_Row_Txt.length(), (32*DISPLAYS_ACROSS)-1, 0);
  long start=millis();
  long timer=start;
  boolean ret=false;
  while(!ret){
   if ((timer+30) < millis()) {
     ret=dmd.stepMarquee(-1,0);
     timer=millis();
   }
  }
  delay(1000);
}
//________________________________________________________________________________ 

//________________________________________________________________________________Double_Row_Display_Mode()
// Subroutine to display text in the first row and display "running text" in the second row in Double Row mode.
void Double_Row_Display_Mode() {
  char CA_double_Row_First_Txt[double_Row_First_Txt.length() + 1];
  double_Row_First_Txt.toCharArray(CA_double_Row_First_Txt, double_Row_First_Txt.length() + 1);

  char CA_double_Row_Second_Txt[double_Row_Second_Txt.length() + 1];
  double_Row_Second_Txt.toCharArray(CA_double_Row_Second_Txt, double_Row_Second_Txt.length() + 1);

  dmd.clearScreen(true);
  dmd.selectFont(SystemFont5x7);
  dmd.drawString(double_Row_First_Txt_Pos, 0, CA_double_Row_First_Txt, double_Row_First_Txt.length(), GRAPHICS_NORMAL);
  
  int scrl_long = (double_Row_Second_Txt.length()*6) + (32*DISPLAYS_ACROSS);
  int i = 32*DISPLAYS_ACROSS;
  long start=millis();
  long timer=start;
  while(true){
    if ((timer+30) < millis()) {
      dmd.drawString(i, 9, CA_double_Row_Second_Txt, double_Row_Second_Txt.length(), GRAPHICS_NORMAL);    
      if (i > ~scrl_long) {
        i--;
      } else {
        break;
      }
      timer=millis();
    }
  }
}
//________________________________________________________________________________ 

//________________________________________________________________________________VOID SETUP()
void setup(void){
  // put your setup code here, to run once:
  
  Serial.begin(115200);
  delay(1000);
  
  Serial.println();

  display_Modes.reserve(5);
  single_Row_Txt.reserve(50);
  delay(500);

  //----------------------------------------Load data stored in flash memory.
  Serial.println("Load data stored in flash memory.");
  preferences.begin("P10_SD", false);
  
  display_Modes = preferences.getString("DM", "");
  single_Row_Txt = preferences.getString("SRT", "");
  double_Row_First_Txt = preferences.getString("DRFT", "");
  double_Row_First_Txt_Pos = preferences.getInt("DRFTP", 0);
  double_Row_Second_Txt = preferences.getString("DRST", "");

  Serial.print("display_Modes : ");
  Serial.println(display_Modes);
  Serial.print("single_Row_Txt : ");
  Serial.println(single_Row_Txt);
  Serial.print("double_Row_First_Txt : ");
  Serial.println(double_Row_First_Txt);
  Serial.print("double_Row_First_Txt_Pos : ");
  Serial.println(double_Row_First_Txt_Pos);
  Serial.print("double_Row_Second_Txt : ");
  Serial.println(double_Row_Second_Txt);

  preferences.end();
  delay(500);
  //----------------------------------------
  
  Serial.println();
  Serial.println("return the clock speed of the CPU.");
  // return the clock speed of the CPU.
  uint8_t cpuClock = ESP.getCpuFreqMHz();
  delay(500);

  Serial.println();
  Serial.println("Timer Begin");
  // Use 1st timer of 4.
  // devide cpu clock speed on its speed value by MHz to get 1us for each signal  of the timer.
  timer = timerBegin(0, cpuClock, true);
  delay(500);

  Serial.println();
  Serial.println("Attach triggerScan function to our timer.");
  // Attach triggerScan function to our timer.
  timerAttachInterrupt(timer, &triggerScan, true);
  delay(500);

  Serial.println();
  Serial.println("Set alarm to call triggerScan function.");
  // Set alarm to call triggerScan function.
  // Repeat the alarm (third parameter).
  timerAlarmWrite(timer, 300, true);
  delay(500);

  Serial.println();
  Serial.println("Start an alarm.");
  // Start an alarm.
  timerAlarmEnable(timer);
  delay(500);

  Serial.println();
  Serial.println("Chose the \"Arial_Black_16\" font.");
  dmd.selectFont(Arial_Black_16);

  Serial.println();
  Serial.println("Clear Screen.");
  // clear/init the DMD pixels held in RAM.
  // true is normal (all pixels off), false is negative (all pixels on).
  dmd.clearScreen(true); 
  delay(500);

  // While the process of connecting to a WiFi network is in progress or when the process of creating an access point is in progress, 
  // the "Alarm Timer" must be disabled.
  timerAlarmDisable(timer);
  delay(1000);

  //----------------------------------------Create ESP32 as Access Point.
Serial.println();
Serial.println("WIFI mode : AP");
WiFi.mode(WIFI_AP);
Serial.println("Setting up ESP32 to be an Access Point.");
WiFi.softAP(ssid, password); //--> Creating Access Points
delay(1000);
Serial.println("Setting up ESP32 softAPConfig.");
WiFi.softAPConfig(local_ip, gateway, subnet);
//----------------------------------------
 
  server.on("/", handleRoot); 
  server.on("/setText", handleSettings);

  // Start server.
  server.begin(); 
  Serial.println();
  Serial.println("HTTP server started");

  delay(500);

  // When successfully connected to a WiFi network or when the access point creation process is complete, 
  // the "Alarm Timer" is re-enabled.
  timerAlarmEnable(timer);
  delay(500);

  Serial.println();
  Serial.print("SSID name : ");
  Serial.println(ssid);
  Serial.print("IP address : ");
  Serial.println(WiFi.softAPIP());
  Serial.println();
  Serial.println("Connect your computer or mobile Wifi to the SSID above.");
  Serial.println("Visit the IP Address above in your browser to open the main page.");
  Serial.println();
  delay(500);

}
//________________________________________________________________________________

//________________________________________________________________________________VOID LOOP()
void loop(void){
  // put your main code here, to run repeatedly:

  // Handle client requests.
  server.handleClient();  

  if (display_Modes == "SR") {
    Single_Row_Display_Mode();
  }

  if (display_Modes == "DR") {
    Double_Row_Display_Mode();
  }
}
//________________________________________________________________________________
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 
 
 
3. PageIndex.h
 
const char MAIN_page[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
  <style>
    h1 { font-size: 2.0rem; color:#2980b9;}

    input[type=text], select {
      width: 100%;
      padding: 8px 8px;
      margin: 8px 0;
      display: inline-block;
      border: 1px solid #ccc;
      border-radius: 4px;
      box-sizing: border-box;
    }

    .div_Form {
      margin: auto;
      width: 70%;
      border-radius: 5px;
      background-color: #f2f2f2;
      padding: 10px 10px;
    }

    .buttonON {
      display: inline-block;
      padding: 5px 25px;
      font-size: 13px;
      cursor: pointer;
      text-align: center;
      text-decoration: none;
      outline: none;
      color: #fff;
      background-color: #4CAF50;
      border: none;
      border-radius: 8px;
      box-shadow: 0 3px #999;
    }
    .buttonON:hover {background-color: #3e8e41}
    .buttonON:active {
      background-color: #3e8e41;
      box-shadow: 0 1px #666;
      transform: translateY(2px);
    }
    .buttonON:disabled {
      background-color: #666;
      box-shadow: 0 1px #666;
      transform: translateY(2px);
    }
  </style>
  
  <body>
    <div style="text-align: center;">
      <h1>ESP32 P10 Module Web Server</h1>
    </div>

    <div class="div_Form">
      <form>
        <label for="Keys_TXT">Key :</label>
        <input type="password" id="Keys_TXT" name="Keys_TXT" placeholder="Enter key here...">
        
        <br>
        <hr style="border: 1px solid #e6e6e6;">
        
        <input type="checkbox" id="cb_SingleRow" name="cb_SingleRow" value="" onclick="CB_SR_Click()">
        <label for="cb_SingleRow"> Single Row</label><br><br>
        
        <label for="SingleRow_TXT">Text :</label>
        <input type="text" id="SingleRow_TXT" name="SingleRow_TXT" placeholder="Enter text here...">
        
        <br>
        <hr style="border: 1px solid #e6e6e6;">
        
        <input type="checkbox" id="cb_DoubleRow" name="cb_DoubleRow" value="" onclick="CB_DR_Click()">
        <label for="cb_DoubleRow"> Double Row</label><br><br>
      
        <label for="DoubleRow_FirtsRow_TXT">Text for First Row :</label>
        <input type="text" id="DoubleRow_FirtsRow_TXT" name="DoubleRow_FirtsRow_TXT" placeholder="Enter text for the first row here...">
        
        <label for="DoubleRow_PositionFirstRow_TXT">Text Position for First Row :</label>
        <input type="text" id="DoubleRow_PositionFirstRow_TXT" name="DoubleRow_PositionFirstRow_TXT" oninput="this.value = this.value.replace(/[^0-9.]/g, '').replace(/(\..*?)\..*/g, '$1');" placeholder="Enter a value for the text position in the first row here...">

        <label for="DoubleRow_SecondRow_TXT">Text for Second Row :</label>
        <input type="text" id="DoubleRow_SecondRow_TXT" name="DoubleRow_SecondRow_TXT" placeholder="Enter text for the second row here...">
        
        <br>
        <hr style="border: 1px solid #e6e6e6;">
        
        <button type="button" class="buttonON" id="BTN_Submit" onclick="BTN_Submit_Click()">Submit</button>
        <label style="padding: 15px; color: black;" id="LBL_Info"></label>
      </form>
    </div>

    <script>
      document.getElementById("cb_SingleRow").checked = false;
      document.getElementById("SingleRow_TXT").disabled = true;
      
      document.getElementById("cb_DoubleRow").checked = false;
      document.getElementById("DoubleRow_FirtsRow_TXT").disabled = true;
      document.getElementById("DoubleRow_PositionFirstRow_TXT").disabled = true;
      document.getElementById("DoubleRow_SecondRow_TXT").disabled = true;
      
      function CB_SR_Click() {
        var checkBox = document.getElementById("cb_SingleRow");
        if (checkBox.checked == true){
          document.getElementById("cb_DoubleRow").disabled = true;
          document.getElementById("SingleRow_TXT").disabled = false;
        } else {
          document.getElementById("cb_DoubleRow").disabled = false;
          document.getElementById("SingleRow_TXT").disabled = true;
        }
      }
      
      function CB_DR_Click() {
        var checkBox = document.getElementById("cb_DoubleRow");
        if (checkBox.checked == true){
          document.getElementById("cb_SingleRow").disabled = true;
          document.getElementById("DoubleRow_FirtsRow_TXT").disabled = false;
          document.getElementById("DoubleRow_PositionFirstRow_TXT").disabled = false;
          document.getElementById("DoubleRow_SecondRow_TXT").disabled = false;
        } else {
          document.getElementById("cb_SingleRow").disabled = false;
          document.getElementById("DoubleRow_FirtsRow_TXT").disabled = true;
          document.getElementById("DoubleRow_PositionFirstRow_TXT").disabled = true;
          document.getElementById("DoubleRow_SecondRow_TXT").disabled = true;
        }
      }
      
      function BTN_Submit_Click() {
        var checkBox_SR = document.getElementById("cb_SingleRow");
        var checkBox_DR = document.getElementById("cb_DoubleRow");
        var key_TXT = document.getElementById("Keys_TXT").value;
        
        if (checkBox_SR.checked == false && checkBox_DR.checked == false) {
          alert("Error ! \rThe Single Row or Double Row checkbox must be checked.");
          return;
        }

        if (key_TXT == "") {
          alert("Error ! \rThe key cannot be empty.");
          return;
        }

        document.getElementById("BTN_Submit").disabled = true;
        document.getElementById("LBL_Info").style.color = "black";
        document.getElementById("LBL_Info").innerHTML = "Please wait...";
        
        if (checkBox_SR.checked == true) {
          var SR_TXT = document.getElementById("SingleRow_TXT").value;
          var msg = key_TXT + ",SR," + SR_TXT;
          Send(msg);
        }
        
        if (checkBox_DR.checked == true) {
          var DR_FR_TXT = document.getElementById("DoubleRow_FirtsRow_TXT").value;
          var DR_PFR_TXT = document.getElementById("DoubleRow_PositionFirstRow_TXT").value;
          var DR_SR_TXT = document.getElementById("DoubleRow_SecondRow_TXT").value;
          var msg = key_TXT + ",DR," + DR_FR_TXT + "," + DR_PFR_TXT + "," + DR_SR_TXT;
          Send(msg);
        }
      }
      
      function Send(x) {
        //alert(x);
        if (window.XMLHttpRequest) {
          // code for IE7+, Firefox, Chrome, Opera, Safari
          xmlhttp = new XMLHttpRequest();
        } else {
          // code for IE6, IE5
          xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        xmlhttp.onreadystatechange = function() {
          if (this.readyState == 4 && this.status == 200) {
            if (this.responseText == "+OK") {
              document.getElementById("BTN_Submit").disabled = false;
              document.getElementById("LBL_Info").innerHTML = "";
            }
            if (this.responseText == "+ERR") {
              document.getElementById("BTN_Submit").disabled = false;
              document.getElementById("LBL_Info").innerHTML = "Keys are wrong !";
              document.getElementById("LBL_Info").style.color = "red";
            }
          }
        }
        xmlhttp.open("GET","setText?Settings="+x,true);
        xmlhttp.send();
      }
    </script>
  </body>
</html>
)=====";
 
 
4. INTERFACE
 

 
5. VIDEO HASILNYA
 

 

Running Text ESP32 DMD P10 Hub 12

Running Text ESP32 DMD P10 Hub 12 
 
        
           Pada kesempatan kali ini saya akan menjelaskan mengenai bagaimana cara membuat sebuah alat dengan menampilkan text running dengan menggunakan ESP32. jadi nanti text yang tampil  ada 2 jenis yaitu single text dan variasi. untuk single text ada di koding pertama dan untuk variasi ada di koding ke dua. berikut adalah skema dan kodingnya.
 

 
 
1. Skema
        
       
2. Program Single Text
 
/*
GND – GND
OE – D23
A – D19
B – D21
CLK – D18
LAT – D2
DR – D23
VCC – VP
GND – EN
*/

#include <DMD32.h>
#include "fonts/SystemFont5x7.h"
#include "fonts/Arial_black_16.h"

#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

hw_timer_t* timer = NULL;

void IRAM_ATTR triggerScan() {
  dmd.scanDisplayBySPI();
}

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println();
  Serial.println("return the clock speed of the CPU.");
  uint8_t cpuClock = ESP.getCpuFreqMHz();
  delay(500);
  Serial.println();
  Serial.println("Timer Begin");
  timer = timerBegin(0, cpuClock, true);
  delay(500);
  Serial.println();
  Serial.println("Attach triggerScan function to our timer.");
  timerAttachInterrupt(timer, &triggerScan, true);
  delay(500);
  Serial.println();
  Serial.println("Set alarm to call triggerScan function.");
  timerAlarmWrite(timer, 100, true);
  delay(500);
  Serial.println();
  Serial.println("Start an alarm.");
  timerAlarmEnable(timer);
  delay(500);
}

void loop() {
  dmd.selectFont(Arial_Black_16);
  String txt_1 = "COBA DMD P10";
  char char_array_txt_1[txt_1.length() + 1];
  txt_1.toCharArray(char_array_txt_1, txt_1.length() + 1);
  dmd.clearScreen(true);
  delay(1000);
  dmd.drawMarquee(char_array_txt_1, txt_1.length(), (32 * DISPLAYS_ACROSS) - 1, 0);
  long timer_1 = millis();
  boolean ret = false;
  while (!ret) {
    if ((timer_1 + 50) < millis()) {
      ret = dmd.stepMarquee(-1, 0);
      timer_1 = millis();
    }
  }
 
 
 
3. Program Variasi Text 
 
/*
GND – GND
OE – D23
A – D19
B – D21
CLK – D18
LAT – D2
DR – D23
VCC – VP
GND – EN
*/

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 01_ESP32_P10_Test_Displays
//----------------------------------------Including the libraries.
#include <DMD32.h>  //--> DMD32 by Qudor-Engineer (KHUDHUR ALFARHAN) : https://github.com/Qudor-Engineer/DMD32
#include "fonts/SystemFont5x7.h"
#include "fonts/Arial_black_16.h"
//----------------------------------------

// Fire up the DMD library as dmd.
#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);

// Timer setup.
// create a hardware timer  of ESP32.
hw_timer_t * timer = NULL;

//________________________________________________________________________________IRAM_ATTR triggerScan()
//  Interrupt handler for Timer1 (TimerOne) driven DMD refresh scanning,
//  this gets called at the period set in Timer1.initialize();
void IRAM_ATTR triggerScan() {
  dmd.scanDisplayBySPI();
}
//________________________________________________________________________________

//________________________________________________________________________________VOID SETUP()
void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  Serial.println();

  delay(500);

  Serial.println();
  Serial.println("return the clock speed of the CPU.");
  // return the clock speed of the CPU.
  uint8_t cpuClock = ESP.getCpuFreqMHz();
  delay(500);

  Serial.println();
  Serial.println("Timer Begin");
  // Use 1st timer of 4.
  // devide cpu clock speed on its speed value by MHz to get 1us for each signal  of the timer.
  timer = timerBegin(0, cpuClock, true);
  delay(500);

  Serial.println();
  Serial.println("Attach triggerScan function to our timer.");
  // Attach triggerScan function to our timer.
  timerAttachInterrupt(timer, &triggerScan, true);
  delay(500);

  Serial.println();
  Serial.println("Set alarm to call triggerScan function.");
  // Set alarm to call triggerScan function.
  // Repeat the alarm (third parameter).
  timerAlarmWrite(timer, 300, true);
  delay(500);

  Serial.println();
  Serial.println("Start an alarm.");
  // Start an alarm.
  timerAlarmEnable(timer);
  delay(500);
}
//________________________________________________________________________________

//________________________________________________________________________________VOID LOOP()
void loop() {
  // put your main code here, to run repeatedly:

  // If you want to change the font, don't forget to use this line of code : dmd.selectFont . Example: dmd.selectFont(Arial_Black_16);

  //----------------------------------------Demo with "Arial_Black_16" font.
  dmd.selectFont(Arial_Black_16);

  //.................Running Text.
  String txt_1 = "ESP32 with P10 LED Display";
  char char_array_txt_1[txt_1.length() + 1];
  txt_1.toCharArray(char_array_txt_1, txt_1.length() + 1);

  dmd.clearScreen(true);
  delay(1000);
  dmd.drawMarquee(char_array_txt_1,txt_1.length(),(32*DISPLAYS_ACROSS)-1,0);
  long start_1=millis();
  long timer_1=start_1;
  boolean ret=false;
  while(!ret){
   if ((timer_1+30) < millis()) {
     ret=dmd.stepMarquee(-1,0);
     timer_1=millis();
   }
  }
  //.................

  //.................Display Text.
  dmd.clearScreen(true);
  delay(1000);
  dmd.drawString(0,0,"DMD", 3, GRAPHICS_NORMAL);  //--> dmd.drawString(x, y, Text, Number of characters in text, GRAPHICS_NORMAL);
  delay(3000);
  //.................
  //----------------------------------------

  //----------------------------------------Demo with "SystemFont5x7" font.
  // If you use the font "SystemFont5x7", then 1 panel P10 (32x16) can display text in 2 rows.
  dmd.selectFont(SystemFont5x7);

  //.................Display Text.
  dmd.clearScreen(true);
  delay(1000);
  dmd.drawString(0,0,"ESP32", 5, GRAPHICS_NORMAL);
  dmd.drawString(0,9,"P10", 3, GRAPHICS_NORMAL);
  delay(3000);
  //.................

  //.................The first row displays text and the second row displays running text.
  String txt_2 = "ESP32 with P10 LED Display";
  char char_array_txt_2[txt_2.length() + 1];
  txt_2.toCharArray(char_array_txt_2, txt_2.length() + 1);
  int scrl_long = (txt_2.length()*6) + (32*DISPLAYS_ACROSS);

  dmd.clearScreen(true);
  delay(1000);
  
  // Displays text in the first row.
  dmd.drawString(4,0,"COBA", 4, GRAPHICS_NORMAL);
  
  long start_2=millis();
  long timer_2=start_2;
  int i = 32*DISPLAYS_ACROSS;
  while(true){
    if ((timer_2+30) < millis()) {
      // Displays running text on the second row.
      dmd.drawString(i, 9, char_array_txt_2, txt_2.length(), GRAPHICS_NORMAL);
      
      if (i > ~scrl_long) {
        i--;
      } else {
        break;
      }
    
      timer_2=millis();
    }
  }
  //.................

  //.................Displays Text and Numbers.
  int T = 29;
  int H = 73;
  char char_array_T[String(T).length() + 1];
  char char_array_H[String(H).length() + 1];
  String(T).toCharArray(char_array_T, String(T).length() + 1);
  String(H).toCharArray(char_array_H, String(H).length() + 1);

  dmd.clearScreen(true);
  delay(1000);
  
  dmd.drawString(0, 0, "T:", 2, GRAPHICS_NORMAL);
  dmd.drawString(11, 0, char_array_T, String(T).length(), GRAPHICS_NORMAL);
  dmd.drawCircle(24, 1, 1, GRAPHICS_NORMAL);
  dmd.drawString(27, 0, "C", 1, GRAPHICS_NORMAL);
  
  dmd.drawString(0, 9, "H:", 2, GRAPHICS_NORMAL);
  dmd.drawString(11, 9, char_array_H, String(H).length(), GRAPHICS_NORMAL);
  dmd.drawString(27, 9, "%", 1, GRAPHICS_NORMAL);

  delay(3000);
  //.................
  //----------------------------------------
}
//________________________________________________________________________________
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 
 
 
4. VIDEO HASILNYA
 

 

Jual Timer Lomba Lari Wireless Start dan Sensor Photoelectric (Mode Single dan Battle)

Jual Timer Lomba Lari Wireless Start dan Sensor Photoelectric (Mode Single dan Battle)
 

       Pada kesempatan kali ini saya akan menjelaskan mengenai produk kami yang berfungsi untuk mengukur waktu atau timer lomba lari / sepatu roda / sejenisnya. alat ini menggunakan remot untuk startnya dan untuk finish menggunakan sensor photoelectic. fitur2nya yaitu sebagai berikut.
 
LINK TOKO SHOPEE:  https://id.shp.ee/Dxgj3kYh
 
Fitur Single (satu jalur) :
1. Jarak sensor finish 1- 4 meter
2. Menggunakan 1 kontroler dan 1 reflektor 
3. Menggunakan battery 12v sehingga bisa portable
4. Jarak Remote kurang lebih 1 - 10 meter (mungkin 20 meter) open area
5. Menggunakan tripod untuk penyangganya. 
 

 
 
Fitur Battle (dua jalur) :
1. Jarak sensor finish 1- 4 meter
2. Menggunakan 2 kontroler dan 2 reflektor 
3. Menggunakan battery 12v sehingga bisa portable
4. Jarak Remote kurang lebih 1 - 10 meter (mungkin 20 meter) open area
5. Menggunakan tripod untuk penyangganya. 
 

 
Jika Ingin pesan bisa melalui toko Shopee atau WA admin: 085726496643 (Yanuar)