Pembuatan aplikasi parkir menggunakan appsheet

Panduan Lengkap Sistem Parkir Terintegrasi AppSheet

🅿️ Sistem Parkir Terintegrasi AppSheet

Panduan Lengkap: Hardware, Pembayaran & Pencetakan Karcis

📅 Versi 2.0 ⏱️ Setup: 4-6 Jam 💰 Estimasi: Rp 3-5 Juta 🎯 Level: Intermediate

🏗️ 1. Arsitektur Sistem

💡 Konsep Utama Sistem ini menghubungkan Hardware Layer (Arduino & Raspberry Pi) dengan Cloud Layer (Google Sheets & AppSheet) melalui API Gateway, memungkinkan kontrol real-time dan monitoring dari mana saja.

1.1 Alur Data Sistem

🚗 Sensor 🔌 Arduino 🖥️ Raspberry Pi ☁️ Google Sheets

📱 AppSheet 🖨️ Printer 🚧 Barrier

1.2 Komponen Utama

Komponen Fungsi Tech Stack
Arduino Uno Interface sensor & aktuator lokal C++
Raspberry Pi 4 Gateway, printer controller, API bridge Python 3, Flask
Google Sheets Database transaksi & master data Apps Script
AppSheet Mobile app untuk operator No-code platform

🔧 2. Komponen Hardware

2.1 Daftar Belanja (1 Set Gerbang)

No Komponen Spesifikasi Harga
1 Raspberry Pi 4 4GB RAM + 32GB SD Card Rp 1.500.000
2 Arduino Uno R3 Original atau Clone Rp 150.000
3 Sensor Ultrasonik HC-SR04 (2 unit) Rp 50.000
4 Printer Thermal 58mm USB/Ethernet Rp 400.000
5 Modul Relay 4 Channel 5V Rp 50.000
6 Motor Barrier Servo/Stepper + Driver Rp 500.000
7 QR Code Scanner USB HID Mode Rp 400.000
8 LCD Display 16x2 I2C Rp 75.000
9 Accessories Kabel, Breadboard, PSU Rp 300.000
TOTAL ESTIMASI Rp 3.425.000

2.2 Wiring Diagram Arduino

📍 Pin Configuration
// PIN MAPPING ARDUINO UNO
const int TRIG_PIN = 9;        // Ultrasonic Trigger
const int ECHO_PIN = 10;       // Ultrasonic Echo  
const int IR_SENSOR = 2;       // Infrared Sensor (Interrupt)
const int BTN_ENTRY = 3;       // Tombol Entry (Interrupt)
const int BTN_EXIT = 4;        // Tombol Exit
const int RELAY_BARRIER = 5;   // Relay Control (Active LOW)
const int LED_GREEN = 6;       // Status OK
const int LED_RED = 7;         // Status Stop/Error
const int BUZZER = 8;          // Piezo Buzzer
const int LCD_SDA = A4;        // I2C LCD
const int LCD_SCL = A5;        // I2C LCD
⚠️ Penting! Relay module umumnya Active LOW, artinya pin LOW (0V) akan mengaktifkan relay. Pastikan barrier gate dalam kondisi aman saat testing.

🗄️ 3. Setup Database Google Sheets

3.1 Struktur Spreadsheet

Buat Google Spreadsheet baru dengan 5 Sheet berikut:

Sheet 1: Transaksi_Parkir (Utama)

Kolom Nama Tipe Formula/Keterangan
A ID_Transaksi Text =CONCATENATE("PKR",TEXT(NOW(),"yyMMddHHmmss"))
B No_Plat Text Input manual atau scan
C Jenis_Kendaraan Enum Motor, Mobil, Truk, Bus
D Waktu_Masuk DateTime =NOW()
E Waktu_Keluar DateTime Auto saat pembayaran
F Durasi_Jam Number =IF(E2="","",ROUND((E2-D2)*24,2))
G Tarif_Per_Jam Currency VLOOKUP ke Master_Tarif
H Total_Biaya Currency =IF(F2="","",F2*G2)
I Status_Pembayaran Enum Belum, Pending, Lunas
J Metode_Pembayaran Enum Tunai, QRIS, Debit, Kartu

Sheet 2: Master_Tarif

Jenis_Kendaraan Tarif_Jam_Pertama Tarif_Jam_Berikutnya Maksimum_Harian
Motor3000200015000
Mobil5000300025000
Truk8000500040000
Bus10000700050000

💻 4. Kode Program

4.1 Arduino Controller

🎛️ parking_controller.ino
#include <ArduinoJson.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Inisialisasi LCD (alamat 0x27 atau 0x3F)
LiquidCrystal_I2C lcd(0x27, 16, 2);

// Pin Definitions
#define TRIG_PIN 9
#define ECHO_PIN 10
#define RELAY_BARRIER 5
#define LED_GREEN 6
#define LED_RED 7
#define BUZZER 8
#define BTN_ENTRY 3
#define BTN_EXIT 4

// Variables
bool vehiclePresent = false;
unsigned long lastDetection = 0;
const unsigned long DEBOUNCE_DELAY = 3000;

void setup() {
    Serial.begin(9600);
    
    // Pin modes
    pinMode(TRIG_PIN, OUTPUT);
    pinMode(ECHO_PIN, INPUT);
    pinMode(RELAY_BARRIER, OUTPUT);
    pinMode(LED_GREEN, OUTPUT);
    pinMode(LED_RED, OUTPUT);
    pinMode(BUZZER, OUTPUT);
    pinMode(BTN_ENTRY, INPUT_PULLUP);
    pinMode(BTN_EXIT, INPUT_PULLUP);
    
    // Initial states
    digitalWrite(RELAY_BARRIER, HIGH); // Active LOW
    digitalWrite(LED_RED, HIGH);
    
    // LCD Setup
    lcd.init();
    lcd.backlight();
    lcd.setCursor(0,0);
    lcd.print("System Ready");
    
    // Interrupts
    attachInterrupt(digitalPinToInterrupt(BTN_ENTRY), entryISR, FALLING);
    attachInterrupt(digitalPinToInterrupt(BTN_EXIT), exitISR, FALLING);
    
    Serial.println("{\"status\":\"ready\",\"device\":\"GATE_01\"}");
}

void loop() {
    // Baca sensor ultrasonik
    long distance = readUltrasonic();
    
    // Deteksi kendaraan
    if (distance < 20 && !vehiclePresent) {
        vehiclePresent = true;
        lastDetection = millis();
        
        digitalWrite(LED_GREEN, HIGH);
        digitalWrite(LED_RED, LOW);
        tone(BUZZER, 1000, 200);
        
        // Kirim ke Raspberry Pi
        StaticJsonDocument<200> doc;
        doc["event"] = "vehicle_detected";
        doc["distance"] = distance;
        doc["timestamp"] = millis();
        
        serializeJson(doc, Serial);
        Serial.println();
        
        lcd.clear();
        lcd.print("Kendaraan Terdeteksi");
    }
    
    // Reset deteksi
    if (distance > 30 && vehiclePresent && 
        (millis() - lastDetection > DEBOUNCE_DELAY)) {
        vehiclePresent = false;
        digitalWrite(LED_GREEN, LOW);
        digitalWrite(LED_RED, HIGH);
        lcd.clear();
        lcd.print("Menunggu...");
    }
    
    // Cek command dari Raspberry Pi
    if (Serial.available()) {
        String command = Serial.readStringUntil('\n');
        processCommand(command);
    }
    
    delay(100);
}

long readUltrasonic() {
    digitalWrite(TRIG_PIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);
    return pulseIn(ECHO_PIN, HIGH) * 0.034 / 2;
}

void processCommand(String cmd) {
    StaticJsonDocument<512> doc;
    DeserializationError error = deserializeJson(doc, cmd);
    
    if (error) return;
    
    const char* action = doc["action"];
    
    if (strcmp(action, "open_barrier") == 0) {
        int duration = doc["duration"] | 5;
        openBarrier(duration);
        
        // Konfirmasi
        StaticJsonDocument<200> resp;
        resp["status"] = "barrier_opened";
        serializeJson(resp, Serial);
        Serial.println();
    }
    else if (strcmp(action, "print_status") == 0) {
        lcd.clear();
        lcd.print(doc["success"] ? "Print OK" : "Print Failed");
    }
}

void openBarrier(int seconds) {
    digitalWrite(RELAY_BARRIER, LOW); // Buka
    tone(BUZZER, 1500, 500);
    lcd.clear();
    lcd.print("Barrier Terbuka");
    
    delay(seconds * 1000);
    
    digitalWrite(RELAY_BARRIER, HIGH); // Tutup
    lcd.clear();
    lcd.print("Silahkan Masuk");
}

// Interrupt Service Routines
volatile bool entryFlag = false;
volatile bool exitFlag = false;

void entryISR() { entryFlag = true; }
void exitISR() { exitFlag = true; }

void handleButtons() {
    if (entryFlag) {
        entryFlag = false;
        StaticJsonDocument<200> doc;
        doc["event"] = "entry_pressed";
        doc["gate"] = "GATE_01";
        serializeJson(doc, Serial);
        Serial.println();
    }
    
    if (exitFlag) {
        exitFlag = false;
        StaticJsonDocument<200> doc;
        doc["event"] = "exit_pressed";
        doc["gate"] = "GATE_01";
        serializeJson(doc, Serial);
        Serial.println();
    }
}

4.2 Google Apps Script (Backend API)

⚡ Code.gs
const SPREADSHEET_ID = '1ABC123XYZ...'; // Ganti dengan ID Anda
const SHEET_NAMES = {
    TRANSAKSI: 'Transaksi_Parkir',
    TARIF: 'Master_Tarif',
    LOG: 'Log_Hardware'
};

// Entry point untuk HTTP requests
function doGet(e) {
    const action = e.parameter.action;
    
    try {
        switch(action) {
            case 'createEntry':
                return createParkingEntry(e.parameter);
            case 'processExit':
                return processParkingExit(e.parameter);
            case 'getTarif':
                return getTarif(e.parameter.jenis);
            case 'checkStatus':
                return checkPaymentStatus(e.parameter.id);
            default:
                return jsonResponse({error: 'Invalid action'}, 400);
        }
    } catch (error) {
        logEvent('API_ERROR', action, error.toString());
        return jsonResponse({error: error.toString()}, 500);
    }
}

function doPost(e) {
    const data = JSON.parse(e.postData.contents);
    
    switch(data.action) {
        case 'confirmPayment':
            return confirmPayment(data);
        case 'logHardware':
            return logHardwareEvent(data);
        default:
            return jsonResponse({error: 'Invalid action'}, 400);
    }
}

// Core Functions
function createParkingEntry(params) {
    const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    const sheet = ss.getSheetByName(SHEET_NAMES.TRANSAKSI);
    
    const timestamp = new Date();
    const idTxn = 'PKR' + Utilities.formatDate(timestamp, 'Asia/Jakarta', 'yyMMddHHmmss');
    
    // Generate QR data
    const qrPayload = JSON.stringify({
        id: idTxn,
        plat: params.plat || 'UNKNOWN',
        time: timestamp.toISOString()
    });
    
    const row = [
        idTxn,
        params.plat || 'UNKNOWN',
        params.jenis || 'Mobil',
        timestamp,
        '',  // waktu keluar
        '',  // durasi
        getTarifValue(params.jenis || 'Mobil'),
        '',  // total
        'Belum',
        '',
        'SYSTEM',
        params.gerbang || 'GATE_01',
        qrPayload,
        'Auto-generated',
        timestamp
    ];
    
    sheet.appendRow(row);
    logEvent(params.gerbang || 'GATE_01', 'ENTRY_CREATED', idTxn);
    
    return jsonResponse({
        success: true,
        id_transaksi: idTxn,
        waktu_masuk: Utilities.formatDate(timestamp, 'Asia/Jakarta', 'dd/MM/yy HH:mm:ss'),
        qr_data: qrPayload,
        plat: params.plat || 'UNKNOWN'
    });
}

function processParkingExit(params) {
    const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    const sheet = ss.getSheetByName(SHEET_NAMES.TRANSAKSI);
    const data = sheet.getDataRange().getValues();
    
    const searchId = params.id || params.plat;
    
    for (let i = 1; i < data.length; i++) {
        if (data[i][0] === searchId || data[i][1] === searchId) {
            // Cek sudah keluar belum
            if (data[i][4] !== '') {
                return jsonResponse({error: 'Kendaraan sudah keluar'}, 400);
            }
            
            const waktuKeluar = new Date();
            const waktuMasuk = new Date(data[i][3]);
            const durasiMs = waktuKeluar - waktuMasuk;
            const durasiJam = Math.ceil(durasiMs / (1000 * 60 * 60));
            const tarif = data[i][6];
            const total = durasiJam * tarif;
            
            // Update row
            sheet.getRange(i+1, 5).setValue(waktuKeluar);
            sheet.getRange(i+1, 6).setValue(durasiJam);
            sheet.getRange(i+1, 8).setValue(total);
            
            return jsonResponse({
                success: true,
                id_transaksi: data[i][0],
                plat: data[i][1],
                waktu_masuk: Utilities.formatDate(waktuMasuk, 'Asia/Jakarta', 'dd/MM/yy HH:mm'),
                waktu_keluar: Utilities.formatDate(waktuKeluar, 'Asia/Jakarta', 'dd/MM/yy HH:mm'),
                durasi_jam: durasiJam,
                tarif_per_jam: tarif,
                total_biaya: total,
                status: data[i][8]
            });
        }
    }
    
    return jsonResponse({error: 'Data tidak ditemukan'}, 404);
}

function confirmPayment(data) {
    const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    const sheet = ss.getSheetByName(SHEET_NAMES.TRANSAKSI);
    const rows = sheet.getDataRange().getValues();
    
    for (let i = 1; i < rows.length; i++) {
        if (rows[i][0] === data.id_transaksi) {
            const now = new Date();
            sheet.getRange(i+1, 5).setValue(now); // Waktu keluar
            sheet.getRange(i+1, 9).setValue('Lunas');
            sheet.getRange(i+1, 10).setValue(data.metode || 'Tunai');
            
            logEvent('PAYMENT', 'PAYMENT_CONFIRMED', data.id_transaksi);
            
            return jsonResponse({
                success: true,
                message: 'Pembayaran berhasil',
                barrier_open: true,
                waktu_keluar: Utilities.formatDate(now, 'Asia/Jakarta', 'dd/MM/yy HH:mm:ss')
            });
        }
    }
    
    return jsonResponse({error: 'Transaksi tidak ditemukan'}, 404);
}

// Helper Functions
function getTarifValue(jenis) {
    const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    const sheet = ss.getSheetByName(SHEET_NAMES.TARIF);
    const data = sheet.getDataRange().getValues();
    
    for (let i = 1; i < data.length; i++) {
        if (data[i][0] === jenis) return data[i][1];
    }
    return 5000; // default
}

function logEvent(device, event, data) {
    const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
    const sheet = ss.getSheetByName(SHEET_NAMES.LOG);
    sheet.appendRow([new Date(), device, event, data, 'SUCCESS']);
}

function jsonResponse(data, statusCode) {
    return ContentService.createTextOutput(JSON.stringify(data))
        .setMimeType(ContentService.MimeType.JSON);
}

4.3 Raspberry Pi Gateway (Python)

🐍 gateway.py
#!/usr/bin/env python3
"""
Raspberry Pi Gateway - Bridge Hardware ke Cloud
"""

import serial
import requests
import json
import time
import threading
import qrcode
from io import BytesIO
from flask import Flask, request, jsonify
from escpos.printer import Usb
from escpos.exceptions import USBNotFoundError

# CONFIG
APPS_SCRIPT_URL = "https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec"
SERIAL_PORT = "/dev/ttyUSB0"  # atau /dev/ttyACM0
BAUD_RATE = 9600

app = Flask(__name__)

class ParkingSystem:
    def __init__(self):
        self.arduino = None
        self.printer = None
        self.connect_hardware()
        
    def connect_hardware(self):
        # Koneksi Arduino
        try:
            self.arduino = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
            print(f"✓ Arduino connected on {SERIAL_PORT}")
        except Exception as e:
            print(f"✗ Arduino error: {e}")
            
        # Koneksi Printer
        try:
            self.printer = Usb(0x0416, 0x5011)  # Ganti VID/PID sesuai printer
            print("✓ Printer connected")
        except USBNotFoundError:
            print("✗ Printer not found, using simulation mode")
            
    def arduino_listener(self):
        """Thread untuk listen data dari Arduino"""
        while True:
            if self.arduino and self.arduino.in_waiting > 0:
                try:
                    line = self.arduino.readline().decode('utf-8').strip()
                    data = json.loads(line)
                    self.handle_hardware_event(data)
                except Exception as e:
                    print(f"Error: {e}")
            time.sleep(0.1)
            
    def handle_hardware_event(self, data):
        event = data.get('event')
        print(f"Hardware Event: {event}")
        
        if event == 'entry_pressed':
            self.create_entry_flow()
        elif event == 'exit_pressed':
            self.prepare_exit_flow()
        elif event == 'vehicle_detected':
            print(f"Vehicle at {data.get('distance')}cm")
            
    def create_entry_flow(self):
        """Flow kendaraan masuk"""
        try:
            # Bisa ditambahkan input dari GPIO/display
            plat = "B1234ABC"  # Placeholder
            
            response = requests.get(
                APPS_SCRIPT_URL,
                params={
                    'action': 'createEntry',
                    'plat': plat,
                    'jenis': 'Mobil',
                    'gerbang': 'GATE_01'
                },
                timeout=10
            )
            
            result = response.json()
            
            if result.get('success'):
                self.print_entry_ticket(result)
                self.send_to_arduino({
                    'action': 'open_barrier',
                    'duration': 10
                })
                print(f"✓ Entry: {result['id_transaksi']}")
                
        except Exception as e:
            print(f"✗ Entry error: {e}")
            
    def print_entry_ticket(self, data):
        """Cetak karcis masuk"""
        if not self.printer:
            print("SIMULATION PRINT:", data)
            return
            
        try:
            p = self.printer
            
            # Header
            p.set(align='center', bold=True, double_height=True)
            p.text("PARKIR MALL\nCENTER\n")
            p.set(align='center', bold=False, double_height=False)
            p.text("Jl. Sudirman No. 123\n")
            p.text("------------------------\n")
            
            # Info
            p.set(align='left')
            p.text(f"No: {data['id_transaksi']}\n")
            p.text(f"Plat: {data['plat']}\n")
            p.text(f"Masuk: {data['waktu_masuk']}\n\n")
            
            # QR Code
            qr = qrcode.QRCode(version=1, box_size=6, border=2)
            qr.add_data(data['qr_data'])
            qr.make(fit=True)
            img = qr.make_image(fill_color="black", back_color="white")
            
            # Convert ke format printer
            img_buffer = BytesIO()
            img.save(img_buffer, format='PNG')
            img_buffer.seek(0)
            
            p.image(img_buffer)
            p.text("\n")
            
            # Footer
            p.set(align='center')
            p.text("------------------------\n")
            p.text("Simpan karcis ini\n")
            p.text("Kehilangan = Denda 50rb\n\n\n")
            p.cut()
            
        except Exception as e:
            print(f"Print error: {e}")
            
    def send_to_arduino(self, data):
        """Kirim command ke Arduino"""
        if self.arduino:
            self.arduino.write(json.dumps(data).encode() + b'\n')

# Flask Routes untuk AppSheet Webhook
system = ParkingSystem()

@app.route('/api/barrier/open', methods=['POST'])
def api_open_barrier():
    data = request.json
    system.send_to_arduino({
        'action': 'open_barrier',
        'duration': data.get('duration', 5)
    })
    return jsonify({"status": "success"})

@app.route('/api/print/receipt', methods=['POST'])
def api_print_receipt():
    data = request.json
    # Implementasi print receipt pembayaran
    return jsonify({"status": "printed"})

@app.route('/health', methods=['GET'])
def health():
    return jsonify({
        "status": "running",
        "arduino": system.arduino is not None,
        "printer": system.printer is not None
    })

if __name__ == '__main__':
    # Start Arduino listener
    listener = threading.Thread(target=system.arduino_listener)
    listener.daemon = True
    listener.start()
    
    # Run Flask
    print("Gateway running on port 5000")
    app.run(host='0.0.0.0', port=5000)

📱 5. Konfigurasi AppSheet

5.1 Setup Awal

  1. Buka AppSheet.com
  2. Klik Create → Start with your own data
  3. Pilih Google Sheets yang sudah dibuat
  4. AppSheet akan otomatis mendeteksi struktur data

5.2 Konfigurasi Kolom (Table: Transaksi_Parkir)

Kolom Tipe Setting Penting
ID_Transaksi Text Key=YES, Editable=NO, Initial Value: CONCATENATE("PKR",TEXT(NOW(),"yyMMddHHmmss"))
No_Plat Text Required=YES, Input Mode: Barcode (untuk scan)
Jenis_Kendaraan Enum Values: Motor,Mobil,Truk,Bus, Display: Buttons
Waktu_Masuk DateTime Initial Value: NOW(), Editable: NO
Total_Biaya Price App Formula: IF([Durasi_Jam]<=1,[Tarif_Per_Jam],[Tarif_Per_Jam]+([Durasi_Jam]-1)*[Tarif_Per_Jam]*0.8)
Status_Pembayaran Enum Color: Belum=Red, Pending=Orange, Lunas=Green

5.3 Views Configuration

💡 Tips: Buat 3 view utama: Entry Form (untuk masuk), Exit Form (untuk bayar), dan Monitor (dashboard real-time).

View: Entry Parkir (Form)

  • Type: Form
  • Position: Left
  • Target: Transaksi_Parkir
  • Condition: USERROLE() = "EntryOperator"

View: Exit & Pembayaran (Form)

  • Type: Form (Edit)
  • Position: Left
  • Target: Transaksi_Parkir
  • Condition: USERROLE() = "ExitOperator"

5.4 Actions & Automation

🔔 Webhook Actions
Action: Proses Pembayaran Lengkap
├── Step 1: Set [Status_Pembayaran] = "Lunas"
├── Step 2: Set [Waktu_Keluar] = NOW()
├── Step 3: Call Webhook: Open Barrier
│   └── URL: http://[IP_RPI]:5000/api/barrier/open
│   └── Body: {"duration": 10, "transaction_id": [ID_Transaksi]}
└── Step 4: Call Webhook: Print Receipt
    └── URL: http://[IP_RPI]:5000/api/print/receipt
    └── Body: {
        "transaction": [ID_Transaksi],
        "plat": [No_Plat],
        "biaya": [Total_Biaya],
        "metode": [Metode_Pembayaran]
    }

💳 6. Sistem Pembayaran

6.1 Opsi Pembayaran

Metode Integrasi Kompleksitas
Tunai Manual input di AppSheet ⭐ Mudah
QRIS Midtrans/Xendit API ⭐⭐⭐ Medium
Kartu Debit EDC Machine (standalone) ⭐⭐ Medium
Member Card RFID + Database Member ⭐⭐⭐⭐ Advanced

6.2 Flow Pembayaran QRIS (Midtrans)

Scan QR Hitung Biaya Generate QRIS Scan Customer Callback Open Gate
📋 Catatan Implementasi QRIS Untuk produksi, daftarkan bisnis ke Midtrans atau Xendit, dapatkan Server Key dan Client Key, lalu implementasikan webhook callback untuk konfirmasi pembayaran otomatis.

🚀 7. Deployment & Testing

7.1 Pre-Deployment Checklist

  • Test semua sensor (ultrasonik, IR, button) individual
  • Verifikasi koneksi printer (test print)
  • Cek koneksi internet di lokasi (minimal 10 Mbps)
  • Backup spreadsheet dan dokumentasikan ID
  • Siapkan power backup (UPS) untuk Raspberry Pi

7.2 Systemd Service (Auto-start)

⚙️ /etc/systemd/system/parking.service
[Unit]
Description=Parking Gateway Service
After=network.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/parking
Environment="PYTHONPATH=/home/pi/parking"
ExecStart=/usr/bin/python3 /home/pi/parking/gateway.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
⌨️ Command Install
sudo cp parking.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable parking.service
sudo systemctl start parking.service
sudo systemctl status parking.service  # Cek status

7.3 Troubleshooting

Masalah Penyebab Solusi
Arduino tidak terdeteksi Permission serial port sudo usermod -a -G dialout pi lalu reboot
Printer tidak print Driver/USB ID salah Cek lsusb, install printer-driver-escpr
Apps Script timeout Loop atau data besar Optimize kode, gunakan batch processing
QR tidak terbaca Print quality buruk Perbesar QR (min 3x3cm), bersihkan print head
Barrier tidak buka Relay wiring Cek active low/high, test dengan multimeter
⚠️ Safety First! Pastikan barrier gate memiliki sensor safety (loop detector atau safety edge) untuk mencegah kecelakaan saat menutup.

✅ Kesimpulan

Sistem parkir terintegrasi ini menggabungkan kekuatan hardware lokal (Arduino + Raspberry Pi) dengan kemudahan cloud-based management (Google Sheets + AppSheet). Dengan biaya setup yang relatif terjangkau (~Rp 3-5 juta per gerbang), Anda mendapatkan sistem yang:

  • Real-time: Data tersinkronisasi instant ke cloud
  • Mobile: Monitoring dari smartphone dimana saja
  • Scalable: Mudah ditambah gerbang baru
  • Customizable: Tarif dan aturan fleksibel
  • Automated: Minimal intervensi manual
🎉 Selamat mencoba! Jika ada kendala, cek log di Google Sheets (sheet Log_Hardware) dan pastikan semua koneksi kabel stabil.

Komentar