Traffic lights (UK sequence) dengan arduino

Urutan traffic lights standar Inggris mengandung arti sebagai berikut :

  1. Lampu merah,
    Setiap kendaraan wajib berhenti tidak lebih dari garis batas
  2. Lampu merah dan lampu kuning,
    Setiap kendaraan wajib berhenti namun bersiap untuk melanjutkan perjalanan
  3. Lampu hijau,
    Boleh melintasi traffic lights
  4. Lampu kuning,
    Bersiap untuk berhenti di belakang garis batas, tetapi jika sudah melewati garis batas masih di perbolehkan jalan.

Jumlah keseluruhan lampu yang dibutuhkan untuk 4 simpang adalah 12 buah, untuk menghemat jalur pin arduino, maka bisa diterapkan sistem scan yang menggunakan 3 jalur lampu, (untuk merah, kuning, hijau), serta 4 jalur untuk masing-masing posisi trafic led, sehingga untuk 4 simpang diperlukan 7 jalur pengontrolan saja.

skema traffic lights berbasis arduino:

program Urutan traffic lights menggunakan arduino:

#define jumlahTrafficLight    4

byte pinDigit[] = {7, 6, 5, 4};
byte pinRYG[] = {10, 9, 8};

#define lampuMerah    (1 << 2)
#define lampuKuning   (1 << 1)
#define lampuHijau    (1 << 0)

byte hijauSekarang;
byte hijauSelanjutnya;
byte timingTraffic;

struct traffic {
  byte lampu;
  byte lampuSelanjutnya;
  uint32_t waktu;
};

traffic skemaTraffic[] = {
  {lampuHijau  , lampuMerah               , 5000},
  {lampuKuning , lampuMerah               , 1000},
  {lampuMerah  , lampuMerah | lampuKuning , 1000},
};

byte lampuTraffic[jumlahTrafficLight];
byte indexTraffic;
byte indexScan;
uint32_t millisTraffic;
byte jalurDarurat;

void setup() {

  for (byte i = 0; i < jumlahTrafficLight; i++)
  {
    pinMode(pinDigit[i], OUTPUT);
  }
  for (byte i = 0; i < 3; i++)
  {
    pinMode(pinRYG[i], OUTPUT);
  }

  Serial.begin(9600);

  for (byte i = 0 ; i < jumlahTrafficLight; i++)
  {
    lampuTraffic[i] = skemaTraffic[2].lampu;
  }
  hijauSekarang = 0;
  hijauSelanjutnya = 1;

}

void loop() {

  if (millisTraffic < millis())
  {
    lampuTraffic[hijauSekarang] = skemaTraffic[indexTraffic].lampu;
    lampuTraffic[hijauSelanjutnya] = skemaTraffic[indexTraffic].lampuSelanjutnya;
    millisTraffic += skemaTraffic[indexTraffic].waktu;

    indexTraffic++;
    if (indexTraffic == 3)
    {
      indexTraffic = 0;
      hijauSekarang = hijauSelanjutnya;
      hijauSelanjutnya = (hijauSekarang + 1) % jumlahTrafficLight;
    }
  }

  trafficScan();
}

void trafficScan()
{
  digitalWrite(pinDigit[indexScan], HIGH);
  indexScan++;
  indexScan %= jumlahTrafficLight;
  digitalWrite(pinRYG[0], lampuTraffic[indexScan] & lampuMerah);
  digitalWrite(pinRYG[1], lampuTraffic[indexScan] & lampuKuning);
  digitalWrite(pinRYG[2], lampuTraffic[indexScan] & lampuHijau);
  digitalWrite(pinDigit[indexScan], LOW);

}

 

 

SPWM fungsional tanpa tabel-sinusiodal dengan arduino

Sinusoidal Pulse width modulation (SPWM) adalah teknik membangkitkan sinyal sinusiodal dengan metode PWM (pulse width modulation). Sinyal PWM umum di gunakan pada perangkat digital,  sehingga memungkinkan perangkat digital untuk menghasilkan sinyal sinusiodal.

Metode SPWM yang umum digunakan diantaranya :

  1. Segitiga yaitu dengan membandingkan sinyal ramp dengan sinyal sinusiodal dan diperoleh deret duty cycle yang bisa di masukkan dalam tabel.
  2. delta, deret pwm dihasilkan melalui penggambaran slope dalam batas (atas dan bawah) sinyal sinusiodal target.
  3. delta sigma
  4. space vector
  5. direct torque
  6. time proportioning

kehandalan S-PWM di ukur berdasarkan spectrum, karena SPWM adalah sinyal digital yang menyerupai sinyal sinus, maka membutuhkann filter low-pass untuk mengurangi spectrum frekusnsi tinggi dari switching digital.

SPWM juga bisa digunakan sebagai regulator AC untuk keperluan pengontrolan tegangan output.

berikut tata letak pin yang digunakan :

H-bridge dalam skema adalah BTS7960, bisa di ganti dengan modul/rangkaian H-bridge lain

 

sketch program arduino spwm:

#define pinSPWM_A               9
#define pinSPWM_B               10

#define frekuensiOutput         50
#define frekuensiSPWM           1000

volatile byte indexSPWM;
volatile float output;
int limitOutput;

void setup() {
  pinMode(pinSPWM_A, OUTPUT);
  pinMode(pinSPWM_B, OUTPUT);

  Serial.begin(9600);
  Serial.println(F("SPWM fungsional dengan arduino"));
  Serial.println(F("https://www.semesin.com/project"));
  Serial.println();

  TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11);

  long cycles = (F_CPU / frekuensiSPWM / 2);
  uint8_t clockSelectBits;

  if      (cycles        < 0xFF) clockSelectBits = _BV(CS10);
  else if ((cycles >>= 3) < 0xFF) clockSelectBits = _BV(CS11);
  else if ((cycles >>= 3) < 0xFF) clockSelectBits = _BV(CS11) | _BV(CS10);
  else if ((cycles >>= 2) < 0xFF) clockSelectBits = _BV(CS12);
  else if ((cycles >>= 2) < 0xFF) clockSelectBits = _BV(CS12) | _BV(CS10);
  else     cycles = 0xFF - 1,    clockSelectBits = _BV(CS12) | _BV(CS10);

  TCCR1B = (1 << WGM13) | clockSelectBits;
  ICR1 = cycles;
  TIMSK1 = _BV(TOIE1);
  OCR1A = 0;
  OCR1B = 0;

  limitOutput = cycles;
}

void loop() {

  output = limitOutput / 2;

}

ISR(TIMER1_OVF_vect)
{
  int duty = 1.0 * sin(2.0 * PI * indexSPWM / (frekuensiSPWM / frekuensiOutput)) * output;

  if (duty >= 0)
  {
    OCR1AL = duty;
    OCR1BL = limitOutput + 1;
  }
  else
  {
    OCR1AL = limitOutput + 1;
    OCR1BL = 0 - duty;
  }

  indexSPWM++;
  indexSPWM %= frekuensiSPWM / frekuensiOutput;
}

output logic analyzer

Meningkatkan presisi pembacaan sensor analog dengan metode Trimmed Mean

Sensor-sensor analog dibaca oleh arduino melalui pin analog menggunakan adc. Agar pembacaan adc oleh arduino memiliki presisi yang baik diperlukan perlakuan (treatment) sisi hardware  diantaranya:

  1. Jika impedansi keluaran (output) sensor besar (>10 kΩ) maka perbesar sampling and hold time (s/h) dengan cara mengganti nilai ADCSRA,
  2. Gunakan tegangan referensi yang sesuai dengan tegangan maksimal keluaran sensor menggunakan perintah analogReference(),
  3. Gunakan kabel sependek mungkin antara pin output sensor dan pin adc arduino dan terselubung ground,
  4. Jauhkan kabel dan sensor dari komponen yang bisa menimbulkan interferensi elektromagnetik,
  5. Jika menggunakan tegangan referensi luar, tambakan dengan filter LC,
  6. Pin-pin analog yang tidak digunakan sebaiknya tidak dioperasikan saat proses pembacaan sensor.
  7. Tegangan power supply yang stabil dan terproteksi dari gangguan luar.

Pada sisi software bisa dilakukan peningkatan presisi diataranya menggunakan metode rata-rata seperti Mean, Trimmed Mean, Truncated mean, Winsorizing, Interquartile mean dan sebagainya.

Berikut contoh penggunaan metode Trimmed Mean untuk meningkatkan presisi pembacaan sensor analog :

#define faktorTrim        0.2 //20%
#define jumlahSampel      10

#define pinSensor         A0

int buffer[10], temp, rataRata;

void setup() {
  Serial.begin(9600);
  Serial.println(F("Meningkatkan presisi pembacaan sensor analog dengan metode Trimmed Mean"));
  Serial.println(F("https://www.semesin.com/project"));
  Serial.println();
}

void loop() {
  for (int i = 0; i < jumlahSampel; i++)
  {
    buffer[i] = analogRead(pinSensor);
  }

  //proses pengurutan (sorting)
  for (int i = 0; i < jumlahSampel - 1; i++)
  {
    for (int j = i + 1; j < jumlahSampel; j++)
    {
      if (buffer[i] > buffer[j])
      {
        temp = buffer[i];
        buffer[i] = buffer[j];
        buffer[j] = temp;
      }
    }
  }

  //rata-rata (trimmed mean)
  rataRata = 0;
  for (int i = jumlahSampel * faktorTrim; i < jumlahSampel * (1 - faktorTrim); i++)
  {
    rataRata += buffer[i];
  }

  Serial.print("sensor = ");
  Serial.println(rataRata);

  delay(500);
}

JWS Universal (Jadwal Waktu Shalat) 1 – 8 panel

Jadwal Waktu Shalat (JWS) adalah media informasi di rumah ibadah Masjid/Mushalla/Surau yang menampilkan informasi waktu-waktu Shalat.

Dalam perkembangannya Jam Waktu Shalat berfungsi sebagai :

  1. Menampilkan Jadwal / Waktu Shalat wajib dan sunat
  2. Menampilkan informasi tanggal Masehi, Hijriah, Pasaran Jawa, dan penanggalan khusus seperti penanggalan Minang
  3. Menampilkan Ayat Al-Quran, Doa
  4. Informasi Tartil, Tarhim, Azan, Iqamah
  5. Pengingat / alarm waktu masuk Shalat dan selesai Iqamah
  6. Memutar Suara/Musik Tartil, Tarhim disertai kontrol hidup/mati amplifier
  7. Perhitungan jadwal berdasarkan posisi matahari, masukan manual, data jadwal online harian.

Diagram alir Jawdal waktu-waktu Shalat :

 

Skema Jam waktu shalat universal:

 

Tampilan aplikasi (apk) JWS Semesin Universal:

 

contoh tampilan jadwal shalat 3 panel :

 

Koding / sketch JWS semesin:

/*
   JWS Semesin 1 - 8 panel

   Fitur yang tidak aktif
   1. Tampilan tanggal Hijriah
   2. Tampilan tanggal Jawa
   3. Tampilan tanggal Minang
   4. Pesan selama tartil
   5. Pesan selama tarhim
   6. Pesan selama Iqamah

*/

#define serialDebug                       false
#define modeDemo                          false

#define namaMesjid                        "JWS"

//defenisi pin
#define pinMP3Busy                        2
#define pinRelayAmpli                     3
#define pinBuzzer                         4
#define RTCDetikIRQ                       A3

#define relayOn                           LOW

#define I2CEEPROM_ADDRESS                 0x57

//setting
#define periodaAlarmWaktuShalatMasuk      100//milidetik
#define periodaAlarmWaktuAkhirIqamah      300//milidetik

#define pixelLebarPanel                   32
#define pixelTinggiPanel                  16

//variabel Setting
#define kecepatanMinimal                  10
#define skalaKecepatan                    10

#define tokenSetting                      32
#define lamaAksesApk                      10L * 60 * 1000

#define jumlahNyalaPadam                  5
#define jumlahTextInformasi               10

#define fontNamaMesjid                    Arial14
#define fontJamDanMenitUtama              angka6x14
#define fontSimbolGambar                  simbolGambar

#include <Wire.h>
#include <EEPROM.h>
#include <DMD_Semesin.h>
#include <RTC_Semesin.h>
#include <DFPlayer_Mini_Mp3.h>
#include <BluetoothApk.h>

#include <fonts/angka6x14.h>
#include <fonts/SystemFont5x7Ramping.h>
#include <fonts/Arial14.h>
#include <fonts/simbolGambar.h>

#include "fungsi.h"
#include "definisi.h"
#include "konstanta.h"
#include "setting.h"
#include "WaktuShalat.h"

const uint8_t *alamatFont[] = {
  Arial14,
  SystemFont5x7Ramping,
};

SPIDMD dmd(8, 1);//max jumlah panel = 8
RTC_DS3231 rtc;
DateTime now;

//Status
bool RTCValid = true;
byte modeOperasi = modeInformasi;
bool statusAlarm;
bool tampilJamMenitDetik = false;
long millisRunningText;
uint8_t kecepatanRunningText;
uint8_t kecepatanRunningEfek;

uint16_t hitungMundurTartil;
uint16_t hitungMundurTarhim;
uint16_t hitungMundurAzan;//terhitung sejak waktu masuk
uint16_t hitungMundurIqamah;//terhitung sejak selesai azan

bool pesanSetelahAzan;
bool pesanSebelumShalat;
uint16_t hitungMundurPeringatanSimbol;
uint16_t hitungMundurShalat;

bool initHitungMundurTartil;
bool initHitungMundurTarhim;
bool initHitungMundurAzan;
bool initHitungMundurIqamah;
bool initHitungMundurPeringatanSimbol;
bool initHitungMundurShalat;

uint8_t hitungMundurAlarmIqamah;//kali

int8_t indexWaktuIbadah;
int8_t indekInformasi;

//efek
uint16_t lamaTampilText;
bool initTampil;

bool tampilanInformasiSambung;

byte indekTampilan = jumlahInformasi;
EfekMarque efekMarque;

uint16_t lebarText;
uint8_t tinggiText;

byte detikSebelumnya = 60;
byte menitSebelumnya = 60;
byte tanggalSebelumnya = 0;

long millisAlarm;

bool status;

BluetoothApk bluetoothApk(&Serial, "JWS2 SEMESIN.COM");
long millisAksesApk;
bool aksesApk;

uint16_t counterAlamat = 0;
uint16_t counterAlamatSebelumnya = 0;
uint16_t parameterSebelumnya = 0;

byte lebarJamUtama = 32;

byte lebarJadwalShalat = 32;
byte modeTampilanJadwal;
char buffer[72];
uint16_t offsetJadwalEEPROM;
bool runningTextAktif;

JamDanMenitJadwal jadwalBerikutnya;
JamDanMenitAlarm alarmBerikutnya;
int8_t hitungMundurAlarm;
bool initHitungMundurAlarm;
bool statusRelayAmpli;

void setup() {

  pinMode(pinBuzzer, OUTPUT);
  digitalWrite(pinRelayAmpli, !relayOn);
  pinMode(pinRelayAmpli, OUTPUT);
  pinMode(pinMP3Busy, INPUT_PULLUP);

  Serial.begin(9600);
  Serial.println(F("JWS Semesin 1-8 panel"));
  Serial.println(F("https://www.semesin.com/project"));

  mp3_set_serial (Serial);
  mp3_set_volume (15);


#if serialDebug
  Serial.println(F("Inisialisasi"));
#endif

  rtc.begin();

  if (rtc.lostPower())
  {
#if serialDebug
    Serial.println(F("RTC tidak jalan"));
#endif
    write_i2c_register(DS3231_ADDRESS, DS3231_STATUSREG, 0x00);
  }
  write_i2c_register(DS3231_ADDRESS, DS3231_CONTROL, DS3231_SquareWave1Hz);

  if (EEPROM.read(offsetof(Setting, token)) != tokenSetting)
  {
    pengaturanAwal();
  }

  updateUkuranTampilan();
  dmd.waitInterruptOver = false;
  dmd.begin();
  dmd.clearScreen();
  dmd.setBrightness(25.5 * EEPROM.read(offsetof(Setting, kecerahanStandar)));

  kecepatanRunningEfek = skala2kecepatanRunning(EEPROM.read(offsetof(Setting, kecepatanEfek)));

#if serialDebug
  Serial.println(F("Sistem mulai"));
#endif

  dmd.selectFont(fontNamaMesjid);
  dmd.drawString(1, 1, namaMesjid);

  delay(3000);
  dmd.clearScreen();

  statusRelayAmpli = digitalRead(pinMP3Busy);
}


void loop() {

....... file lengkap bisa didownload melalui link dibawah

file JWS universal:

JWS Semesin v2.1

catatan:
* untuk RTC DS3231, pin-SQW harus terpasang pada pin-A3 (arduino)
* jika menggunakan pcb jws (versi manapun) ada kemungkinan tidak cocok dan perlu modifikasi

Mengirim data melalui jaringan Wifi menggunakan ESP01 dengan metode POST (kasus IOT ThingSpeak)

ESP8266 adalah modul Wi-Fi dengan fitur TCP/IP yang lengkap dan bisa dihubungkan dengan mikrokontroler melalui protokon AT-Command. TCP/IP (Transmission Control Protocol / Internet Protocol)  adalah protokol komunikasi antara dua perangkat yang terhubung dengan sebuah metode paket termasuk metode pengalamatan dan metode transmisi dalam jaringan internet.

Dalam komunikasi TCP/IP dikenal beberapa metode request/permintaan yaitu : HEAD, GET, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH.

Metode POST  sering digunakan untuk mengirimkan data-data ter-enkripsi dan langsung ke server sehingga lebih menjamin kerahasiaan data. berbeda dengan metode GET yang mengirimkan requeat dalam bentuk URL.

Skematik ESP8266/ESP01 (+Arduino Uno) request metode post:

Sketch/koding pemrograman komunikasi ESP01 + arduino dengan server menggunakan metode POST :

char ssid[]             = "xxxx";            // your network SSID (name)
char pass[]             = "xxxxxxxx";        // your network password

char server[]           = "api.thingspeak.com";
byte port               = 80;
char APIKey[]           = "XXXXXXXXXXXXXX";
uint32_t periodeKirim   = 20000;

#include <WiFiEsp.h>
#include <SoftwareSerial.h>

SoftwareSerial SerialEsp(10, 11);
WiFiEspClient client;
int status = WL_IDLE_STATUS;     // the Wifi radio's status
uint32_t millisKirim;
bool statusKirim;

void setup()
{
  Serial.begin(9600);
  Serial.println("Mengirim data melalui jaringan Wifi menggunakan ESP01 dengan metode POST");
  Serial.println("https://www.semesin.com/project/");
  Serial.println();

  SerialEsp.begin(115200);
  SerialEsp.println("AT+UART_DEF=9600,8,1,0,0");
  delay(500);
  SerialEsp.begin(9600);

  WiFi.init(&SerialEsp);

  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
  }
  millisKirim = millis();
}

void loop()
{
  if (millisKirim < millis())
  {
    millisKirim = millis() + periodeKirim;
    
    // hubungkan ke jaringan wifi jika belum tersambung atau terputus (auto reconnect)
    if (WiFi.status() != WL_CONNECTED)
    {
      Serial.print("Menghubungkan ke jaringan SSID: ");
      Serial.println(ssid);
      while (WiFi.status() != WL_CONNECTED)
      {
        WiFi.begin(ssid, pass);
        Serial.print(".");
        delay(5000);
      }
      printWifiStatus();
      Serial.println("Berhasil terhubung ke jaringan");
    }


    if (WiFi.status() == WL_CONNECTED)
    {
      Serial.println();
      Serial.println("Menghubungkan dengan server...");
      if (client.connect(server, port))
      {
        Serial.println();
        Serial.println("Terhubung dengan server.");

        char content[30];
        sprintf(content, "field1=%d", millis() % 100);

        client.println("POST /update HTTP/1.1");
        client.println("Host: api.thingspeak.com");
        client.println("User-Agent: tslib-arduino/1.5.0");
        client.print("X-THINGSPEAKAPIKEY: ");
        client.println(APIKey);
        client.println("Content-Type: application/x-www-form-urlencoded");
        client.print("Content-Length: ");
        client.println(strlen(content));
        client.println("Connection: close");
        client.println();

        client.print(content);

        statusKirim = true;
      }
    }
  }

  if (statusKirim)
  {
    while (client.available())
    {
      char c = client.read();
      Serial.write(c);
    }

    if (!client.connected()) 
    {
      Serial.println();
      Serial.println("Memutuskan hubungan dengan server...");
      delay(10);
      client.stop();

      statusKirim = false;
    }
  }
}


void printWifiStatus()
{
  // print the SSID of the network you're attached to
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength
  long rssi = WiFi.RSSI();
  Serial.print("Signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

Library :

 

Hex Serial Monitor dengan Visual C#

Serial monitor seperti pada putty, hyper terminal, arduino IDE sangat bermanfaat untuk memonitor atau mengontrol perangkat yang sedang dikembangkan/development. Serial monitor juga bisa digunakan untuk proses debug untuk mencari kesalahan algoritma, misalnya dengan memberikan informasi nilai variabel ataupun informasi step by step percabangan.

Penggunaan serial monitor untuk keperluan debug haruslah memberikan informasi detail. Proses debug perangkat digital sangat berhubungan dengan format hexadesimal (hex) dan komunikasi binary.

Fitur Hex Serial Monitor:

  1. Data Serial yang diterima di tampilkan dalam format tabel hexa, juga disertai string.
  2. Baris baru dengan pilihan 0x00 dan 0x0A
  3. Kirim data serial dalam format :
    • String
    • 2 karakter, contohnya 12AB34BC = 0x12, 0xAB, 0x34, 0xBC
    • separator spasi, contoh : 12 AB 34 BC
    • separator koma, contoh : 12, AB,34, BC
    • awalan 0x
    • awalan $, contoh : $12$AB $34  $BC

Fungsi utama software ini adalah untuk berkomunikasi melalui serial port dalam mode binary dan ditampilkan dalam format hexa desimal.

tampilan Hexadecimal serial monitor menggunakan c#:

 

Kode software visual c# hex serial monitor:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;

namespace Hex_serial_monitor
{
    public partial class Form1 : Form
    {
        public int baris = 0;
        public int kolom = 0;
        String strKolom;


        public Form1()
        {
            InitializeComponent();
        }

        public delegate void AddDataDelegate();
        public AddDataDelegate delegateSerial;

        private void Form1_Load(object sender, EventArgs e)
        {
            string[] ports = SerialPort.GetPortNames();

            comboBoxCOMPort.Items.Clear();
            foreach (string port in ports)
            {
                comboBoxCOMPort.Items.Add(port);
            }
            if (comboBoxCOMPort.Items.Count > 0)
            {
                comboBoxCOMPort.SelectedIndex = 0;
            }

            this.delegateSerial = new AddDataDelegate(updateSerial);

            dataGridView1.Rows.Clear();
            dataGridView1.Rows.Add();
            dataGridView1.Rows[baris].Cells[0].Value = baris.ToString("X2");
            strKolom = "";

            radioButtonAscii.Checked = true;
            //checkBox0.Checked = true;

        }
        private void updateSerial()
        {
            while (serialPort1.BytesToRead > 0)
            {
                int data = serialPort1.ReadByte();
                dataGridView1.Rows[baris].Cells[kolom + 1].Value = data.ToString("X2");

                if ((data >= ' ') && (data < 128))
                {

                    strKolom += (char)data;
                }
                else
                {
                    strKolom += ' ';
                }


                dataGridView1.Rows[baris].Cells[17].Value = strKolom;

                kolom++;
                if (((data == 0) && (checkBox0.Checked)) ||
                    ((data == 0x0A) && (checkBox0A.Checked)) || 
                    (kolom == 16))
                {
                    kolom = 0;
                    baris++;
                    dataGridView1.Rows.Add();
                    dataGridView1.Rows[baris].Cells[0].Value = baris.ToString("X2");
                    strKolom = "";
                }
                if (checkBoxAutoScroll.Checked)
                {
                    dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.RowCount - 1;
                }
            }
            textBoxKirim.Focus();
        }

        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            dataGridView1.Invoke(this.delegateSerial, new Object[] { });
        }

        private void buttonClear_Click(object sender, EventArgs e)
        {
            dataGridView1.Rows.Clear();
            kolom = 0;
            baris = 0;
            dataGridView1.Rows.Add();
            dataGridView1.Rows[baris].Cells[0].Value = baris.ToString("X2");
            strKolom = "";
        }

        private void buttonConnect_Click(object sender, EventArgs e)
        {
            if (buttonConnect.Text == "Connect")
            {
                serialPort1.PortName = comboBoxCOMPort.Text;
                serialPort1.Open();
                if (serialPort1.IsOpen)
                {
                    buttonConnect.Text = "Disconnect";
                }
            }
            else
            {
                if (serialPort1.IsOpen)
                {
                    serialPort1.Close();
                    buttonConnect.Text = "Connect";
                }

            }

        }

        private void buttonKirim_Click(object sender, EventArgs e)
        {
            if (serialPort1.IsOpen)
            {
                if (radioButtonAscii.Checked)
                {
                    serialPort1.WriteLine(textBoxKirim.Text + "\r\n");
                }
                else if (radioButton2Character.Checked)
                {
                    if (textBoxKirim.Text.Length % 2 == 0)
                    {
                        int jumlahHex = textBoxKirim.Text.Length / 2;
                        byte[] hex = new byte[jumlahHex];

                        for (int i = 0; i < jumlahHex; i++)
                        {
                            String str2Hex = textBoxKirim.Text.Substring(i * 2, 2);
                            hex[i] = (byte)int.Parse(str2Hex, System.Globalization.NumberStyles.HexNumber);

                        }
                        serialPort1.Write(hex, 0, jumlahHex);
                    }
                }
                else if (radioButtonSpace.Checked)
                {
                    String[] strHexSplit = textBoxKirim.Text.Split(' ');
                    byte[] hex = new byte[strHexSplit.Length];

                    for (int i = 0; i < strHexSplit.Length; i++)
                    {
                        String strHex = strHexSplit[i].Substring(0, 2);
                        hex[i] = (byte)int.Parse(strHex, System.Globalization.NumberStyles.HexNumber);
                    }
                    serialPort1.Write(hex, 0, strHexSplit.Length);
                }
                else if (radioButtonComma.Checked)
                {
                    String[] strHexSplit = textBoxKirim.Text.Split(',');
                    byte[] hex = new byte[strHexSplit.Length];

                    for (int i = 0; i < strHexSplit.Length; i++)
                    {
                        String strHex = strHexSplit[i].Substring(0, 2);
                        hex[i] = (byte)int.Parse(strHex, System.Globalization.NumberStyles.HexNumber);
                    }
                    serialPort1.Write(hex, 0, strHexSplit.Length);
                }
                else if (radioButton0x.Checked)
                {
                    String[] strHexSplit = textBoxKirim.Text.Split('x');
                    byte[] hex = new byte[strHexSplit.Length];

                    for (int i = 0; i < strHexSplit.Length; i++)
                    {
                        String strHex = strHexSplit[i].Substring(0, 2);
                        hex[i] = (byte)int.Parse(strHex, System.Globalization.NumberStyles.HexNumber);
                    }
                    serialPort1.Write(hex, 0, strHexSplit.Length);
                }
                else if (radioButtonDollar.Checked)
                {
                    String[] strHexSplit = textBoxKirim.Text.Split('$');
                    byte[] hex = new byte[strHexSplit.Length];

                    for (int i = 0; i < strHexSplit.Length; i++)
                    {
                        String strHex = strHexSplit[i].Substring(0, 2);
                        hex[i] = (byte)int.Parse(strHex, System.Globalization.NumberStyles.HexNumber);
                    }
                    serialPort1.Write(hex, 0, strHexSplit.Length);
                }
                
            }
        }
    }
}

 

File Hexa Serial monitor berbasis c#

 

Media pembelajaran polygon berbasis Delphi

Poligon adalah geometri dengan banyak sudut seperti segitiga, segi empat dan nonagon dan lainnya. poligon terdiri atas beberapa titik atau vertex yang terhubung menjadi sebuah bentuk geometri. Poligon memiliki penamaan khusus sesuai jumlah sudutnya.

Poligon merupakan geometri yang dapat dikembangkan secara luas sehingga digunakan di berbagai bidang.

Untuk memudahkan mengenal atau memperkenalkan polygon, salah satu media yang bisa digunakan adalah pc/laptop. Fitur dasar seperti create dan modify adalah sebagai langkah awal  untuk membangun sebuah media pembelajaran geometri. Selanjutnya  bisa dikembangkan menjadi lebih fungsional sesuai kebutuhan pengembangannya.

Screenshoot media pembelajaran polygin menggunakan delphi :

Kode delphi pengembangan media belajar geometri polygon :

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Buttons, Grids;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    Image1: TImage;
    GroupBox1: TGroupBox;
    GroupBox3: TGroupBox;
    GroupBox2: TGroupBox;
    LabelPosisi: TLabel;
    ButtonClose: TButton;
    LabelVertex: TLabel;
    GroupBox4: TGroupBox;
    StringGrid1: TStringGrid;
    ButtonDelete: TButton;
    ButtonDraw: TButton;
    ButtonEdit: TButton;
    ButtonInsert: TButton;
    ButtonClear: TButton;
    ButtonSave: TButton;
    ButtonOpen: TButton;
    Label1: TLabel;
    procedure ButtonDrawClick(Sender: TObject);
    procedure ButtonEditClick(Sender: TObject);
    procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure Image1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ButtonCloseClick(Sender: TObject);
    procedure ButtonSaveClick(Sender: TObject);
    procedure ButtonLoadClick(Sender: TObject);
    procedure refreshCanvas();
    procedure StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
      var CanSelect: Boolean);
    procedure ButtonDeleteClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure ButtonInsertClick(Sender: TObject);
    procedure ButtonClearClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

type Point = record
 X : integer;
 Y : integer;
end;

var
  Form1: TForm1;
  modeOperasi : integer;
  posisi : Point;
  tinggiCanvas : integer;
  startPolygon : boolean;
  polygon : array[0..20] of Point;
  polygonCount : integer;
  editIndex : integer;

const
  modeIdle = 0;
  modeDraw = 1;
  modeEdit = 3;
  modeInsert = 4;

implementation

{$R *.dfm}

procedure TForm1.refreshCanvas();
var
  i : integer;
begin
  StringGrid1.RowCount := polygonCount + 1;

  i := 0;
  while true do
  begin
    if polygon[i].X = -1 then
      break;
    StringGrid1.Cells[0,i+1] := inttostr(i+1);
    StringGrid1.Cells[1,i+1] := inttostr(polygon[i].X);
    StringGrid1.Cells[2,i+1] := inttostr(polygon[i].Y);

    if i = 0 then
    begin
      image1.Canvas.rectangle(0,0,image1.width-1, image1.height-1);
      image1.Canvas.MoveTo(polygon[0].X, tinggiCanvas - polygon[0].Y);
    end
    else
    begin
      if (modeOperasi = modeInsert) and (editIndex = i) then
      begin
        Image1.Canvas.Pen.Color := clRed;
      end
      else
      begin
        Image1.Canvas.Pen.Color := clBlack;
      end;
      image1.Canvas.LineTo(polygon[i].X, tinggiCanvas - polygon[i].Y);
    end;

    i := i + 1;
    if i = 20 then
      break;

  end;

   if modeOperasi = modeEdit then
   begin
    image1.Canvas.Rectangle(polygon[editIndex].X-5, tinggiCanvas - polygon[editIndex].Y - 5,
      polygon[editIndex].X+5, tinggiCanvas - polygon[editIndex].Y + 5);
   end;

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  StringGrid1.Cells[0,0] := 'No';
  StringGrid1.Cells[1,0] := 'X';
  StringGrid1.Cells[2,0] := 'Y';
end;


procedure TForm1.FormResize(Sender: TObject);
begin
  tinggiCanvas := Image1.Height;

end;

procedure TForm1.ButtonDrawClick(Sender: TObject);
begin
  buttonDraw.Font.Style := [fsBold];
  ButtonEdit.Font.Style := [];
  ButtonInsert.Font.Style := [];

  modeOperasi := modeDraw;
  startPolygon := true;
  polygonCount := 0;
  polygon[polygonCount].X := -1;

  refreshCanvas();
end;

procedure TForm1.ButtonCloseClick(Sender: TObject);
begin

  if (modeOperasi = modeDraw) and (polygonCount > 1) then
  begin
    polygon[polygonCount] := polygon[0];
    polygonCount := polygonCount + 1;

    polygon[polygonCount].X := -1;

    LabelVertex.Caption := 'Vertex ' + inttostr(polygonCount);

    refreshCanvas();

    buttonDraw.Font.Style := [];
    ButtonEdit.Font.Style := [];
    ButtonInsert.Font.Style := [];
    modeOperasi := modeIdle;
  end;

end;

procedure TForm1.ButtonEditClick(Sender: TObject);
begin
  buttonDraw.Font.Style := [];
  ButtonEdit.Font.Style := [fsBold];
  ButtonInsert.Font.Style := [];
  modeOperasi := modeEdit;
  editIndex := 1;
  refreshCanvas();
end;

procedure TForm1.ButtonInsertClick(Sender: TObject);
begin
  buttonDraw.Font.Style := [];
  ButtonEdit.Font.Style := [];
  ButtonInsert.Font.Style := [fsBold];
  modeOperasi := modeInsert;
  editIndex := 1;
  refreshCanvas();
end;


procedure TForm1.ButtonClearClick(Sender: TObject);
begin
  polygonCount := 0;
  polygon[polygonCount].X := -1;

  refreshCanvas();
end;

procedure TForm1.ButtonDeleteClick(Sender: TObject);
var
  i : integer;
begin
  if editIndex <> 0 then
  begin
    for i := editIndex to polygonCount do
    begin
      polygon[i].X := polygon[i+1].X;
      polygon[i].Y := polygon[i+1].Y;
    end;
    polygonCount := polygonCount - 1;
    refreshCanvas();
  end;
end;


procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  posisi.X := X;
  posisi.Y := tinggiCanvas - Y;
  LabelPosisi.Caption := 'Posisi ' + inttostr(posisi.X) + ', ' + inttostr(posisi.Y);
end;

procedure TForm1.Image1Click(Sender: TObject);
var
  i : integer;
begin
  if (modeOperasi = modeDraw) and (polygonCount < 20) then
  begin
    polygon[polygonCount].X := posisi.X;
    polygon[polygonCount].Y := posisi.Y;
    polygonCount := polygonCount + 1;
    polygon[polygonCount].X := -1;
    LabelVertex.Caption := 'Vertex ' + inttostr(polygonCount);

    refreshCanvas();
  end
  else if (modeOperasi = modeEdit) and (editIndex > 0)then
  begin
    if (editIndex = 1) or (editIndex = polygonCount-1) then
    begin
      polygon[1].X := posisi.X;
      polygon[1].Y := posisi.Y;
      polygon[polygonCount-1].X := posisi.X;
      polygon[polygonCount-1].Y := posisi.Y;
    end
    else if (editIndex > 0) then
    begin
      polygon[editIndex].X := posisi.X;
      polygon[editIndex].Y := posisi.Y;

    end;

    refreshCanvas();
  end
  else if (modeOperasi = modeInsert) and (editIndex > 0) and (polygonCount < 20) then
  begin
    for i := polygonCount downto editIndex do
    begin
      polygon[i+1].X := polygon[i].X;
      polygon[i+1].Y := polygon[i].Y;
    end;
    polygon[editIndex].X := posisi.X;
    polygon[editIndex].Y := posisi.Y;
    polygonCount := polygonCount + 1;
    refreshCanvas();
 end;

end;


procedure TForm1.ButtonSaveClick(Sender: TObject);
var
  Stream: TStream;
begin
  Stream:= TFileStream.Create('polygon.bin', fmCreate);
  Stream.WriteBuffer(polygonCount, SizeOf(integer));
  Stream.WriteBuffer(polygon, (polygonCount+1) * SizeOf(Point));
  Stream.Free;
end;

procedure TForm1.ButtonLoadClick(Sender: TObject);
var
  Stream: TStream;
begin
  Stream:= TFileStream.Create('polygon.bin', fmOpenRead);
  Stream.ReadBuffer(polygonCount, SizeOf(integer));
  Stream.ReadBuffer(polygon, (polygonCount+1) * SizeOf(Point));
  Stream.Free;

  refreshCanvas();
  LabelVertex.Caption := 'Vertex ' + inttostr(polygonCount);

end;

procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol,
  ARow: Integer; var CanSelect: Boolean);
begin
  editIndex := ARow - 1;
  refreshCanvas();
end;

end.

code lengkap media pengajaran geometri poligon:
library :

Penjadwalan pembacaan sensor berbasis RTC DS3231

Pembacaan sensor dengan arduino dapat dilakukan kapan saja dan dimana saja dalam sketch arduino sesuai kebutuhan. Penjadwalan diperlukan untuk mendapatkan nilai dari sensor dalam rentang tertentu atau pada waktu-waktu khusus misalnya baca sensor saat gerhana matahari. Jika menginginkan sensor dibaca hanya pada waktu-waktu tertentu saja berdasarkan RTC dapat menggunakan metode berikut:

  1. Metode pergantian waktu, yaitu aksi pembacaan sensor dilakukan ketika satuan waktu berubah, contohnya setiap detik, setiap menit dan seterusnya. contoh :
      if (detikSebelumnya != now.second() )
      {
        detikSebelumnya = now.second();
        bacaSensor();
      }
    
  2. Metode modulus waktu, yaitu pembacaan sensor dilakukan pada setiap kelipatan satuan waktu, seperti pembacaan sensor setiap 2 detik, setiap 30 detik dan seterusnya. Contohnya :
      if (!(now.second() % periodaBacaSensor) )
      {
        bacaSensor();
      }
    
  3. Metode rentang waktu, yaitu pembacaan sensor dilakukan selama waktu pada rentang waktu tertentu. Contohnya :
      if ((now.hour() >= 20) || (now.hour() < 3))
      {
        bacaSensor();
      }
    
  4. Metode Tabel, yaitu pembacaan sensor sesuai waktu yang ditetapkan berdasarkan tanggal dan waktu tertentu, (pembacaan hanya dilakukan 1 kali). Contohnya:
      if (now.getEpoch() == databaseJadwal[i].getEpoch())
      {
        bacaSensor();
      }
    

Rangkaian yang digunakan pada pembacaan sensor sesuai jadwal tertentu berbasis rtc:

berikut kode/sketch penjadwalan pembacaan sensor menggunakan rtc pada arduino:


#define pinSensor     A0

#include <Wire.h>
#include "Sodaq_DS3231.h"

DateTime set = {2020, 2, 22, 7, 0, 0, 1};
DateTime now;
DateTime databaseJadwal[] = {
  {2020, 2, 22, 7, 0, 3, 1},
  {2020, 2, 23, 7, 0, 0, 1},
  {2020, 2, 24, 7, 0, 0, 1},
  {2020, 2, 25, 7, 0, 0, 1},
  {2020, 2, 26, 7, 0, 0, 1},
  {2020, 2, 27, 7, 0, 0, 1},
};
byte detikSebelumnya;
byte menitSebelumnya;

#define periodaBacaSensor   5//detik

void bacaSensor()
{
  analogRead(pinSensor);
}

void setup() {
  Serial.begin(9600);
  Serial.println("Penjadwalan pembacaan sensor berbasis RTC");
  Serial.println("https://www.semesin.com/project");
  Serial.println();

  Wire.begin();
  rtc.begin();
  now = rtc.now();
  //  if (now.year() == 2000)
  {
    rtc.setDateTime(set);
  }
}

void loop() {
  now = rtc.now();

  if (detikSebelumnya != now.second() )
  {

    char bufWaktu[32];
    sprintf(bufWaktu, "%02d:%02d:%02d %02d-%02d-%04d", now.hour(), now.minute(), now.second(), now.date(), now.month(), now.year());
    Serial.println(bufWaktu);

    //pembacaan setiap detik
    detikSebelumnya = now.second();
    Serial.println("setiap detik");
    bacaSensor();


    //pembacaan setiap 2 detik
    if (now.second() % 2)
    {
      Serial.println("setiap 2 detik");
      bacaSensor();
    }

    //pembacaan setiap detik ke-5
    if (now.second() == 5)
    {
      Serial.println("setiap detik ke-5");
      bacaSensor();
    }

    //pembacaan setiap n detik (n 1..60)
    if (!(now.second() % periodaBacaSensor) )
    {
      Serial.println("setiap n detik");
      bacaSensor();
    }

    //pembacaan setiap 937 detik (berlaku untuk x detik 1..~)
    if (!(now.getEpoch() % 937) )
    {
      Serial.println("setiap 937 detik");
      bacaSensor();
    }

    //pembacaan setiap menit
    if (menitSebelumnya != now.minute() )
    {
      Serial.println("setiap menit");
      menitSebelumnya = now.minute();
      bacaSensor();
    }

    //pembacaan setiap 3 jam = 00:00, 03:00, 06:00, 09:00, 12:00, 15:00, 18:00, 21:00
    if (!(now.hour() % 3) && !now.minute() && !now.second())
    {
      Serial.println("setiap 3 jam");
      bacaSensor();
    }

    //pembacaan dalam rentang waktu
    if ((now.hour() >= 7) && (now.hour() < 10))//jam 07:00 - jam 09:59
    {
      Serial.println("pembacaan dalam rentang waktu 1");
      bacaSensor();
    }
    else if ((now.hour() >= 11) && (now.hour() < 15))//jam 11:00 - jam 14:59
    {
      Serial.println("pembacaan dalam rentang waktu 2");
      bacaSensor();
    }
    else if ((now.hour() >= 20) || (now.hour() < 3))//jam 20:00 - jam 02:59
    {
      Serial.println("pembacaan dalam rentang waktu 3");
      bacaSensor();
    }

    //pembacaan berdasarkan tabel waktu
    for (byte i = 0; i < sizeof(databaseJadwal) / sizeof(DateTime); i++)
    {
      if (now.getEpoch() == databaseJadwal[i].getEpoch())
      {
        Serial.print("tabel waktu = ");
        Serial.println(i);
        bacaSensor();
      }
    }
  }

}

library :

DMD Jam, Timer, Countdown dengan arduino

Jam, timer/stopwatch, countdown seringkali di kombinasikan dalam sebuah perangkat, karena fitur2 ini memiliki kemiripan fungsi.

Fitur tampilan dapat dipilih melalui satu tombol, setiap tombol ditekan maka mode waktu akan berubah yaitu Watch – Timer – Countdown, fungsi masing-masing adalah :

Watch (Jam)

Ditandai dengan Karakter ‘W’ (watch) pada sudut kiri atas, berfungsi menampilan waktu rtc saat ini dalam format “hh:mm:dd” atau jam:menit:detik.

Timer (Hitung maju)

Ditandai dengan karakter ‘T’ (Timer) pada sudut kiri atas. Dalam mode ini pewaktu memulai hitungan dari 00:00:00 dan akan terus bertambah setiap detik. Apabila hitungan maju ini telah mencapai 86.400 detik atau 23:59:59 atau 1 hari penuh, maka hitungan akan dimulai lagi dari 0.

Countdown (Hitung mundur)

Ditandai dengan karakter ‘C’ (Count down) pada sudut kiri atas.berfungsi menampilkan waktu tersisa dari batas aawal yang bisa diatur dengan memodifikasi variabel ‘waktuHitungMundur’ seperti contoh :


#define waktuHitungMundur 5 * 60// 5 menit

Waktu hitung mungdur dapat diisi angka 1 – 86400 dalam satuan detik.

Apabila hitungan habis atau mencapai 0 (00:00:00) maka tampilan akan berubah ke mode watch dan menampilkan waktu rtc saat ini.

Komponen yang digunakan dalam perancangan jam, counter, countdown:

  1. Arduino uno
  2. 2x Panel P10 hub12
  3. RTC DS3231

berikut skema rakitan tampilan waktu, hitung maju dan hitung mundur:

Koding/sketch arduino tampilan jam, penghitung mundur dan pewaktu maju:

#define waktuHitungMundur     5 * 60// 5 menit

#define pinTombol             A0


#include "Sodaq_DS3231.h"
#include <DMD2.h>
#include <fonts/angka6x14.h>
#include <fonts/System_Min5x7.h>

enum Mode
{
  modeJam,
  modeHitungMaju,
  modeHitungMundur,
  jumlahMode,
};

DateTime now;
uint32_t hitungMundur;
uint32_t hitungMaju;
byte mode;
char buffer[10];
byte jam, menit, detik;
byte detikSebelumnya;

SPIDMD dmd(2, 1);

void setup() {

  pinMode(pinTombol, INPUT_PULLUP);

  Serial.begin(9600);
  Serial.println("Jam, Hitung maju, Hitung mundur (Watch, Timer, Count down)");
  Serial.println("https://www.semesin.com/project");

  rtc.begin();

  //Set rtc untuk keperluan testing
  DateTime dt(2020, 2, 20, 15, 30, 0, 1);
  rtc.setDateTime(dt);

  dmd.begin();

  setMode();
}

void loop() {
  now = rtc.now();
  if (detikSebelumnya != now.second())
  {
    detikSebelumnya = now.second();

    switch (mode)
    {
      case modeJam:
        sprintf(buffer, "%02d%s%02d%s%02d", now.hour(), now.second() % 2 ? ":" : " ", now.minute(), now.second() % 2 ? ":" : " ", now.second());
        dmd.drawString(8, 1, buffer);
        break;

      case modeHitungMaju:
        jam = hitungMaju / (3600L);
        menit = (hitungMaju % (3600L)) / 60;
        detik = hitungMaju % 60;
        sprintf(buffer, "%02d%s%02d%s%02d", jam, now.second() % 2 ? ":" : " ", menit, now.second() % 2 ? ":" : " ", detik);
        dmd.drawString(8, 1, buffer);
        hitungMaju++;
        if(hitungMaju == 86400L)
        {
          hitungMaju = 0; 
        }
        
        break;

      case modeHitungMundur:
        jam = hitungMundur / (3600L);
        menit = (hitungMundur % (3600L)) / 60;
        detik = hitungMundur % 60;
        sprintf(buffer, "%02d%s%02d%s%02d", jam, now.second() % 2 ? ":" : " ", menit, now.second() % 2 ? ":" : " ", detik);
        dmd.drawString(8, 1, buffer);

        hitungMundur--;
        if (!hitungMundur)
        {
          mode = modeJam;
          setMode();
        }
        break;
    }
  }
  if (!digitalRead(pinTombol))
  {
    delay(100);
    if (!digitalRead(pinTombol))
    {
      mode++;
      mode %= jumlahMode;
      setMode();

      while (!digitalRead(pinTombol));
    }
  }
}

void setMode()
{
  dmd.selectFont(System_Min5x7);
  switch (mode)
  {
    case modeJam:
      dmd.drawString(0, 0, "W");
      break;

    case modeHitungMaju:
      dmd.drawString(0, 0, "T");
      hitungMaju = 0;
      break;

    case modeHitungMundur:
      dmd.drawString(0, 0, "C");
      hitungMundur = waktuHitungMundur;
      break;
  }
  dmd.selectFont(angka6x14);
  dmd.fontHeader.fixedWidth = 2;
}

library :

JWS Jammer dengan Database Jadwal Waktu Shalat Bulanan

Jammer merupakan alat pengacak/blokir sinyal handphone/hp dapat digunakan di tempat ibadah untuk me-nonaktifkan perangkat cellular sehingga tidak bisa dipanggil/memanggil dengan tujuan menjaga tempat ibadah dari kebisingan dering ponsel.

Penggunaan jammer/jamming/repeater sinyal hp bisa mengganggu hak orang lain dan berpotensi melanggar undang-undang telekomunikasi, gunakan dengan bijak

Jadwal Shalat

Jadwal shalat berubah-ubah setiap hari tergantung posisi matahari dan bulan, hal ini membuat perangkat digital harus mampu mengikuti perubahan ini. Beberapa metode penggunaan jadwal shalat shalat digital :

  1. Jadwal manual, yaitu jadwal rata-rata dari setiap waktu-waktu shalat
  2. Jadwal database, yaitu jadwal yang tersimpan dalam tabel, bisa mingguan, bulanan atau tahunan
  3. Jadwal Matahari, yaitu penghitungan waktu shalat menggunakan posisi peredaran matahari, dengan memasukkan variabel posisi lintang, bujur, dan tanggal.
  4. Jadwal online, dengan mengambil data dari server online setiap hari.

Skema JWS Jammer menggunakan arduino:

Komponen yang digunakan dalam JWS Database Jadwal Waktu Bulanan:

  1. Arduino Uno
  2. LCD 1602 I2C Backpack
  3. RTC DS3231
  4. Relay untuk jammer
  5. 3x LED
  6. 3x TOmbol

Koding/program JWS Jadwal Bulanan:

#define pinLedMerah             5
#define pinLedHijau             6
#define pinLedBiru              7

#define pinTombolOtomatis       11
#define pinTombolManual         12
#define pinTombolJammer         13

#define pinJammer               4
#define relayOn                 LOW

#define waktuJammerShalat       20
#define waktuJammerShalatJumat  60
#define waktuJammerManual       20

#include <Wire.h>
#include "Sodaq_DS3231.h"
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x3F, 16, 2);

struct waktu
{
  byte jam;
  byte menit;
};


const waktu jadwalShalat[][8] PROGMEM = {
  //Imsyak  Shubuh  Terbit  Dhuha Dzuhur  Ashr  Maghrib Isya
  {{ 4, 20}, { 4, 30}, { 5, 51}, { 6, 15}, {12, 7}, {15, 34}, {18, 20}, {19, 36}},
  {{ 4, 20}, { 4, 30}, { 5, 51}, { 6, 15}, {12, 8}, {15, 34}, {18, 20}, {19, 36}},
  {{ 4, 21}, { 4, 31}, { 5, 52}, { 6, 16}, {12, 8}, {15, 35}, {18, 21}, {19, 36}},
  {{ 4, 21}, { 4, 31}, { 5, 52}, { 6, 16}, {12, 9}, {15, 35}, {18, 21}, {19, 37}},
  {{ 4, 22}, { 4, 32}, { 5, 53}, { 6, 17}, {12, 9}, {15, 36}, {18, 21}, {19, 37}},
  {{ 4, 23}, { 4, 33}, { 5, 53}, { 6, 17}, {12, 9}, {15, 36}, {18, 22}, {19, 37}},
  {{ 4, 23}, { 4, 33}, { 5, 54}, { 6, 18}, {12, 10}, {15, 36}, {18, 22}, {19, 38}},
  {{ 4, 24}, { 4, 34}, { 5, 54}, { 6, 18}, {12, 10}, {15, 37}, {18, 23}, {19, 38}},
  {{ 4, 24}, { 4, 34}, { 5, 55}, { 6, 19}, {12, 11}, {15, 37}, {18, 23}, {19, 38}},
  {{ 4, 25}, { 4, 35}, { 5, 55}, { 6, 19}, {12, 11}, {15, 37}, {18, 23}, {19, 38}},
  {{ 4, 25}, { 4, 35}, { 5, 56}, { 6, 20}, {12, 12}, {15, 37}, {18, 24}, {19, 39}},
  {{ 4, 26}, { 4, 36}, { 5, 56}, { 6, 20}, {12, 12}, {15, 38}, {18, 24}, {19, 39}},
  {{ 4, 27}, { 4, 37}, { 5, 56}, { 6, 20}, {12, 12}, {15, 38}, {18, 24}, {19, 39}},
  {{ 4, 27}, { 4, 37}, { 5, 57}, { 6, 21}, {12, 13}, {15, 38}, {18, 25}, {19, 39}},
  {{ 4, 28}, { 4, 38}, { 5, 57}, { 6, 21}, {12, 13}, {15, 38}, {18, 25}, {19, 40}},
  {{ 4, 28}, { 4, 38}, { 5, 58}, { 6, 22}, {12, 13}, {15, 39}, {18, 25}, {19, 40}},
  {{ 4, 29}, { 4, 39}, { 5, 58}, { 6, 22}, {12, 14}, {15, 39}, {18, 25}, {19, 40}},
  {{ 4, 29}, { 4, 39}, { 5, 59}, { 6, 23}, {12, 14}, {15, 39}, {18, 26}, {19, 40}},
  {{ 4, 30}, { 4, 40}, { 5, 59}, { 6, 23}, {12, 14}, {15, 39}, {18, 26}, {19, 40}},
  {{ 4, 30}, { 4, 40}, { 5, 59}, { 6, 23}, {12, 15}, {15, 39}, {18, 26}, {19, 40}},
  {{ 4, 31}, { 4, 41}, { 6, 0}, { 6, 24}, {12, 15}, {15, 39}, {18, 26}, {19, 40}},
  {{ 4, 31}, { 4, 41}, { 6, 0}, { 6, 24}, {12, 15}, {15, 39}, {18, 26}, {19, 40}},
  {{ 4, 32}, { 4, 42}, { 6, 1}, { 6, 25}, {12, 16}, {15, 39}, {18, 27}, {19, 41}},
  {{ 4, 32}, { 4, 42}, { 6, 1}, { 6, 25}, {12, 16}, {15, 39}, {18, 27}, {19, 41}},
  {{ 4, 33}, { 4, 43}, { 6, 1}, { 6, 25}, {12, 16}, {15, 39}, {18, 27}, {19, 41}},
  {{ 4, 33}, { 4, 43}, { 6, 2}, { 6, 26}, {12, 16}, {15, 39}, {18, 27}, {19, 41}},
  {{ 4, 34}, { 4, 44}, { 6, 2}, { 6, 26}, {12, 17}, {15, 39}, {18, 27}, {19, 41}},
  {{ 4, 34}, { 4, 44}, { 6, 2}, { 6, 26}, {12, 17}, {15, 39}, {18, 27}, {19, 41}},
  {{ 4, 35}, { 4, 45}, { 6, 3}, { 6, 27}, {12, 17}, {15, 39}, {18, 27}, {19, 40}},
  {{ 4, 35}, { 4, 45}, { 6, 3}, { 6, 27}, {12, 17}, {15, 39}, {18, 27}, {19, 40}},
  {{ 4, 36}, { 4, 46}, { 6, 3}, { 6, 27}, {12, 17}, {15, 39}, {18, 27}, {19, 40}},
};
byte waktuUtama[] =  {1, 4, 5, 6, 7};
char namaHari[][8] = {"Minggu,", "Senin, ", "Selasa,", " Rabu, ", "Kamis, ", "Jumat, ", "Sabtu, "};
char namaWaktu[][16] = {"Imsyak", "Shubuh", "Terbit", "Dhuha", "Dzuhur", "Ashr", "Maghrib", "Isya"};

enum Mode
{
  otomatis,
  manual,
};

byte mode;
char buffer[32];
byte detikSebelumnya = 60;
byte menitSebelumnya = 60;
uint16_t unixRelay;
bool statusJammer;
DateTime now;

void setup()
{
  pinMode(pinLedMerah, OUTPUT);
  pinMode(pinLedHijau, OUTPUT);
  pinMode(pinLedBiru, OUTPUT);
  digitalWrite(pinJammer, !relayOn);
  pinMode(pinJammer, OUTPUT);

  pinMode(pinTombolOtomatis, INPUT_PULLUP);
  pinMode(pinTombolManual, INPUT_PULLUP);
  pinMode(pinTombolJammer, INPUT_PULLUP);

  Serial.begin(9600);
  Serial.println("JWS Jammer Sinyal HP dengan Jadwal Bulanan");
  Serial.println("https://www.semesin.com/project");

  Wire.begin();
  Wire.beginTransmission(0x3F);
  if (Wire.endTransmission())
  {
    lcd = LiquidCrystal_I2C(0x27, 16, 2);
  }
  lcd.begin();

  rtc.begin();
  now = rtc.now();
  if (now.year() == 2000)
  {
    //Waktu compiler
    char bulan[12];
    byte indexBulan;
    int jam, menit, detik, tanggal, tahun;

    char *namaBulan[12] = {
      "Jan", "Feb", "Mar", "Apr", "May", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };
    sscanf(__TIME__, "%d:%d:%d", &jam, &menit, &detik);
    sscanf(__DATE__, "%s %d %d", bulan, &tanggal, &tahun);
    for (indexBulan = 0; indexBulan < 12; indexBulan++) {
      if (strcmp(bulan, namaBulan[indexBulan]) == 0)
        break;
    }
    uint8_t wday = hariDariTanggal(tanggal, indexBulan + 1, tahun);
    DateTime dt(tahun, indexBulan + 1, tanggal, jam, menit, detik, wday);
    rtc.setDateTime(dt);
    Serial.println("RTC sudah otomatis di setting (Sekali saja)");
  }

  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("JWS Bulanan");
  lcd.setCursor(0, 1);
  lcd.print("fitur : Jammer");

  delay(3000);
  lcd.clear();

  digitalWrite(pinLedHijau, HIGH);
  Serial.println("Sistem mulai");

}

void loop()
{
  now = rtc.now();

  uint16_t unixSekarang = now.hour() * 60 + now.minute();
  if (detikSebelumnya != now.second())
  {

    detikSebelumnya = now.second();
    if (!statusJammer)
    {
      sprintf(buffer, "%s %02d/%02d/%02d", namaHari[now.dayOfWeek() - 1], now.date(), now.month(), now.year() - 2000);
      lcd.setCursor(0, 0);
      lcd.print(buffer);
    }
    sprintf(buffer, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
    lcd.setCursor(4, 1);
    lcd.print(buffer);
  }
  if (menitSebelumnya != now.minute())
  {
    menitSebelumnya = now.minute();
    if (mode == otomatis)
    {
      for (byte i = 0; i < 5; i++)
      {
        if ((pgm_read_byte(&jadwalShalat[now.date() - 1][waktuUtama[i]].jam) == now.hour()) &&
            (pgm_read_byte(&jadwalShalat[now.date() - 1][waktuUtama[i]].menit) == now.minute()))
        {
          digitalWrite(pinLedMerah, HIGH);
          digitalWrite(pinJammer, relayOn);

          if ((i == 1) && (now.dayOfWeek() == 6)) //Dzuhur / jumatan
          {
            unixRelay = unixSekarang + waktuJammerShalatJumat;//menit
          }
          else
          {
            unixRelay = unixSekarang + waktuJammerShalat;//menit
          }
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("  J A M M E R   ");
          statusJammer = true;

          sprintf(buffer, "Waktu: %s", namaWaktu[waktuUtama[i]]);
          lcd.setCursor(0, 1);
          lcd.print(buffer);

          delay(5000);
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("  J A M M E R   ");
        }
      }
    }
    if (statusJammer)
    {
      if (unixSekarang == unixRelay)
      {
        statusJammer = false;
        digitalWrite(pinLedMerah, LOW);
        digitalWrite(pinJammer, !relayOn);
      }
    }
  }


  if (!digitalRead(pinTombolOtomatis))
  {
    delay(50);
    if (!digitalRead(pinTombolOtomatis))
    {
      mode = otomatis;
      digitalWrite(pinLedHijau, HIGH);
      digitalWrite(pinLedBiru, LOW);
      lcd.setCursor(0, 0);
      lcd.print("    M O D E     ");
      lcd.setCursor(0, 1);
      lcd.print("O T O M A T I S ");
      delay(3000);
      lcd.clear();
    }
  }
  if (!digitalRead(pinTombolManual))
  {
    delay(50);
    if (!digitalRead(pinTombolManual))
    {
      mode = manual;
      digitalWrite(pinLedHijau, LOW);
      digitalWrite(pinLedBiru, HIGH);
      lcd.setCursor(0, 0);
      lcd.print("    M O D E     ");
      lcd.setCursor(0, 1);
      lcd.print("  M A N U A L   ");
      delay(3000);
      lcd.clear();
    }
  }
  if (mode == manual)
  {
    if (!digitalRead(pinTombolJammer))
    {
      delay(50);
      if (!digitalRead(pinTombolJammer))
      {
        if (statusJammer)
        {
          digitalWrite(pinLedMerah, LOW);
          digitalWrite(pinJammer, !relayOn);
          statusJammer = false;
        }
        else
        {
          digitalWrite(pinLedMerah, HIGH);
          digitalWrite(pinJammer, relayOn);
          unixRelay = unixSekarang + waktuJammerManual;//menit
          statusJammer = true;
          lcd.setCursor(0, 0);
          lcd.print("  J A M M E R   ");
          lcd.setCursor(0, 1);
          lcd.print("  M A N U A L   ");
          delay(5000);
        }
        while (!digitalRead(pinTombolJammer));
        delay(50);
      }
    }
  }
}
byte hariDariTanggal(byte tanggal, byte bulan, uint16_t tahun)
{
  uint16_t jumlahHariPerBulanMasehi[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
  if (tahun >= 2000)
    tahun -= 2000;

  uint32_t jumlahHari = tahun * 365;
  uint16_t tahunKabisat = tahun / 4;
  for (byte i = 0; i < tahun; i++)
  {
    if (!(i % 4))
    {
      jumlahHari++;
    }
  }
  jumlahHari += jumlahHariPerBulanMasehi[bulan - 1];
  if ( (bulan >= 2) && !(tahun % 4))
  {
    jumlahHari++;
  }
  jumlahHari += tanggal;
  return ((jumlahHari + 5) % 7) + 1;
}

Library:

simulasi Gerak lurus berubah beraturan (GLBB) – Gerak jatuh bebas menggunakan arduino

Gerak lurus berubah beraturan (GLBB) adalah gerak lurus yang percepatannya tetap, artinya kecepatannya berubah beraturan. terdapat beberapa jenis GLBB yaitu :

  1. Gerak vertikal ke atas
  2. Gerak jatuh bebas
  3. Gerak vertikal ke bawah

untuk keperluan simulasi gerak lurus berubah beraturan diperlukan peralatan sebagai berikut :

  1. Arduino uno
  2. 3x sensor obstacle
  3. sensor ultrasonic
  4. Tombol

Dengan tata letak seperti berikut:

Skema simulasi GLBB menggunakan arduino:

sketch/koding simulasi gerak lurus berubah beraturan berbasis arduino:

#define tinggiSensorBawah   10//cm

#define pinTombolMulai      A0

#define pinSensorA          8
#define pinSensorB          9
#define pinSensorC          10

#include <Ultrasonic.h>

Ultrasonic ultrasonic(3, 2);

uint16_t nomor;

void setup() {
  pinMode(pinTombolMulai, INPUT_PULLUP);

  pinMode(pinSensorA, INPUT_PULLUP);
  pinMode(pinSensorB, INPUT_PULLUP);
  pinMode(pinSensorC, INPUT_PULLUP);

  Serial.begin(9600);
  Serial.println("Gerak Lurus Berubah Beraturan");
  Serial.println("https://www.semesin.com/project");

  Serial.println();
  Serial.println("Gerak Lurus Berubah Beraturan (jatuh bebas)");
  Serial.println("No\tJarak (cm)\tWaktu (s)\tPercepatan (cm/s2)");

}

void loop() {

  if (!digitalRead(pinTombolMulai))
  {
    delay(50);
    if (!digitalRead(pinTombolMulai))
    {
      while (!digitalRead(pinTombolMulai));

      uint16_t tinggi = 1.0 * ultrasonic.read() - tinggiSensorBawah;
      float jarakSensor = 1.0 * tinggi / 2;

      unsigned long waktuA, waktuB, waktuC;
      bool mulaiDiTitikA, mulaiDiTitikC = false;
      while (1)
      {
        if (!digitalRead(pinSensorA))
        {
          waktuA = micros();
          mulaiDiTitikA = true;
          break;
        }
        if (!digitalRead(pinSensorC))
        {
          waktuC = micros();
          mulaiDiTitikC = true;
          break;
        }
      }

      while (digitalRead(pinSensorB));
      waktuB = micros();

      while (1)
      {
        if (!digitalRead(pinSensorA))
        {
          waktuA = micros();
          break;
        }
        if (!digitalRead(pinSensorC))
        {
          waktuC = micros();
          break;
        }
      }

      float waktu_ac = abs(1.0 * waktuC - waktuA) / 1000000;//detik
      float waktu_ab = abs(1.0 * waktuB - waktuA) / 1000000;//detik
      float waktu_bc = abs(1.0 * waktuC - waktuB) / 1000000;//detik

      float kecepatan_ab = 0.5 * jarakSensor / waktu_ab;//cm/s
      float kecepatan_cb = 0.5 * jarakSensor / waktu_ab;//cm/s
      float kecepatan_bc = 0.5 * jarakSensor / waktu_bc;//cm/s

      float percepatan;
      if (mulaiDiTitikA)
      {
        percepatan = (1.0 * kecepatan_bc - kecepatan_ab) / waktu_bc;//cm/s2
      }
      if (mulaiDiTitikC)
      {
        percepatan = (1.0 * kecepatan_ab - kecepatan_bc) / waktu_ab;//cm/s2
      }

      char buffer[200];
      char strWaktu_ac[10];
      char strPercepatan[10];

      dtostrf(waktu_ac, 8, 2, strWaktu_ac);
      dtostrf(percepatan, 8, 2, strPercepatan);

      sprintf(buffer, "%d\t%d\t\t%s\t%s", nomor, tinggi, strWaktu_ac, strPercepatan);
      Serial.println(buffer);
      nomor++;

    }
  }
}

Library:

webserver + ajax dengan Nodemcu (esp8266) (aplikasi monitoring suhu dan kelembaban)

Monitoring atau kegiatan pemantauan bisa dilakukan dari jarak jauh melalui sistem komunikasi internet maupun metode komunikasi lainnya. Salah satu unsur monitoring data adalah display atau tampilan dimana beberapa aspek yang perlu diperhatikan adalah :

  1. Informatif, data yang ditampilkan mampu memberikan perspektif kondisi yang ada, sebagai contoh tampilan grafik: selain menunjukkan data juga akan memberikan gambaran pergerakan dari data-data sebelumnya (naik/turun).
  2. Real time. yaitu datayang tampil merupakan data lapangan saat ini dan terus diperbaharui dalam frekuensi tertentu dimana semakin besar akan semakin realtime.
  3. Bandwidth, adalah besarnya lalu lintas data yang terpakai oleh aktifitas monitoring, jadi sistem yang baik perlu mempertimbangkan metode lalu lintas data sekecil mungkin tanpa mengurangi aspek lainnya.
  4. Kenyamanan, berkaitan dengan design dan pola dinamik tampilan saat perubahan data.

Ajax

Asynchronous Javascript and XML (Ajax) adalah metode lalu lintas data web secara asynchronous (dibelakang layar) antara interface web (browser) dengan server yang bertujuan memberikan fitur interaktif kepada pengguna.

penggunaan ajax memungkinkan update data fleksibel tanpa perlu melakukan aksi (menekan tombl update) atau refresh halaman web.

Tampilan monitoring suhu dan kelembaban menggunakan nodemcu:

Komponen yang digunakan dalam perancangan monitoring suhu dan kelembaban berbasis webserver nodemcu :

  1. Nodemcu
  2. DHT11

Skema ajax webserver dengan nodemcu:

Sketch/program webserver nodemcu dengan ajax relatime:

#define jumlahData        5
#define hotspotSSID       "XXXXXXXX"
#define hotspotPassword   "XXXXXXXX"
#define pinDHT            D4

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

#include <dht.h>

dht DHT;

ESP8266WebServer server(80);

struct Data
{
  uint32_t index;
  float temperatur;
  float kelembaban;
};

Data data[jumlahData];
byte ringIndex;
uint32_t indexDataDHT;
long millisBacaSensor;

void handleRoot() {
  char buffer[] =
    "<html><head>"
    "  <title>Temperatur dan Kelembaban</title>"
    "  <meta charset='UTF-8'>"
    "  <script src='https://code.highcharts.com/highcharts.js'></script>"
    "</head>"
    "<body>"
    "  <center>"
    "    <h1>Temperatur dan Kelembaban</h1>"
    "    <h2>Grafik Temperatur dan Kelembaban</h2>"
    "    <div id='grafik' style='height:400px;'></div>"
    "  </center>"
    "<script type='text/javascript'>"
    "  var timerId = setInterval('updateGrafik()', 1000);"
    "  var grafik = Highcharts.chart('grafik', {"
    "    title: {text: ''},"
    "    xAxis: {title: {text: 'index'},categories: [],rotation:90,},"
    "    yAxis: ["
    "      {labels: {format: '{value} °C',style: {color: Highcharts.getOptions().colors[1]}},title: {text: 'Temperatur'},},"
    "      {labels: {format: '{value} %',style: {color: Highcharts.getOptions().colors[1]}},title: {text: 'Kelembaban'},opposite: true,}, ],"
    "    credits: {enabled: false},"
    "    series: ["
    "      {name: 'Temperatur',yAxis: 0,data: []}, "
    "      {name: 'Kelembaban',yAxis: 1,data: []}, ]"
    "  });"
    "  function updateGrafik(){"
    "    var xhttp = new XMLHttpRequest();"
    "    xhttp.onreadystatechange = function() {"
    "      if (this.readyState == 4 && this.status == 200) {"
    "        eval(this.responseText.trim());}};"
    "    xhttp.open('GET', '/updateData', true);"
    "    xhttp.send(); }"
    "</script>"
    "</body></html>";
  server.send(200, "text/html", buffer);
}

void handleNotFound()
{
  server.send(404, "text/plain", "Alamat tidak ditemukan");
}

void updateData()
{
  char buffer[1000];
  byte index;

  strcpy(buffer, "grafik.xAxis[0].setCategories([");

  index = ringIndex;
  for (byte i = 0; i < jumlahData; i++)
  {
    sprintf(&buffer[strlen(buffer)], "%lu,", data[index].index);
    index++;
    index %= jumlahData;
  }
  strcpy(&buffer[strlen(buffer)], "]);");

  strcpy(&buffer[strlen(buffer)], "grafik.series[0].setData([");
  index = ringIndex;
  for (byte i = 0; i < jumlahData; i++)
  {
    dtostrf(data[index].temperatur, 8, 2, &buffer[strlen(buffer)]);
    strcpy(&buffer[strlen(buffer)], ",");
    index++;
    index %= jumlahData;
  }
  strcpy(&buffer[strlen(buffer)], "]);");

  strcpy(&buffer[strlen(buffer)], "grafik.series[1].setData([");
  index = ringIndex;
  for (byte i = 0; i < jumlahData; i++)
  {
    dtostrf(data[index].kelembaban, 8, 2, &buffer[strlen(buffer)]);
    strcpy(&buffer[strlen(buffer)], ",");
    index++;
    index %= jumlahData;
  }
  strcpy(&buffer[strlen(buffer)], "]);");

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

}


void setup(void) {
  Serial.begin(9600);
  Serial.println("Web server dengan fitur ajax menggunakan nodemcu (aplikasi monitor suhu dan kelembaban)");
  Serial.println("https://www.semesin.com/project");
  
  WiFi.mode(WIFI_STA);
  WiFi.begin(hotspotSSID, hotspotPassword);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(hotspotSSID);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.on("/updateData", updateData);
  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {
  server.handleClient();

  if (millisBacaSensor < millis())
  {
    millisBacaSensor = millis() + 1000;

    if (DHT.read11(pinDHT) == DHTLIB_OK)
    {
      data[ringIndex].index = indexDataDHT;
      data[ringIndex].temperatur = DHT.temperature;
      data[ringIndex].kelembaban = DHT.humidity;

      indexDataDHT++;

      ringIndex++;
      ringIndex %= jumlahData;
    }
  }
}

Library: