9. ünite / 10 · yaklaşık 4 saat
Flask ile web kontrol paneli
Pi'de küçük bir HTTP sunucusu: yönlendirmeler, JSON, girdi doğrulama, belirteç ve güvenlik bekçisi.
Bu ünitenin sonunda
- Flask ile GET ve POST uç noktaları yazmak ve JSON döndürmek
- Girdiyi doğrulayıp doğru HTTP durum kodlarını (200, 400, 401, 404, 405) vermek
- Bağlantı kesilince robotu durduran bir bekçi (watchdog) kurmak
- Tarayıcıdan basılı tutulan düğmeyle güvenli sürüş sağlamak
Ders notları
Flask, birkaç satırla HTTP sunucusu yazdıran bir Python kütüphanesidir. Pi'de çalışır, aynı ağdaki telefon ya da dizüstü http://raspberrypi.local:5000 adresinden paneli açar.
Uç noktalar
GET /: panelin HTML sayfası.GET /api/status: robotun durumu (komut, hız, pil) JSON olarak.POST /api/drive:{"cmd": "forward", "speed": 0.5}.
Sunucu gelen her şeye şüpheyle bakar: bilinmeyen komut ya da 0–1 dışındaki hız 400, belirteç yoksa 401, yanlış yöntem 405 döner. Doğrulama robotun ilk güvenlik katmanıdır.
Bekçi
Wi-Fi koparsa son komut "ileri" olarak kalır ve robot duvara gider. Çözüm: tarayıcı basılı tutulan düğme için komutu her 200 ms'de bir yeniden yollar, sunucudaki bekçi iş parçacığı 1 s komut gelmezse robotu durdurur. Bu desen sanayide deadman switch olarak bilinir.
Güvenlik
Panel yalnız yerel ağda çalışmalı. Belirteç koda değil ortam değişkenine yazılır. Kamera görüntüsü eklenecekse MJPEG akışı ayrı bir uç noktadır ve kare hızı sınırlandırılır.
Simülasyon
Flask kontrol paneli ve bekçi (watchdog)
Tarayıcıdaki panelden Pi'deki küçük HTTP sunucusuna istek gönder. Yanıt kodlarını (200, 400, 401, 405), belirteç denetimini ve bağlantı kesilince robotu durduran bekçiyi dene.
Robot: stop · hız 0,00
Kendin dene
Panelde ileri düğmesine basılı tut ve bırak: bekçi açıkken ve kapalıyken ne oluyor? "Bağlantıyı kes" ile Wi-Fi kopmasını dene. Belirteci aç ve belirteçsiz istek gönder. Hızı 1,5 yapıp hangi kodu aldığını, "jump" komutuyla ne döndüğünü not et.
Kod
from flask import Flask, jsonify, request, abort
import threading, time, os
app = Flask(__name__)
TOKEN = os.environ["ROBOT_TOKEN"] # belirteç koda değil ortama
state = {"cmd": "stop", "speed": 0.0, "t": 0.0}
lock = threading.Lock()
@app.post("/api/drive")
def drive():
if request.headers.get("X-Token") != TOKEN:
abort(401)
body = request.get_json(silent=True) or {}
cmd, speed = body.get("cmd"), body.get("speed", 0.5)
if cmd not in {"forward", "back", "left", "right", "stop"} or not 0 <= speed <= 1:
return jsonify(error="bad command"), 400
with lock:
state.update(cmd=cmd, speed=0 if cmd == "stop" else speed, t=time.monotonic())
return jsonify(ok=True, **state)
def watchdog():
while True: # 1 s komut gelmezse dur
with lock:
if state["cmd"] != "stop" and time.monotonic() - state["t"] > 1.0:
state.update(cmd="stop", speed=0.0)
time.sleep(0.1)
threading.Thread(target=watchdog, daemon=True).start()
app.run(host="0.0.0.0", port=5000) # yalnız yerel ağda; internete açma// Bekçi 1 s'de durdurur; bu yüzden basılıyken her 200 ms'de yeniden gönder
let timer = null
const send = (cmd) => fetch('/api/drive', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Token': localStorage.token },
body: JSON.stringify({ cmd, speed: Number(speedInput.value) }),
})
function hold(cmd) { send(cmd); timer = setInterval(() => send(cmd), 200) }
function release() { clearInterval(timer); send('stop') } // bırakınca hemen dur
document.querySelectorAll('[data-cmd]').forEach((b) => {
b.addEventListener('pointerdown', () => hold(b.dataset.cmd))
b.addEventListener('pointerup', release)
b.addEventListener('pointerleave', release)
})Sık yapılan hatalar
- Flask geliştirme sunucusunu internete açmak: yalnız yerel ağ; dışarıdan erişim gerekiyorsa VPN kullan.
- Komutu tek seferlik göndermek (bas, bırak): bağlantı koparsa durma komutu hiç gitmez.
- Girdiyi doğrulamadan motora vermek: speed = 5 motoru zorlar.
- Belirteci kaynak koda yazıp depoya göndermek.
Yazılım mühendisliği bağlantısı
HTTP arayüzü bir sözleşmedir: uç noktaları, gövdeleri ve durum kodlarını belgeleyip test et.
- Flask'ın test istemcisiyle (
app.test_client()) her durum kodunu sınayan testler yaz. - Yönlendirme mantığını donanımdan ayır: benzetimdeki
handle()gibi saf bir fonksiyon.
Kendini sına
POST /api/drive {"cmd": "jump"} için uygun durum kodu?
GET /api/drive (yalnız POST tanımlı) ne döner?
Bekçi neden gerekli?
Belirteç (token) nerede saklanmalı?
Bu üniteden sonra bitirme projesinde
- Panel ve durum makinesi: Flask paneli, bekçi ve next_mode() geçiş fonksiyonu; her kenar için test.