From 68c89110bb9eeef253acd27d843befa14a56c054 Mon Sep 17 00:00:00 2001 From: ske087 Date: Mon, 10 Mar 2025 17:27:22 +0200 Subject: [PATCH] final --- ESP_api/ESP_api.ino | 248 ++++++++++++++++++++++++++++++++ server_api/app.py | 164 +++++++++++++++++++++ server_api/instance/logs.db | Bin 0 -> 16384 bytes server_api/templates/board.html | 145 +++++++++++++++++++ server_api/templates/index.html | 68 +++++++++ 5 files changed, 625 insertions(+) create mode 100644 ESP_api/ESP_api.ino create mode 100644 server_api/app.py create mode 100644 server_api/instance/logs.db create mode 100644 server_api/templates/board.html create mode 100644 server_api/templates/index.html diff --git a/ESP_api/ESP_api.ino b/ESP_api/ESP_api.ino new file mode 100644 index 0000000..caac68b --- /dev/null +++ b/ESP_api/ESP_api.ino @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include "esp_mac.h" +//ver 1.03 +// Constants for Access Point mode +const char* ap_ssid = "ESP32-AP"; +const char* ap_password = "12345678"; + +// Pin definitions +const int userLedPin = 8; +const int buttonPin = 0; +const int relayPins[4] = {10, 11, 22, 23}; +const int inputPins[4] = {1, 2, 3, 15}; + +// Create a WebServer on port 80 +WebServer server(80); + +// Variables to store WiFi settings +String ssid, password, static_ip, netmask, gateway, dns, hostname; +String logServerIP, logServerPort; +bool isAPMode = false; + +// Variable to keep track of the last log time +unsigned long lastLogTime = 0; + +// Function to send logs to the log server +void sendLog(String message) { + if (WiFi.status() == WL_CONNECTED && logServerIP.length() > 0 && logServerPort.length() > 0) { + HTTPClient http; + String url = "http://" + logServerIP + ":" + logServerPort + "/log"; + http.begin(url); + http.addHeader("Content-Type", "application/json"); + String payload = "{\"hostname\": \"" + hostname + "\", \"ip_address\": \"" + WiFi.localIP().toString() + "\", \"message\": \"" + message + "\"}"; + int httpResponseCode = http.POST(payload); + http.end(); + if (httpResponseCode > 0) { + Serial.println("Log sent successfully: " + message); + } else { + Serial.println("Error sending log: " + message); + } + } +} + +// Handle the root URL ("/") and serve the configuration page +void handleRoot() { + String macAddress = getDefaultMacAddress(); + String html = "Configuration"; + html += ""; + + if (isAPMode) { + html += "

Board Configuration

"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + html += "
"; + } + + html += ""; + server.send(200, "text/html", html); +} + +// Handle the save URL ("/save") and save the WiFi settings +void handleSave() { + ssid = server.arg("ssid"); + password = server.arg("password"); + static_ip = server.arg("static_ip"); + netmask = server.arg("netmask"); + gateway = server.arg("gateway"); + dns = server.arg("dns"); + hostname = server.arg("hostname"); + logServerIP = server.arg("log_server_ip"); + logServerPort = server.arg("log_server_port"); + + saveSettings(); + server.send(200, "text/html", "Settings saved. Device will restart in client mode."); + delay(2000); + ESP.restart(); +} + +// Handle the relay control URL ("/relay") and control the relays +void handleRelay() { + int relay = server.arg("relay").toInt() - 1; // Adjust for zero-based index + String action = server.arg("action"); + + if (relay >= 0 && relay < 4) { + if (action == "on") { + digitalWrite(relayPins[relay], HIGH); + sendLog("Relay " + String(relay + 1) + " turned ON"); + } else if (action == "off") { + digitalWrite(relayPins[relay], LOW); + sendLog("Relay " + String(relay + 1) + " turned OFF"); + } + } + + server.sendHeader("Location", "/"); + server.send(303); +} + +// Save the WiFi settings to EEPROM +void saveSettings() { + EEPROM.writeString(0, ssid); + EEPROM.writeString(32, password); + EEPROM.writeString(64, static_ip); + EEPROM.writeString(96, netmask); + EEPROM.writeString(128, gateway); + EEPROM.writeString(160, dns); + EEPROM.writeString(192, hostname); + EEPROM.writeString(224, logServerIP); + EEPROM.writeString(256, logServerPort); + EEPROM.commit(); +} + +// Load the WiFi settings from EEPROM +void loadSettings() { + ssid = EEPROM.readString(0); + password = EEPROM.readString(32); + static_ip = EEPROM.readString(64); + netmask = EEPROM.readString(96); + gateway = EEPROM.readString(128); + dns = EEPROM.readString(160); + hostname = EEPROM.readString(192); + logServerIP = EEPROM.readString(224); + logServerPort = EEPROM.readString(256); +} + +// Get the default MAC address of the ESP32 +String getDefaultMacAddress() { + String mac = ""; + unsigned char mac_base[6] = {0}; + + if (esp_efuse_mac_get_default(mac_base) == ESP_OK) { + char buffer[18]; + sprintf(buffer, "%02X:%02X:%02X:%02X:%02X:%02X", mac_base[0], mac_base[1], mac_base[2], mac_base[3], mac_base[4], mac_base[5]); + mac = buffer; + } + + return mac; +} + +void setup() { + Serial.begin(115200); + EEPROM.begin(512); + + pinMode(userLedPin, OUTPUT); + digitalWrite(userLedPin, LOW); + + pinMode(buttonPin, INPUT_PULLUP); + + for (int i = 0; i < 4; i++) { + pinMode(relayPins[i], OUTPUT); + digitalWrite(relayPins[i], LOW); + pinMode(inputPins[i], INPUT_PULLUP); + } + + loadSettings(); + WiFi.softAPdisconnect(true); + + if (digitalRead(buttonPin) == LOW || ssid.length() == 0) { + startAPMode(); + } else { + connectToWiFi(); + } +} + +void loop() { + server.handleClient(); + if (millis() - lastLogTime >= 30000) { + sendLog("Board is functioning"); + lastLogTime = millis(); + } +} + +void startAPMode() { + WiFi.softAP(ap_ssid, ap_password); + Serial.println("Access Point Started"); + Serial.print("IP Address: "); + Serial.println(WiFi.softAPIP()); + server.on("/", handleRoot); + server.on("/save", handleSave); + server.on("/relay", handleRelay); + server.begin(); + isAPMode = true; +} + +void connectToWiFi() { + IPAddress ip, gw, nm, dnsServer; + if (!ip.fromString(static_ip) || !gw.fromString(gateway) || !nm.fromString(netmask) || !dnsServer.fromString(dns)) { + startAPMode(); + return; + } + + WiFi.config(ip, gw, nm, dnsServer); + WiFi.setHostname(hostname.c_str()); + WiFi.begin(ssid.c_str(), password.c_str()); + + unsigned long startTime = millis(); + while (WiFi.status() != WL_CONNECTED) { + if (millis() - startTime >= 10000) { + startAPMode(); + return; + } + delay(500); + } + + Serial.println("Connected to WiFi."); + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + Serial.print("Hostname: "); + Serial.println(WiFi.getHostname()); + digitalWrite(userLedPin, HIGH); + isAPMode = false; + startWebServer(); +} + +void startWebServer() { + server.on("/", handleRoot); + server.on("/save", handleSave); + server.on("/relay", handleRelay); + server.begin(); +} + +void blinkLed() { + static unsigned long lastBlinkTime = 0; + static bool ledState = LOW; + unsigned long interval = isAPMode ? 1000 : 3000; + + if (millis() - lastBlinkTime >= interval) { + ledState = !ledState; + digitalWrite(userLedPin, ledState); + lastBlinkTime = millis(); + } +} \ No newline at end of file diff --git a/server_api/app.py b/server_api/app.py new file mode 100644 index 0000000..9b2cb2c --- /dev/null +++ b/server_api/app.py @@ -0,0 +1,164 @@ +from flask import Flask, request, jsonify, render_template, redirect, url_for +from flask_sqlalchemy import SQLAlchemy +from datetime import datetime, timedelta +import os +import threading +import requests + +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///logs.db' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +db = SQLAlchemy(app) + +class Log(db.Model): + id = db.Column(db.Integer, primary_key=True) + hostname = db.Column(db.String(100), nullable=False) + ip_address = db.Column(db.String(100), nullable=False) + message = db.Column(db.String(500), nullable=False) + timestamp = db.Column(db.DateTime, default=datetime.utcnow) + +# Create the database if it does not exist +if not os.path.exists('instance/logs.db'): + with app.app_context(): + db.create_all() + +# Flag to check if tables are created +tables_created = False + +@app.before_request +def create_tables(): + global tables_created + if not tables_created: + db.create_all() + tables_created = True + +@app.route('/log', methods=['POST']) +def log_message(): + data = request.get_json() + hostname = data.get('hostname') + ip_address = data.get('ip_address') + message = data.get('message') + if hostname and ip_address and message: + new_log = Log(hostname=hostname, ip_address=ip_address, message=message) + db.session.add(new_log) + db.session.commit() + return jsonify({'status': 'success', 'message': 'Log saved'}), 201 + return jsonify({'status': 'error', 'message': 'Invalid data'}), 400 + +@app.route('/logs', methods=['GET']) +def get_logs(): + logs = Log.query.all() + return jsonify([{'id': log.id, 'hostname': log.hostname, 'message': log.message, 'timestamp': log.timestamp} for log in logs]) + +@app.route('/cleanup', methods=['DELETE']) +def cleanup_logs(): + cutoff_date = datetime.utcnow() - timedelta(hours=24) + Log.query.filter(Log.timestamp < cutoff_date).delete() + db.session.commit() + return jsonify({'status': 'success', 'message': 'Old logs deleted'}) + +@app.route('/') +def index(): + # Get the latest log entry for each board + latest_logs = db.session.query(Log).order_by(Log.timestamp.desc()).all() + boards = {} + for log in latest_logs: + if log.hostname not in boards: + boards[log.hostname] = log + + # Determine the status of each board + board_status = [] + for hostname, log in boards.items(): + time_diff = datetime.utcnow() - log.timestamp + status = 'Online' if time_diff.total_seconds() <= 20 else 'Possible Offline' + board_status.append({'hostname': hostname, 'status': status, 'last_seen': log.timestamp}) + + return render_template('index.html', boards=board_status) + +@app.route('/board/') +def view_board(hostname): + logs = Log.query.filter_by(hostname=hostname).order_by(Log.timestamp.desc()).limit(50).all() + relay_status = ['off', 'off', 'off', 'off'] + input_status = ['off', 'off', 'off', 'off'] + relay_messages = ['', '', '', ''] + input_messages = ['', '', '', ''] + ip_address = logs[0].ip_address if logs else '' # Get the IP address from the logs + last_update = logs[0].timestamp if logs else None # Get the last update time + + # Determine the status of each relay and input based on the latest log messages + for log in logs: + for i in range(4): + if f"Relay {i + 1} turned ON" in log.message: + relay_status[i] = 'on' + relay_messages[i] = log.message + elif f"Relay {i + 1} turned OFF" in log.message: + relay_status[i] = 'off' + relay_messages[i] = log.message + if f"Input {i + 1} pressed" in log.message: + input_status[i] = 'on' + input_messages[i] = log.message + post_action_to_server(hostname, f"Input {i + 1} pressed") + elif f"Input {i + 1} released" in log.message: + input_status[i] = 'off' + input_messages[i] = log.message + post_action_to_server(hostname, f"Input {i + 1} released") + + return render_template('board.html', hostname=hostname, ip_address=ip_address, logs=logs, relay_status=relay_status, input_status=input_status, relay_messages=relay_messages, input_messages=input_messages, last_update=last_update) + +@app.route('/delete_board/', methods=['POST']) +def delete_board(hostname): + Log.query.filter_by(hostname=hostname).delete() + db.session.commit() + return redirect(url_for('index')) + +@app.route('/board//logs', methods=['GET']) +def get_board_logs(hostname): + logs = Log.query.filter_by(hostname=hostname).order_by(Log.timestamp.desc()).all() + return jsonify({'logs': [{'timestamp': log.timestamp, 'message': log.message} for log in logs]}) + +@app.route('/board//relay//', methods=['POST']) +def control_relay(hostname, relay, action): + log = Log.query.filter_by(hostname=hostname).order_by(Log.timestamp.desc()).first() + if log: + ip_address = log.ip_address + url = f"http://{ip_address}/relay" + payload = { + "relay": relay, + "action": action + } + try: + response = requests.post(url, data=payload) + if response.status_code == 200: + return redirect(url_for('view_board', hostname=hostname)) + else: + return f"Failed to control relay: {response.text}", 500 + except Exception as e: + return f"Error controlling relay: {e}", 500 + else: + return f"No logs found for hostname: {hostname}", 404 + +def post_action_to_server(hostname, action): + url = "http://your-server-url.com/action" + payload = { + "hostname": hostname, + "action": action + } + try: + response = requests.post(url, json=payload) + if response.status_code == 200: + print(f"Action posted successfully: {action}") + else: + print(f"Failed to post action: {action}") + except Exception as e: + print(f"Error posting action: {e}") + +def schedule_cleanup(): + with app.app_context(): + cutoff_date = datetime.utcnow() - timedelta(hours=24) + Log.query.filter(Log.timestamp < cutoff_date).delete() + db.session.commit() + threading.Timer(3600, schedule_cleanup).start() + +if __name__ == '__main__': + schedule_cleanup() # Start the cleanup scheduler + app.run(host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/server_api/instance/logs.db b/server_api/instance/logs.db new file mode 100644 index 0000000000000000000000000000000000000000..a54ffb64053dda99d8f8accdeb37ad6b99e51dfe GIT binary patch literal 16384 zcmeI1O>AS;702y-*zrefU?@Ys=qq#}1I*)of4tH{Y|g-xBr}-Iz?3hgBurh+B%{Pg zttwR=vFo<2DkKoQN|mY>7F{3~NVI~gyRNz@i%PB7u|l(g1^*iaR+5QT?vs#^?vpJ$ zdDhSW_kZV}bI0Ec(ecSPtP6p z`gA3KwmOfq)h?^xk&fg*av(X797ql%2a*HHf#g7PAUTj6NDh1{9JsPvtsgydr20a2 zwDzNm8yB}O;4#%7(Bdj(tBbuQ%J7eDq1hO&XQx(H=|LJ~E2mepm9tArcW2GUkGFP4 zn`@UgvU7`r{zHp{1V#-`SUD z&(3yc-kJI3jGw+b{p$2^`qrs`PyJ?Ud8$@@v-;C2ue^^h{Jclc)hb7i-LptLm!;rd z?aht#?DUFYLZ4vr1ZNr71@ndWxpY!F)wA&oom=lYQ)B`| zAJoy?TH9XFhCA7F&u>0E8g6Y4H!mEJqD0Otw^ouagWO!kfj$f3({4B2z9KVfJv4E&{=;;)B?w1Rk1&e2!O zU_#0j##`CHaiT5b^?bkHem3S#;fECkh!u_6q?+!Z@|!F}r(a=1~zjHOpW)!)8}G>8TNq!JZiyBaKuvR|XT* zNYpe-^>_^S%)l@qupO(F!9Wdq8;eXe8;`-BndRCT?kvre!9b1Fp`)Pbcnr3x3{lJl z+LEa<80K@Yu!XTyjmOAB`rIOHtPcJEvDv>>x^Hwx-4mVnIag z|EYbUoweR={jPPU)o8xe{CQI~-f#TA@l509xqr_6YOY`ZsQ#DwX8n%Ze`+t*2DKJY z=|~PF2a*HHfiK7bdMdIMV4PzME_0=gLz#Rs9)sEsjPMeJp(n~Yvh8upi3^^0dwfEXgt%~JB-i;H-&b&6GjNudoVx_9TtrtdxjCa454$0 z@r>kjGX8o@nCMyjS|=G%JOu zX?QUngKQ2AgEfrqFN1*^WfiIm-4~BRHU~x_4Ga2y8B9>aY#8}`FCK$zE^>*)r#0PM s1_L$N1Xyn9yYZOaxwOPmV2+A17^o4L@JZG80TYuWSeSZhsWev~0}SEEw*UYD literal 0 HcmV?d00001 diff --git a/server_api/templates/board.html b/server_api/templates/board.html new file mode 100644 index 0000000..a238aec --- /dev/null +++ b/server_api/templates/board.html @@ -0,0 +1,145 @@ + + + + + + ESP Board Details + + + + +
+

ESP Board Details - {{ hostname }}

+
+ +
+
+
+

Relay Status

+
    + {% for i in range(4) %} +
  • + Relay {{ i + 1 }} + {{ relay_status[i] }} +
    +
    + +
    +
    + +
    +
    +
  • + {% endfor %} +
+
+
+

Input Status

+
    + {% for i in range(4) %} +
  • + Input {{ i + 1 }} + {{ input_status[i] }} +
  • + {% endfor %} +
+
+
+ + +
+
+
+
+
Next update in 5 seconds
+ +
+
+
+ +
+

Logs

+
    + {% for log in logs %} +
  • + {{ log.timestamp }}: {{ log.message }} +
  • + {% endfor %} +
+
+
+
+ + + \ No newline at end of file diff --git a/server_api/templates/index.html b/server_api/templates/index.html new file mode 100644 index 0000000..55a5f9a --- /dev/null +++ b/server_api/templates/index.html @@ -0,0 +1,68 @@ + + + + + + ESP Board Status + + + + +
+

ESP Board Status

+
+ {% for board in boards %} +
+
+
+
{{ board.hostname }}
+

Status: {{ board.status }}

+

Last Seen: {{ board.last_seen }}

+ View Details +
+ +
+
+
+
+ {% endfor %} +
+
+ + \ No newline at end of file