In meiner Kindheit gab es bei einem Freund ein Spielzeug. Es war eine Kugel, die man schütteln musste; daraufhin ertönten Licht- und Soundeffekte, und man erhielt ein „Ja“ oder „Nein“ als Antwort. Ein beliebtes Spiel bestand darin, Fragen zu stellen und schließlich die vermeintliche Antwort aus der Kristallkugel zu erfahren. Natürlich war uns auch damals schon klar, dass es sich hierbei nicht um echte „Antworten“ handelte, sondern dass wohl irgendein Schaltkreis eine vermeintlich zufällige Antwort vermittelte. Trotzdem war es faszinierend, darüber nachzudenken.
Während der Überlegungen zu den TRNG Artikeln kam die Idee auf, einen solchen Zufallsgenerator mit echter Randomisierung umzusetzen. In diesem Beitrag sollen die Technik und die Umsetzung erläutert werden.
Die vorherigen Artikel, die zu diesem Projekt geführt haben, können hier noch einmal nachgelesen werden:
Inhalt
Hardware
Zunächst gilt es das Lesen und Übertragen von Gamma-Ereignisdaten von einem GDK101-Sensor mit Hilfe eines W5500-EVB-Pico, einem robusten Mikrocontroller-Board, das speziell für Netzwerkanwendungen konzipiert wurde zu bewerkstelligen. Wir werden den Aufbau des Codes detailliert durchgehen und erläutern, wie jeder Teil zur Gesamtfunktionalität des Systems beiträgt.
Hardware Setup
Das Herzstück des Systems ist der W5500-EVB-Pico, eine spezielle Version des Raspberry Pi Pico mit einem Ethernet-Netzwerk Modul. Der GDK101 ist der bekannte Gammastrahlungsdetektor, der Gamma-Ereignisse detektiert und als elektronische Signale ausgibt, welche wir auslesen und verarbeiten.
ADC-Konfiguration für den GDK101
Der erste Schritt in unserem Code ist das Einrichten des ADC (Analog-Digital-Umsetzer), um die analogen Signale vom GDK101 zu digitalisieren. Hier definieren wir den ADC auf einem bestimmten Pin, setzen den Schwellenwert fest, und bereiten einen Puffer für die Speicherung der Daten vor:
import machine
# Setup ADC
adc = machine.ADC(26) # ADC auf Pin 26 initialisieren
threshold_voltage = 120 # Schwellenspannung in mV
buffer_size = 500 # Größe des Puffers
samples = [0] * buffer_size # Puffer initialisieren
JavaScriptErkennung von Gamma-Ereignissen
Um zu bestimmen, ob ein gelesener Wert ein Gamma-Ereignis darstellt, überprüfen wir, ob der Spannungswert den Schwellenwert überschreitet:
def check_threshold(sample):
voltage = (sample * 3.3) / 65535 # Umrechnung des ADC-Werts in Spannung
return voltage > (threshold_voltage / 1000.0) # Vergleich mit dem Schwellenwert
JavaScriptDaten sammeln
Die collect_data()
-Funktion ist verantwortlich für das kontinuierliche Auslesen des ADC, das Überprüfen des Schwellenwerts und das Speichern der Daten, sobald ein Ereignis erkannt wird:
def collect_data():
print("Starting Data Collector Thread")
current_index = 0
trigger_found = False
while True:
sample = adc.read_u16() # ADC-Wert lesen
samples[current_index] = sample
if not trigger_found and check_threshold(sample):
trigger_time = utime.time() # Zeitstempel des Ereignisses
trigger_found = True
if trigger_found:
if current_index == buffer_size - 1:
data_window = {"timestamp": trigger_time, "data": samples[:]}
with queue_lock:
data_queue.append(data_window)
trigger_found = False
current_index = -1
print(data_window)
current_index = (current_index + 1) % buffer_size
JavaScriptDatenübertragung
Sobald Daten gesammelt sind, muss ein separater Thread sie sicher an einen Server übertragen. Wir benutzen hierfür HTTP-Requests:
def data_uploader():
while True:
utime.sleep(1)
if data_queue:
with queue_lock:
data_window = data_queue.pop(0)
try:
headers = {"content-type": "application/json", "Authorization": 'secret-token'}
response = urequests.post(
f"https://{HOST}/{ENDPOINT}",
json=data_window,
headers=headers
)
print("Upload successful:", response.text)
except Exception as e:
print("Failed to upload data:", e)
JavaScriptStarten des Systems
Zum Abschluss initialisieren wir das Gerät und starten die beiden Threads:
init_device()
_thread.start_new_thread(data_uploader, ())
_thread.start_new_thread(collect_data, ())
JavaScriptDer Gesamte Code für den GDK101-W5500-EVB-Pico
import network
import ntptime
import machine
from machine import Pin, SPI, WDT
import utime
import _thread
import urequests
import uping
import time
# API REST-Endpoint Configuration
HOST = "domain.tld"
ENDPOINT = "upload"
AUTH_TOKEN = 'secret-token'
# Setup ADC
adc = machine.ADC(26) # ADC auf Pin 26 initialisieren
threshold_voltage = 120 # Schwellenspannung in mV
buffer_size = 500 # Größe des Puffers
samples = [0] * buffer_size # Puffer initialisieren
data_queue = []
queue_lock = _thread.allocate_lock() # Create a lock for thread-safe operations on the queue
# Initialize Watchdog Timer with a timeout of 10000 milliseconds (10 seconds)
wdt = WDT(timeout=8000)
def check_threshold(sample):
voltage = (sample * 3.3) / 65535
return voltage > (threshold_voltage / 1000.0)
def collect_data():
print("Starting Data Collector Thread")
current_index = 0
trigger_found = False
while True:
sample = adc.read_u16()
samples[current_index] = sample
if not trigger_found and check_threshold(sample):
trigger_time = utime.time()
trigger_found = True
if trigger_found:
if current_index == buffer_size - 1:
data_window = {"timestamp": trigger_time, "data": samples[:]}
with queue_lock: # Acquire the lock before adding to queue
data_queue.append(data_window)
trigger_found = False
current_index = -1
print(data_window)
current_index = (current_index + 1) % buffer_size
wdt.feed() # Reset the watchdog timer
def data_uploader():
print("Starting Data Uploader Thread")
while True:
utime.sleep(1)
while data_queue:
with queue_lock: # Acquire the lock before accessing the queue
if data_queue:
data_window = data_queue.pop(0)
try:
headers = {
"content-type": "application/json",
"Authorization": AUTH_TOKEN
}
response = urequests.post(
f"https://{HOST}/{ENDPOINT}",
json=data_window,
headers=headers
)
print("Upload successful:", response.text)
except Exception as e:
print("Failed to upload data:", e)
wdt.feed() # Reset the watchdog timer
utime.sleep(1) # Throttle uploads
def w5x00_init():
spi = SPI(0, 2_000_000, mosi=Pin(19), miso=Pin(16), sck=Pin(18))
nic = network.WIZNET5K(spi, Pin(17), Pin(20))
nic.active(True)
nic.ifconfig(('192.168.178.56', '255.255.255.0', '192.168.178.1', '8.8.8.8'))
attempts = 0
while not nic.isconnected() and attempts < 10:
time.sleep(1)
attempts += 1
print("Attempting to connect:", attempts)
if not nic.isconnected():
raise Exception("Failed to connect to the network after multiple attempts")
print(nic.ifconfig())
def init_device():
w5x00_init()
uping.ping(HOST, count=1)
attempts = 0
while attempts < 5:
wdt.feed() # Reset the watchdog timer
try:
ntptime.host = "pool.ntp.org"
ntptime.settime()
print("NTP time synchronized")
return
except Exception as e:
print("Failed to synchronize NTP time:", e)
attempts += 1
time.sleep(2 ** attempts)
raise Exception("NTP time could not be synchronized after several attempts")
init_device()
_thread.start_new_thread(data_uploader, ())
collect_data()
PythonDie Flask WebApplication
Das Interface der Genie Application wird mittels Python-Flask bereitgestellt.
Diese Anwendung nutzt Flask-SocketIO für Echtzeit-Kommunikation und SQLAlchemy für die Datenverwaltung.
Die Flask-Anwendung wird zunächst mit einer Standardkonfiguration eingerichtet, einschließlich eines statischen Ordners für die Webressourcen und einer SQLite-Datenbank für die Speicherung der Daten. Wir konfigurieren Flask-SocketIO mit spezifischen CORS-Einstellungen, um sichere Cross-Origin-Anfragen zu ermöglichen.
Das Datenmodell besteht aus zwei Hauptklassen: GammaEventEntry
für die Aufzeichnung der Gamma-Ereignisse und ChatMessage
für die Verwaltung der Chat-Nachrichten. GammaEventEntry
speichert jedes Ereignis mit einem Zeitstempel, den Rohdaten als JSON und einem Bild des Datenplots als Textblob. ChatMessage
enthält Informationen zu jeder Chat-Nachricht, einschließlich des Benutzernamens und Zeitstempels.
Die Datenerfassung erfolgt über einen REST-Endpoint, der POST-Anfragen aufnimmt. Dieser Endpoint prüft das Authorization
-Token im Header, um sicherzustellen, dass nur autorisierte Benutzer Daten senden können. Wenn die Daten im JSON-Format vorliegen, werden sie zusammen mit einem generierten Plot in die Datenbank eingefügt. Bei erfolgreicher Verarbeitung wird eine Erfolgsmeldung zurückgesendet, ansonsten eine Fehlermeldung.
Die Anwendung nutzt SocketIO, um Daten in Echtzeit an alle verbundenen Clients zu senden. Sobald ein neuer Datenpunkt eingefügt wird, werden die neuesten 40 Einträge aus der Datenbank abgerufen und an die Clients gesendet. Dies ermöglicht eine dynamische und interaktive Benutzererfahrung, da Benutzer sofortige Updates sehen können.
Die Visualisierung der Daten erfolgt durch Matplotlib, das zur Erstellung von Plots verwendet wird. Diese Plots werden als PNG-Bilder gerendert, in Bytes umgewandelt und dann in einen Base64-String kodiert, der direkt über Web-Sockets gesendet werden kann.
Ein integriertes Chat-System ermöglicht es Benutzern, Nachrichten zu senden, die das System validiert und an alle verbundenen Clients übermittelt. Dieses System umfasst Funktionen zur Überprüfung auf Profanität, HTML-Escaping zur Verhinderung von Cross-Site-Scripting-Angriffen und eine Zeichenbegrenzung, um die Datenverwaltung zu vereinfachen.
Diese Flask-Anwendung stellt ein umfassendes System dar, das nicht nur Daten effizient sammelt und speichert, sondern diese auch in Echtzeit verarbeitet und visualisiert.
Der vollständige Code
from flask import Flask, request, jsonify, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from flask_socketio import SocketIO, emit
from datetime import datetime
import logging
from random import choice
from random_username.generate import generate_username
from datetime import datetime, timedelta
import threading
import time
import io
import base64
import matplotlib.pyplot as plt
import matplotlib
from PIL import Image
import time
from html import escape
from better_profanity import profanity
matplotlib.use("Agg")
# map IP addresses to their last message's timestamp
last_message_time_by_ip = {}
app = Flask(__name__, static_folder="static")
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
socketio = SocketIO(
app,
cors_allowed_origins=["https://decision.atlane.de"],
)
# Model definition
class GammaEventEntry(db.Model):
id = db.Column(db.Integer, primary_key=True)
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
data = db.Column(db.JSON, nullable=False)
image_plot = db.Column(db.Text) # Storing the image as a binary blob
class ChatMessage(db.Model):
id = db.Column(db.Integer, primary_key=True)
question = db.Column(db.String, nullable=False)
answer = db.Column(db.String, default="")
answer_image_plot = db.Column(db.Text)
username = db.Column(db.String, default="")
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
# Secret token for validation, should be kept secret and complex
SECRET_TOKEN = "secret-token"
def create_and_save_plot(data_dict):
plt.axis("off")
plt.plot(data_dict["data"], color="black")
buf = io.BytesIO()
plt.savefig(buf, format="png", transparent=True)
buf.seek(0)
plt.close()
image_base64 = base64.b64encode(buf.getvalue()).decode("utf-8")
return image_base64
def check_for_answers():
# Query for all chat messages with an empty answer
empty_answer_messages = ChatMessage.query.filter(ChatMessage.answer == "")
# Now check for each message if there are at least four corresponding GammaEventEntries
# with a timestamp greater than the timestamp of the message
for message in empty_answer_messages:
GammaEventEntries = (
GammaEventEntry.query.filter(GammaEventEntry.timestamp > message.timestamp)
.limit(4)
.all()
)
if len(GammaEventEntries) >= 4:
print("The answer is ready")
# Calculate Boolean Answer
answer = ""
timeValues = []
for entry in GammaEventEntries:
timeValues.append(entry.timestamp)
timeDiff1 = time.mktime(timeValues[1].timetuple()) - time.mktime(
timeValues[0].timetuple()
)
timeDiff2 = time.mktime(timeValues[3].timetuple()) - time.mktime(
timeValues[2].timetuple()
)
timeDiffRes = timeDiff1 - timeDiff2
if timeDiffRes < 0:
answer = "Nope..."
else:
answer = "Yeah!"
# Process images if there are at least four gamma event entries
images = []
for entry in GammaEventEntries:
image_data = entry.image_plot
if image_data:
# Convert base64 string to Image
image_bytes = base64.b64decode(image_data)
image = Image.open(io.BytesIO(image_bytes))
images.append(image)
if images:
# Combine images horizontally
total_width = sum(img.width for img in images)
max_height = max(img.height for img in images)
# Create a new image with transparency (note the 'RGBA')
combined_image = Image.new("RGBA", (total_width, max_height))
x_offset = 0
for img in images:
# Ensure images have an alpha layer
img = img.convert("RGBA")
combined_image.paste(img, (x_offset, 0), img)
x_offset += img.width
# Convert combined image to base64
buffered = io.BytesIO()
combined_image.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
message.answer = answer
message.answer_image_plot = img_str
db.session.commit() # Commit the update to the database
# Broadcast client message
with app.app_context():
socketio.emit(
"receive_message",
{
"id": message.id,
"message": message.question,
"username": message.username,
"answer": message.answer,
"answer_image_plot": message.answer_image_plot,
"timestamp": message.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
},
)
@app.route("/upload", methods=["POST"])
def upload_data():
token = request.headers.get("Authorization")
if token != SECRET_TOKEN:
logging.warning("Unauthorized access attempt.")
return jsonify({"status": "error", "message": "Unauthorized"}), 401
if request.is_json:
data = request.get_json()
new_entry = GammaEventEntry(data=data)
# Create plot and store it as a binary blob
new_entry.image_plot = create_and_save_plot(data)
db.session.add(new_entry)
db.session.commit()
# Emit new data to all connected clients
emit_data = {
"id": new_entry.id,
"timestamp": new_entry.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
"data": new_entry.data,
"image_plot": new_entry.image_plot,
}
socketio.emit("new_data", emit_data)
check_for_answers()
return jsonify({"status": "success", "message": "Data received"}), 200
else:
return jsonify({"status": "error", "message": "Request body must be JSON"}), 400
@app.route("/")
def index():
return send_from_directory(app.static_folder, "index.html")
@socketio.on("connect")
def handle_connect():
initial_data = (
GammaEventEntry.query.order_by(GammaEventEntry.timestamp.desc()).limit(40).all()
)
data_list = [
{
"id": data.id,
"timestamp": data.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
"data": data.data,
"image_plot": data.image_plot,
}
for data in initial_data
]
emit("initial_data", data_list)
"""
Chat System
"""
@socketio.on("send_message")
def handle_message(data):
current_time = datetime.utcnow()
ip_address = request.remote_addr
# Retrieve and sanitize the message
message_content = escape(data) # Escapes HTML special characters
if len(message_content) == 0:
emit(
"message_error",
{"error": "Empty message is not allowed."},
room=request.sid,
)
return
# Character limit check
if len(message_content) > 1000:
emit(
"message_error",
{"error": "Message too long. Limit is 1000 characters."},
room=request.sid,
)
return
# Profanity and code injection checks
if profanity.contains_profanity(message_content):
emit(
"message_error",
{"error": "Message contains inappropriate content."},
room=request.sid,
)
return
# Rate limiting check
if ip_address in last_message_time_by_ip and (
current_time - last_message_time_by_ip[ip_address]
) < timedelta(seconds=30):
emit(
"message_error",
{"error": "You can only send a message every 30 seconds."},
room=request.sid,
)
return
last_message_time_by_ip[ip_address] = current_time
new_message = ChatMessage(
question=message_content, username=generate_username(1)[0]
)
db.session.add(new_message)
db.session.commit()
# Broadcast server response
socketio.emit(
"message_added",
{
"id": new_message.id,
"message": new_message.question,
"username": new_message.username,
"answer": new_message.answer,
"answer_image_plot": new_message.answer_image_plot,
"timestamp": new_message.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
},
room=request.sid,
)
@app.route("/latest_messages", methods=["GET"])
def get_latest_messages():
messages = ChatMessage.query.order_by(ChatMessage.timestamp.asc()).limit(100).all()
messages = [
{
"id": msg.id,
"message": msg.question,
"username": msg.username,
"answer": msg.answer,
"answer_image_plot": msg.answer_image_plot,
"timestamp": msg.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
}
for msg in messages
]
return jsonify(messages)
@app.route("/older_messages", methods=["GET"])
def get_older_messages():
last_id = request.args.get(
"last_id", type=int
) # ID of the oldest message currently displayed
if last_id:
messages = (
ChatMessage.query.filter(ChatMessage.id < last_id)
.order_by(ChatMessage.timestamp.asc())
.limit(20)
.all()
)
messages = [
{
"id": msg.id,
"message": msg.question,
"username": msg.username,
"answer": msg.answer,
"answer_image_plot": msg.answer_image_plot,
"timestamp": msg.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
}
for msg in reversed(messages)
]
return jsonify(messages)
return jsonify([])
def cleanup_ip_timestamps():
while True:
current_time = datetime.utcnow()
# Acquire a lock to ensure thread safety
with threading.Lock():
keys_to_remove = [
ip
for ip, last_time in last_message_time_by_ip.items()
if (current_time - last_time) > timedelta(seconds=30)
]
for ip in keys_to_remove:
del last_message_time_by_ip[ip]
# Sleep for a specific interval before checking again
time.sleep(10) # Check every 10 seconds
if __name__ == "__main__":
with app.app_context():
db.create_all()
cleanup_thread = threading.Thread(target=cleanup_ip_timestamps)
cleanup_thread.daemon = (
True # This makes the thread exit when the main program exits
)
cleanup_thread.start()
socketio.run(app)
PythonDie index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Decision - ATLANE</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Fuzzy+Bubbles:wght@400;700&display=swap');
HTML,
BODY {
background: url("/static/background.png") no-repeat center center fixed;
background-size: cover;
height: 100%;
width: 100%;
height: 100vh;
width: 100vw;
margin: 0;
padding: 0;
/* required to prevent rogue scrollbars */
overflow: hidden;
/* cosmetic stuff: */
background-color: black;
}
.responsive-div {
margin-left: 2.5vw;
margin-top: 5vw;
display: flex;
flex-direction: column;
height: 85vh;
width: 90vw;
background-color: #f8f9fa;
opacity: 0.9;
box-sizing: border-box;
border-radius: 1em;
overflow: hidden;
/* Prevent any overflow beyond this point */
}
.chat-messages {
flex-grow: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
/* Allow this element to scroll */
padding: 10px;
}
.message {
background-color: #f0f0f0;
padding: 10px 20px;
border-radius: 20px;
margin-bottom: 10px;
max-width: 90%;
}
.sent {
align-self: flex-start;
background-color: #DCF8C6;
position: relative;
}
.received {
font-family: "Fuzzy Bubbles", sans-serif;
font-weight: 700;
font-size: 1.5em;
align-self: flex-end;
right: 5vw;
background-color: #f0f0f0;
}
#chartsContainerAnswer * {
max-width: 100%;
height: auto;
}
#chartsContainerAnswer iframe,
#chartsContainerAnswer embed,
#chartsContainerAnswer object {
max-width: 45%;
height: auto;
}
</style>
</head>
<body>
<div class="container d-flex" style="height: 100vh;">
<div class="responsive-div">
<div class="text-center p-3" style="border-bottom: lightgray;">
<img style="height: 2.5vw;" src="https://atlane.de/wp-content/uploads/2024/05/atlane-logo-v3.jpg">
</br>
<span class="h3">Request a Decision from the Universe: </span><br>
<span class="h5">Powered by a Gamma-Event True Random Number Generator</span>
</div>
<div id="messages" class="chat-messages d-flex flex-grow-1"></div>
<div id="chartsContainer" class="d-flex flex-grow-1" style="visibility: hidden;"></div>
<!-- Input -->
<div class="text-center p-3">
<input type="text" id="chatInput" class="form-control" rows="3" maxlength="1000">
<button class="btn btn-primary mt-3" onclick="sendMessage()">Ask the univers for a "Yeah!" or
"Nope..."</button>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="rateLimitModal" tabindex="-1" aria-labelledby="rateLimitModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="rateLimitModalLabel">Message Sending Error</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="error-message"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="sendMessageModal" tabindex="-1" aria-labelledby="rateLimitModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="rateLimitModalLabel">Question added to RandomAnswer Queue</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Your message was added to the RandomAnswer Queue. Please wait until the Univers sends an
answer to your question: </br>
<div id="messages-dialog" class="mb-3 chat-messages"></div>
Needed 4 Gamma-Events:
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"></div>
</div>
<div id="chartsContainerAnswer"></div>
<!-- Container for new charts after question -->
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
<script>
const socket = io();
var awaiting_answer = false;
var awaiting_answer_gamma_events = 0;
var awaiting_answer_message_id = 0;
document.addEventListener('DOMContentLoaded', function () {
socket.on('message_error', function (data) {
// Show error modal for rate limit or other errors
var rateLimitModalElement = document.getElementById('rateLimitModal');
var rateLimitModalInstance = new bootstrap.Modal(rateLimitModalElement);
console.log(data);
const errormessagediv = document.getElementById('error-message');
errormessagediv.textContent = data.error;
rateLimitModalInstance.show();
});
socket.on('message_added', function (data) {
awaiting_answer = true;
awaiting_answer_gamma_events = 0;
awaiting_answer_message_id = data.id;
const progressBar = document.querySelector('.progress-bar');
const maxValue = parseInt(progressBar.style.width); // Get the maximum value from the width style
progressBar.setAttribute('aria-valuenow', 0);
progressBar.style.width = 0 + '%';
document.getElementById('chartsContainerAnswer').replaceChildren();
var modalElement = document.getElementById('sendMessageModal');
var modalInstance = new bootstrap.Modal(modalElement);
const messagesContainerDialgo = document.getElementById('messages-dialog');
const messageDiv = document.createElement('div');
messageDiv.classList.add('message');
messageDiv.classList.add('sent');
messageDiv.textContent = data.message + " (" + data.timestamp + ") - " + data.username;
messagesContainerDialgo.replaceChildren();
messagesContainerDialgo.appendChild(messageDiv); // This appends the message at the end.
modalInstance.show();
});
});
function createChart(timestamp, data, plot_image, question_dialog = false) {
const chartContainer = document.createElement('div');
chartContainer.className = 'chart-container my-4 p-3 shadow-sm border';
const title = document.createElement('h5');
title.textContent = `Event: ${timestamp}`;
chartContainer.appendChild(title);
const image = document.createElement('img')
image.src = `data:image/jpeg;base64,${plot_image}`;
image.style = "background-color: transparent; max-height: 25vw"
chartContainer.appendChild(image);
//document.getElementById('chartsContainer').appendChild(chartContainer);
if (question_dialog) {
document.getElementById('chartsContainerAnswer').appendChild(chartContainer);
}
}
socket.on('initial_data', function (entries) {
entries.forEach(entry => {
createChart(entry.timestamp, entry.data.data, entry.image_plot);
});
});
socket.on('new_data', function (entry) {
if (awaiting_answer == false) {
} else {
const progressBar = document.querySelector('.progress-bar');
const maxValue = parseInt(progressBar.style.width); // Get the maximum value from the width style
if (awaiting_answer_gamma_events < 4) {
createChart(entry.timestamp, entry.data.data, entry.image_plot, question_dialog = true);
awaiting_answer_gamma_events += 1;
let currentValue = parseInt(progressBar.getAttribute('aria-valuenow')); // Current value starts at 0
currentValue += 25; // Increment the current value
progressBar.setAttribute('aria-valuenow', currentValue);
progressBar.textContent = awaiting_answer_gamma_events + "/4";
progressBar.style.width = currentValue + '%';
} else {
awaiting_answer = false;
}
}
});
function sendMessage() {
const message = document.getElementById('chatInput').value;
socket.emit('send_message', message);
document.getElementById('chatInput').value = '';
}
function loadInitialMessages() {
$.get('/latest_messages', function (messages) {
messages.forEach(msg => displayMessage(msg));
});
}
loadInitialMessages();
function displayMessage(msg) {
console.log("Display Message called");
console.log(msg);
const messagesContainer = document.getElementById('messages');
const messageDiv = document.createElement('div');
messageDiv.classList.add('message');
messageDiv.classList.add('sent');
messageDiv.innerHTML = msg.message + " </br><em><small style='color:gray,'> (" + msg.timestamp + ") - " + msg.username + "</small></em>";
const answerDiv = document.createElement('div');
answerDiv.classList.add('message');
answerDiv.classList.add('received');
answerDiv.textContent = msg.answer;
const image = document.createElement('img')
image.src = `data:image/jpeg;base64,${msg.answer_image_plot}`;
image.style = "background-color: transparent; max-width: 10vw; padding-top:10px"
answerDiv.appendChild(document.createElement('br'));
answerDiv.appendChild(image);
messagesContainer.appendChild(messageDiv); // This appends the message at the end.
messagesContainer.appendChild(answerDiv); // This appends the message at the end.
messagesContainer.scrollTop = messagesContainer.scrollHeight + 300;
let answerDiv2 = answerDiv;
let messageDiv2 = messageDiv;
if (msg.id == awaiting_answer_message_id) {
const messagesContainerDialog = document.getElementById('messages-dialog');
messagesContainerDialog.replaceChildren();
const messageDiv2 = document.createElement('div');
messageDiv2.classList.add('message');
messageDiv2.classList.add('sent');
messageDiv2.innerHTML = msg.message + " </br><em><small style='color:gray,'> (" + msg.timestamp + ") - " + msg.username + "</small></em>";
const answerDiv2 = document.createElement('div');
answerDiv2.classList.add('message');
answerDiv2.classList.add('received');
answerDiv2.textContent = msg.answer;
const image = document.createElement('img')
image.src = `data:image/jpeg;base64,${msg.answer_image_plot}`;
image.style = "background-color: transparent; max-width: 10vw; padding-top:10px"
answerDiv2.appendChild(document.createElement('br'));
answerDiv2.appendChild(image);
messagesContainerDialog.appendChild(messageDiv2);
messagesContainerDialog.appendChild(answerDiv2);
}
}
socket.on('receive_message', function (msg) {
displayMessage(msg);
});
let lastLoadedMessageId;
window.addEventListener('scroll', function () {
if (window.scrollY === 0) { // User has scrolled to the top of the window
loadOlderMessages();
}
});
function loadOlderMessages() {
if (lastLoadedMessageId) {
$.get(`/older_messages?last_id=${lastLoadedMessageId}`, function (messages) {
if (messages.length > 0) {
messages.forEach(msg => {
const messageDiv = document.createElement('div');
messageDiv.textContent = msg.message + " (" + msg.timestamp + ") - " + msg.username;
const messagesContainer = document.getElementById('messages');
messagesContainer.insertBefore(messageDiv, messagesContainer.firstChild);
lastLoadedMessageId = messages[messages.length - 1].id;
});
}
});
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
HTML