Compare commits
	
		
			34 Commits
		
	
	
		
			1.03
			...
			cbcdc34e6c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cbcdc34e6c | |||
| 48774a42f4 | |||
| 73c486be73 | |||
| 6574947a80 | |||
| 956cc49e6f | |||
| b1da9449ad | |||
| e54cadcc7c | |||
| 0b2245d7f6 | |||
| 3ceab44a96 | |||
| 91de9f0785 | |||
| 837fd7f558 | |||
| 6cacd8451f | |||
| 5ae054f314 | |||
| 3e571a515d | |||
| a2aa302121 | |||
| d04489819d | |||
| 52026296f2 | |||
| a22f71649a | |||
| ae8eef52ef | |||
| d9ee193e26 | |||
| 170c66ff49 | |||
| 2ad835966c | |||
| a20a351002 | |||
| c9fba23e70 | |||
| 9301607468 | |||
| fc592c4342 | |||
| ab2ab0e0c1 | |||
| 5ee0a23a6d | |||
| cf76ea7cc7 | |||
| 7a2e95c126 | |||
| 1cf0560957 | |||
| b66d175948 | |||
| 0967b6aa65 | |||
| 498d813624 | 
							
								
								
									
										4
									
								
								Software/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								Software/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,9 @@ | ||||
| data/ | ||||
| data_src/version | ||||
| .pio | ||||
| .vscode/.browse.c_cpp.db* | ||||
| .vscode/c_cpp_properties.json | ||||
| .vscode/launch.json | ||||
| .vscode/ipch | ||||
| wifi_credentials.ini | ||||
| wifi_credentials.ini | ||||
| __pycache__ | ||||
							
								
								
									
										145
									
								
								Software/codegen/dtcs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								Software/codegen/dtcs.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| import os | ||||
| import time | ||||
| from jinja2 import Environment, FileSystemLoader | ||||
| import json | ||||
| import sys | ||||
|  | ||||
| import filechecksum as fcs | ||||
|  | ||||
| def build_dtcs(): | ||||
|     # Pfad zur Eingabedatei und Ausgabedatei | ||||
|     input_file = "src/dtc_defs.txt" | ||||
|     output_file = "include/dtc_defs.h" | ||||
|     json_output_file = "data_src/static/dtc_table.json" | ||||
|      | ||||
|     # Überprüfen, ob das Verzeichnis existiert, andernfalls erstellen | ||||
|     json_output_dir = os.path.dirname(json_output_file) | ||||
|     if not os.path.exists(json_output_dir): | ||||
|         os.makedirs(json_output_dir) | ||||
|      | ||||
|     # Mehrdimensionales Array zum Speichern der Zeilen aus der Eingabedatei | ||||
|     dtc_lines = [] | ||||
|      | ||||
|     # Lesen und analysieren der Eingabedatei | ||||
|     with open(input_file, "r", encoding="utf-8") as f: | ||||
|         for line in f: | ||||
|             line = line.strip() | ||||
|             if not line or line.startswith("#"): | ||||
|                 continue | ||||
|      | ||||
|             parts = line.split(";") | ||||
|             if len(parts) == 5: | ||||
|                 num, dtc_name, dtc_severity, title, description = [part.strip() for part in parts] | ||||
|                 dtc_lines.append([int(num), dtc_name, dtc_severity, title, description]) | ||||
|      | ||||
|     # Überprüfen auf Duplikate in den DTC-Nummern und DTC-Namen | ||||
|     num_set = set() | ||||
|     dtc_name_set = set() | ||||
|     duplicates = [] | ||||
|      | ||||
|     for line in dtc_lines: | ||||
|         num, dtc_name, _, _, _ = line | ||||
|         if num in num_set: | ||||
|             duplicates.append(f"DTC-Nummer {num} ist ein Duplikat.") | ||||
|         else: | ||||
|             num_set.add(num) | ||||
|      | ||||
|         if dtc_name in dtc_name_set: | ||||
|             duplicates.append(f"DTC-Name '{dtc_name}' ist ein Duplikat.") | ||||
|         else: | ||||
|             dtc_name_set.add(dtc_name) | ||||
|      | ||||
|     if duplicates: | ||||
|         for duplicate in duplicates: | ||||
|             print(f"Fehler: {duplicate}") | ||||
|         raise ValueError("Duplicate DTC Data detected") | ||||
|      | ||||
|     # Suchen nach DTC_NO_DTC und DTC_LAST_DTC | ||||
|     dtc_no_dtc_added = False | ||||
|     dtc_last_dtc_line = None | ||||
|      | ||||
|     for line in dtc_lines: | ||||
|         _, dtc_name, _, _, _ = line | ||||
|         if dtc_name == "DTC_NO_DTC": | ||||
|             dtc_no_dtc_added = True | ||||
|         elif dtc_name == "DTC_LAST_DTC": | ||||
|             dtc_last_dtc_line = line | ||||
|      | ||||
|     # Einen DTC für DTC_NO_DTC hinzufügen (wenn nicht vorhanden) | ||||
|     if not dtc_no_dtc_added: | ||||
|         dtc_lines.insert(0, [0, "DTC_NO_DTC", "DTC_NONE", "No Error", "No Error"]) | ||||
|      | ||||
|     # Falls DTC_LAST_DTC existiert, lösche es | ||||
|     if dtc_last_dtc_line: | ||||
|         dtc_lines.remove(dtc_last_dtc_line) | ||||
|      | ||||
|     # Einen DTC für DTC_LAST_DTC hinzufügen (mit der höchsten Nummer) | ||||
|     if dtc_lines: | ||||
|         highest_num = max([line[0] for line in dtc_lines]) | ||||
|     else: | ||||
|         highest_num = 0 | ||||
|      | ||||
|     dtc_lines.append([highest_num + 1, "DTC_LAST_DTC", "DTC_NONE", "Last Error", "Last Error"]) | ||||
|      | ||||
|     # Sortieren der Zeilen nach der Nummer aufsteigend | ||||
|     dtc_lines.sort(key=lambda x: x[0]) | ||||
|      | ||||
|     checksum = fcs.calculate_checksum(dtc_lines) | ||||
|     timestamp = int(time.time()) | ||||
|      | ||||
|     if fcs.read_and_compare_checksum(output_file, checksum): | ||||
|         print("Keine Änderungen im DTC-Headerfile erforderlich.") | ||||
|     else: | ||||
|         # DTC_NAME_CONSTANT-Makros initialisieren | ||||
|         dtc_macros = [] | ||||
|         dtc_structs = [] | ||||
|      | ||||
|         # Verarbeiten der sortierten Zeilen | ||||
|         for i, line in enumerate(dtc_lines): | ||||
|             num, dtc_name, dtc_severity, title, description = line | ||||
|             dtc_macros.append(f"#define {dtc_name:<30} {num}") | ||||
|             comma = "," if i < len(dtc_lines) - 1 else " " | ||||
|             dtc_structs.append(f"    {{ {dtc_name:<30}, {dtc_severity:<12} }}{comma} // {description}") | ||||
|      | ||||
|         env = Environment(loader=FileSystemLoader('codegen/templates', encoding='utf-8')) | ||||
|         # Lade das Jinja2-Template aus der Datei | ||||
|         template = env.get_template('dtc_defs.h.j2') | ||||
|      | ||||
|         # Erstelle ein Context-Dictionary mit den erforderlichen Daten | ||||
|         context = { | ||||
|             'timestamp_unix': timestamp, | ||||
|             'timestamp' : time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp)), | ||||
|             'date' : time.strftime('%d.%m.%Y', time.localtime(timestamp)), | ||||
|             'dtc_macros': dtc_macros,  # Übergebe die dtc_macros-Liste direkt | ||||
|             'dtc_structs': dtc_structs,  # Übergebe die dtc_structs-Liste direkt | ||||
|             'checksum' : checksum | ||||
|         } | ||||
|     | ||||
|         # Rendere das Template mit den Werten und erhalte den Header-Text | ||||
|         header_text = template.render(context) | ||||
|      | ||||
|         # Schreibe den generierten Header-Text in die Header-Datei | ||||
|         with open(output_file, "w", encoding='utf-8') as f: | ||||
|             f.write(header_text) | ||||
|      | ||||
|         print(f"Header-Datei wurde erstellt: {output_file}") | ||||
|      | ||||
|     if fcs.read_and_compare_json_checksum(json_output_file, checksum): | ||||
|         print("Keine Änderungen im DTC-JSON-file erforderlich.") | ||||
|     else: | ||||
|         dtc_info = { | ||||
|             "codegenerator_checksum": checksum, | ||||
|             'timestamp' : time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp)), | ||||
|             "dtc_table_data": [] | ||||
|         } | ||||
|      | ||||
|         # Verarbeiten der sortierten Zeilen | ||||
|         for i, line in enumerate(dtc_lines): | ||||
|             num, dtc_name, dtc_severity, title, description = line | ||||
|             dtc_info["dtc_table_data"].append({"num": num, "title": title, "description": description}) | ||||
|  | ||||
|         # JSON-Datei mit UTF-8-Zeichencodierung erstellen | ||||
|         with open(json_output_file, 'w', encoding='utf-8') as json_f: | ||||
|             json.dump(dtc_info, json_f, ensure_ascii=False, indent=4, separators=(',', ': ')) | ||||
|      | ||||
|         print(f"JSON-Datei wurde erstellt: {json_output_file}") | ||||
							
								
								
									
										45
									
								
								Software/codegen/filechecksum.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Software/codegen/filechecksum.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
|  | ||||
| import hashlib | ||||
| import json | ||||
|  | ||||
| # Funktion zum Berechnen der SHA-256-Checksumme | ||||
| def calculate_checksum(data): | ||||
|     sha256 = hashlib.sha256() | ||||
|     sha256.update(str(data).encode('utf-8')) | ||||
|     return sha256.hexdigest() | ||||
|  | ||||
| # Funktion zum Lesen und Vergleichen der Checksumme in einer Datei | ||||
| def read_and_compare_checksum(file_path, expected_checksum): | ||||
|     try: | ||||
|         with open(file_path, 'r') as file: | ||||
|             content = file.read() | ||||
|             # Suche nach der Zeile mit der Checksumme | ||||
|             checksum_line_start = content.find("// CODEGENERATOR_CHECKSUM:") | ||||
|             if checksum_line_start != -1: | ||||
|                 # Extrahiere die Checksumme aus der Zeile | ||||
|                 existing_checksum = content[checksum_line_start + len("// CODEGENERATOR_CHECKSUM:"):].strip() | ||||
|                 # Vergleiche die Checksummen | ||||
|                 if existing_checksum == expected_checksum: | ||||
|                     return True | ||||
|     except FileNotFoundError: | ||||
|         pass  # Datei existiert nicht, was nicht schlimm ist | ||||
|  | ||||
|     return False | ||||
|  | ||||
| def read_and_compare_json_checksum(json_file_path, expected_checksum): | ||||
|     try: | ||||
|         with open(json_file_path, 'r') as json_file: | ||||
|             # Lade das JSON aus der Datei | ||||
|             data = json.load(json_file) | ||||
|              | ||||
|             # Überprüfe, ob "codegenerator_checksum" im JSON vorhanden ist | ||||
|             if "codegenerator_checksum" in data: | ||||
|                 existing_checksum = data["codegenerator_checksum"] | ||||
|                  | ||||
|                 # Vergleiche die Checksummen | ||||
|                 if existing_checksum == expected_checksum: | ||||
|                     return True | ||||
|     except FileNotFoundError: | ||||
|         pass  # Datei existiert nicht, was nicht schlimm ist | ||||
|  | ||||
|     return False | ||||
| @@ -1,14 +1,73 @@ | ||||
| # SCRIPT TO GZIP CRITICAL FILES FOR ACCELERATED WEBSERVING | ||||
| # see also https://community.platformio.org/t/question-esp32-compress-files-in-data-to-gzip-before-upload-possible-to-spiffs/6274/10 | ||||
| 
 | ||||
| 
 | ||||
| import glob | ||||
| import shutil | ||||
| import gzip | ||||
| import os | ||||
| import subprocess | ||||
| import platform | ||||
| from os import popen | ||||
| Import("env") | ||||
| Import("projenv") | ||||
| 
 | ||||
| # Überprüfe die Betriebssystemplattform | ||||
| if platform.system() == "Windows": | ||||
|     # Setze die Pfade zu den Tools für Windows | ||||
|     html_minifier_path = os.path.join(os.getenv("APPDATA"), "npm", "html-minifier.cmd") | ||||
|     uglifyjs_path = os.path.join(os.getenv("APPDATA"), "npm", "uglifyjs.cmd") | ||||
|     terser_path = os.path.join(os.getenv("APPDATA"), "npm", "terser.cmd") | ||||
|     cssnano_path = os.path.join(os.getenv("APPDATA"), "npm", "cssnano.cmd") | ||||
| elif platform.system() == "Linux": | ||||
|     # Setze die Namen der Tools für Linux | ||||
|     html_minifier_path = "html-minifier" | ||||
|     uglifyjs_path = "uglifyjs" | ||||
|     terser_path = "terser" | ||||
|     cssnano_path = "cssnano" | ||||
| else: | ||||
|     # Hier könntest du weitere Bedingungen für andere Betriebssysteme hinzufügen | ||||
|     raise Exception("Unterstütztes Betriebssystem nicht erkannt") | ||||
| 
 | ||||
| 
 | ||||
| def minify_html(input_path, output_path): | ||||
|     subprocess.run([html_minifier_path, '--collapse-whitespace', '--remove-comments', input_path, '-o', output_path]) | ||||
| 
 | ||||
| def minify_js(input_path, output_path): | ||||
|     subprocess.run([terser_path, input_path, '-o', output_path, '-c', '-m']) | ||||
| 
 | ||||
| def minify_css(input_path, output_path): | ||||
|     subprocess.run([cssnano_path, '--no-discardUnused', input_path, output_path]) | ||||
| 
 | ||||
| def process_file(src_path, dest_path): | ||||
|     _, file_extension = os.path.splitext(src_path) | ||||
| 
 | ||||
|     # Extrahiere den Ordnerpfad im Zielverzeichnis | ||||
|     dest_dir = os.path.dirname(dest_path) | ||||
| 
 | ||||
|     # Erstelle den Ordner und alle dazugehörigen Unterordner, falls sie nicht existieren | ||||
|     os.makedirs(dest_dir, exist_ok=True) | ||||
| 
 | ||||
|     if file_extension.lower() == '.js': | ||||
|         minify_js(src_path, dest_path) | ||||
|     elif file_extension.lower() == '.css': | ||||
|         minify_css(src_path, dest_path) | ||||
|     elif file_extension.lower() in ['.html', '.htm']: | ||||
|         minify_html(src_path, dest_path) | ||||
|     else: | ||||
|         # Kopiere nicht bearbeitbare Dateien direkt in den Zielordner | ||||
|         shutil.copy2(src_path, dest_path) | ||||
| 
 | ||||
| def strip_files(src_dir, dest_dir): | ||||
|     # Erstelle den Zielordner und alle dazugehörigen Unterordner, falls sie nicht existieren | ||||
|     os.makedirs(dest_dir, exist_ok=True) | ||||
| 
 | ||||
|     # Durchlaufe alle Dateien und Unterverzeichnisse im Quellordner | ||||
|     for root, _, files in os.walk(src_dir): | ||||
|         for filename in files: | ||||
|             src_path = os.path.join(root, filename) | ||||
|             dest_path = os.path.relpath(src_path, src_dir) | ||||
|             dest_path = os.path.join(dest_dir, dest_path) | ||||
| 
 | ||||
|             # Verarbeite nur Dateien (keine Unterverzeichnisse) | ||||
|             process_file(src_path, dest_path) | ||||
| 
 | ||||
| 
 | ||||
| def gzip_file(src_path, dst_path): | ||||
| 
 | ||||
| @@ -47,14 +106,16 @@ def gzip_webfiles(source, target, env): | ||||
|     filetypes_to_gzip = ['.css', '.png', '.js', '.ico', '.woff2', '.json'] | ||||
|     print('\nGZIP: Starting gzip-Process for LittleFS-Image...\n') | ||||
|     data_src_dir_path = os.path.join(env.get('PROJECT_DIR'), 'data_src') | ||||
|     data_temp_dir_path = os.path.join(env.get('PROJECT_DIR'), 'data_stripped') | ||||
|     strip_files(data_src_dir_path, data_temp_dir_path) | ||||
|     data_dir_path = env.get('PROJECT_DATA_DIR') | ||||
|     # check if data and datasrc exist. If the first exists and not the second, it renames it | ||||
|     if(os.path.exists(data_dir_path) and not os.path.exists(data_src_dir_path)): | ||||
|     if(os.path.exists(data_dir_path) and not os.path.exists(data_temp_dir_path)): | ||||
|         print('GZIP: Directory "'+data_dir_path + | ||||
|               '" exists, "'+data_src_dir_path+'" is not found.') | ||||
|               '" exists, "'+data_temp_dir_path+'" is not found.') | ||||
|         print('GZIP: Renaming "' + data_dir_path + | ||||
|               '" to "' + data_src_dir_path + '"') | ||||
|         os.rename(data_dir_path, data_src_dir_path) | ||||
|               '" to "' + data_temp_dir_path + '"') | ||||
|         os.rename(data_dir_path, data_temp_dir_path) | ||||
|     # Delete the 'data' directory | ||||
|     if(os.path.exists(data_dir_path)): | ||||
|         print('GZIP: Deleting the "data" directory ' + data_dir_path) | ||||
| @@ -67,27 +128,27 @@ def gzip_webfiles(source, target, env): | ||||
|     files_to_copy = [] | ||||
|     files_to_gzip = [] | ||||
| 
 | ||||
|     all_data_src = getListOfFiles(data_src_dir_path) | ||||
|     all_data_src = getListOfFiles(data_temp_dir_path) | ||||
|     for file in all_data_src: | ||||
|         file_name, file_extension = os.path.splitext(file) | ||||
|         print(file_name + " has filetype " + file_extension) | ||||
|         if file_extension in filetypes_to_gzip: | ||||
|             files_to_gzip.append(file) | ||||
|         else: | ||||
|             filename_subdir = remove_prefix(file, data_src_dir_path) | ||||
|             filename_subdir = remove_prefix(file, data_temp_dir_path) | ||||
|             files_to_copy.append(filename_subdir) | ||||
| 
 | ||||
|     for file in files_to_copy: | ||||
|         print('GZIP: Copying file from: ' + data_src_dir_path + file + ' to: ' + data_dir_path + file) | ||||
|         print('GZIP: Copying file from: ' + data_temp_dir_path + file + ' to: ' + data_dir_path + file) | ||||
|         os.makedirs(os.path.dirname(data_dir_path + file), exist_ok=True) | ||||
|         shutil.copy(data_src_dir_path + file, data_dir_path + file) | ||||
|         shutil.copy(data_temp_dir_path + file, data_dir_path + file) | ||||
|     # Compress and move files | ||||
|      | ||||
|     was_error = False | ||||
|     try: | ||||
|         for source_file_path in files_to_gzip: | ||||
|             print('GZIP: compressing... ' + source_file_path) | ||||
|             filename_subdir = remove_prefix(source_file_path, data_src_dir_path) | ||||
|             filename_subdir = remove_prefix(source_file_path, data_temp_dir_path) | ||||
|             target_file_path = data_dir_path + filename_subdir | ||||
|             os.makedirs(os.path.dirname(target_file_path), exist_ok=True) | ||||
|             print('GZIP: Compressed... ' + target_file_path) | ||||
| @@ -100,12 +161,18 @@ def gzip_webfiles(source, target, env): | ||||
|         print('GZIP: Failure/Incomplete.\n') | ||||
|     else: | ||||
|         print('GZIP: Compressed correctly.\n') | ||||
|         shutil.rmtree(data_temp_dir_path) | ||||
| 
 | ||||
|     return | ||||
|   | ||||
| 
 | ||||
| def gzip_binffiles(source, target, env): | ||||
|     git_revision = popen('git rev-parse --short HEAD').read().strip() | ||||
|     custom_flash_version = env.GetProjectOption("custom_flash_version", "0.99") | ||||
| 
 | ||||
|     # Format the target file name | ||||
|     target_filename = f"filesystem_{custom_flash_version}_{git_revision}.fs" | ||||
|     littlefsbin = target[0].get_abspath() | ||||
|     targetbin = os.path.join(os.path.dirname(littlefsbin), 'filesystem.fs') | ||||
|     targetbin = os.path.join(os.path.dirname(littlefsbin), target_filename) | ||||
|     shutil.copyfile(littlefsbin, targetbin) | ||||
|     gzip_file(targetbin, os.path.join(str(targetbin) + '.gz')) | ||||
|     os.remove(targetbin) | ||||
							
								
								
									
										33
									
								
								Software/codegen/run_pre.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Software/codegen/run_pre.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| Import("env")  # pylint: disable=undefined-variable | ||||
| env.Execute("\"$PYTHONEXE\" -m pip install jinja2") | ||||
|  | ||||
| import struct2json | ||||
| import dtcs | ||||
| from os import popen | ||||
|  | ||||
| git_revision = popen('git rev-parse --short HEAD').read().strip() | ||||
|  | ||||
| # Versionsnummern aus platformio.ini holen | ||||
| custom_firmware_version = env.GetProjectOption("custom_firmware_version", "0.99") | ||||
| custom_flash_version = env.GetProjectOption("custom_flash_version", "0.99") | ||||
|  | ||||
| # Versionsnummern aufteilen in Major und Minor | ||||
| fw_major, fw_minor = custom_firmware_version.split('.') | ||||
| fl_major, fl_minor = custom_flash_version.split('.') | ||||
|  | ||||
| # Version in Datei "version" im Ordner "data_src" überschreiben | ||||
| with open('data_src/version', 'w') as version_file: | ||||
|     version_file.write(custom_flash_version) | ||||
|      | ||||
| # Build-Flags setzen | ||||
| env.Replace(PROGNAME="firmware_%s_%s.fw" % (custom_firmware_version, git_revision)) | ||||
| env.Append(CPPDEFINES=[ | ||||
|     ('GIT_REV', '\\"{}\\"'.format(git_revision)), | ||||
|     ('FW_MAJOR', fw_major), | ||||
|     ('FW_MINOR', fw_minor), | ||||
|     ('FL_MAJOR', fl_major), | ||||
|     ('FL_MINOR', fl_minor) | ||||
| ]) | ||||
|  | ||||
| struct2json.struct2json() | ||||
| dtcs.build_dtcs() | ||||
							
								
								
									
										113
									
								
								Software/codegen/struct2json.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								Software/codegen/struct2json.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| import os | ||||
| import time | ||||
| from jinja2 import Environment, FileSystemLoader | ||||
| import re | ||||
|  | ||||
| import filechecksum as fcs | ||||
|  | ||||
| # Pfad zur Eingabedatei und Ausgabedatei | ||||
| input_file = "include/eeprom.h" | ||||
| output_sourcefile = "src/struct2json.cpp" | ||||
| output_headerfile = "include/struct2json.h" | ||||
| # Liste der zu suchenden Variablen/Structs | ||||
| variable_names = ['ConfigData', 'PersistenceData'] | ||||
|  | ||||
| def get_types(file_content, variable_names): | ||||
|     result = {} | ||||
|      | ||||
|     # Entferne Kommentare, um unerwünschte Störungen zu vermeiden | ||||
|     file_content = re.sub(r'\/\*.*?\*\/', '', file_content, flags=re.DOTALL) | ||||
|     file_content = re.sub(r'\/\/.*', '', file_content) | ||||
|      | ||||
|     for var_name in variable_names: | ||||
|         # Erstelle ein reguläres Ausdrucksmuster, um den Typ der Variable zu extrahieren | ||||
|         pattern = re.compile(r'\b(?:extern\s+)?(\w+)\s+' + re.escape(var_name) + r'\s*;') | ||||
|         match = pattern.search(file_content) | ||||
|          | ||||
|         if match: | ||||
|             # Extrahiere den Typ aus dem Treffer | ||||
|             type_match = match.group(1) | ||||
|             result[var_name] = type_match | ||||
|  | ||||
|     return result | ||||
|  | ||||
| def extract_struct_fields(file_content, variable_types): | ||||
|     result = {} | ||||
|  | ||||
|     # Entferne Kommentare, um unerwünschte Störungen zu vermeiden | ||||
|     file_content = re.sub(r'\/\*.*?\*\/', '', file_content, flags=re.DOTALL) | ||||
|     file_content = re.sub(r'\/\/.*', '', file_content) | ||||
|  | ||||
|     for var_name, var_type in variable_types.items(): | ||||
|         # Erstelle ein reguläres Ausdrucksmuster, um das Strukturfeld zu extrahieren | ||||
|         pattern = re.compile(r'typedef\s+struct\s*{([^}]*)}\s*' + re.escape(var_type) + r'\s*;') | ||||
|         match = pattern.search(file_content) | ||||
|  | ||||
|         if match: | ||||
|             # Extrahiere die Felder aus dem Treffer | ||||
|             fields_match = re.findall(r'\b(\w+)\s+(\w+)(?:\[(\d+)\])?\s*;', match.group(1)) | ||||
|             if fields_match: | ||||
|                 result[var_name] = {'type': var_type, 'fields': {}} | ||||
|                 for field_type, field_name, array_size in fields_match: | ||||
|                     if array_size: | ||||
|                         result[var_name]['fields'][field_name] = {'type': field_type, 'size': int(array_size)} | ||||
|                     else: | ||||
|                         result[var_name]['fields'][field_name] = {'type': field_type} | ||||
|  | ||||
|     return result | ||||
|  | ||||
| def struct2json(): | ||||
|     # Überprüfen, ob die Verzeichnisse existieren, andernfalls erstellen | ||||
|     output_dir_source = os.path.dirname(output_sourcefile) | ||||
|     if not os.path.exists(output_dir_source): | ||||
|         os.makedirs(output_dir_source) | ||||
|     output_dir_header = os.path.dirname(output_headerfile) | ||||
|     if not os.path.exists(output_dir_header): | ||||
|         os.makedirs(output_dir_header) | ||||
|  | ||||
|     # Unix-Zeitstempel hinzufügen | ||||
|     timestamp = int(time.time()) | ||||
|  | ||||
|     # Parse structs | ||||
|     with open(input_file, 'r') as file: | ||||
|         content = file.read() | ||||
|  | ||||
|     variable_types = get_types(content, variable_names) | ||||
|     structs = extract_struct_fields(content, variable_types) | ||||
|     checksum = fcs.calculate_checksum(structs) | ||||
|  | ||||
|     env = Environment(loader=FileSystemLoader('codegen/templates', encoding='utf-8')) | ||||
|     # Lade das Jinja2-Template aus der Datei | ||||
|     template_c = env.get_template('struct2json.cpp.j2') | ||||
|     template_h = env.get_template('struct2json.h.j2') | ||||
|  | ||||
|     # Erstelle ein Context-Dictionary mit den erforderlichen Daten | ||||
|     context = { | ||||
|         'timestamp_unix': timestamp, | ||||
|         'timestamp' : time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp)), | ||||
|         'date' : time.strftime('%d.%m.%Y', time.localtime(timestamp)), | ||||
|         'structs': structs, | ||||
|         'checksum': checksum | ||||
|     } | ||||
|  | ||||
|     # Überprüfe, ob die Checksummen übereinstimmen | ||||
|     if fcs.read_and_compare_checksum(output_sourcefile, checksum): | ||||
|         print("Keine Änderungen in der Source-Datei erforderlich.") | ||||
|     else: | ||||
|         # Rendere das Template mit den Werten und erhalte den Source-Text | ||||
|         source_text = template_c.render(context) | ||||
|         # Schreibe den generierten Source-Text in die Source-Datei | ||||
|         with open(output_sourcefile, "w", encoding='utf-8') as f: | ||||
|             f.write(source_text) | ||||
|         print(f"Source-Datei wurde erstellt: {output_sourcefile}") | ||||
|  | ||||
|     # Überprüfe, ob die Checksummen übereinstimmen | ||||
|     if fcs.read_and_compare_checksum(output_headerfile, checksum): | ||||
|         print("Keine Änderungen in der Header-Datei erforderlich.") | ||||
|     else: | ||||
|         # Rendere das Template mit den Werten und erhalte den Header-Text | ||||
|         header_text = template_h.render(context) | ||||
|         # Schreibe den generierten Header-Text in die Header-Datei | ||||
|         with open(output_headerfile, "w", encoding='utf-8') as f: | ||||
|             f.write(header_text) | ||||
|         print(f"Header-Datei wurde erstellt: {output_headerfile}") | ||||
							
								
								
									
										54
									
								
								Software/codegen/templates/dtc_defs.h.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								Software/codegen/templates/dtc_defs.h.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| /** | ||||
|  * @file dtc_defs.h | ||||
|  * | ||||
|  * @brief Header file for Diagnostic Trouble Code (DTC) definitions in the DE-Timer application. | ||||
|  * | ||||
|  * This file contains definitions for Diagnostic Trouble Codes (DTC) in the DE-Timer project. | ||||
|  * It includes enums for DTC active status, severity levels, and specific DTC codes. | ||||
|  * The file also defines an array of DTC definitions and a timestamp indicating the generation time. | ||||
|  * | ||||
|  * @note This file is auto-generated by a script on {{ timestamp }}. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date   {{ date }} | ||||
|  */ | ||||
|  | ||||
| #ifndef DTC_DEFS_H | ||||
| #define DTC_DEFS_H | ||||
|  | ||||
| #include <stdint.h> | ||||
|  | ||||
| typedef uint32_t DTCNum_t; | ||||
|  | ||||
| typedef enum | ||||
| { | ||||
|   DTC_INACTIVE, | ||||
|   DTC_ACTIVE, | ||||
|   DTC_PREVIOUS | ||||
| } DTCActive_t; | ||||
|  | ||||
| typedef enum | ||||
| { | ||||
|   DTC_NONE, | ||||
|   DTC_INFO, | ||||
|   DTC_WARN, | ||||
|   DTC_CRITICAL | ||||
| } DTCSeverity_t; | ||||
|  | ||||
| typedef struct { | ||||
|   DTCNum_t code; | ||||
|   DTCSeverity_t severity; | ||||
| } DTC_t; | ||||
|  | ||||
| {% for dtc in dtc_macros -%} | ||||
| {{ dtc }} | ||||
| {% endfor %} | ||||
| const DTC_t dtc_definitions[] = { | ||||
| {% for struct in dtc_structs -%} | ||||
| {{ struct }} | ||||
| {% endfor -%} | ||||
| }; | ||||
|  | ||||
| #endif // DTC_DEFS_H | ||||
|  | ||||
| // CODEGENERATOR_CHECKSUM: {{ checksum }} | ||||
							
								
								
									
										25
									
								
								Software/codegen/templates/struct2json.cpp.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Software/codegen/templates/struct2json.cpp.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| /** | ||||
|  * @file struct2json.cpp | ||||
|  * | ||||
|  * @brief Implementation file for converting structs to JSON objects. | ||||
|  * | ||||
|  * @note This file is auto-generated by a script on {{ timestamp }}. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date   {{ date }} | ||||
|  */ | ||||
|  | ||||
|  | ||||
| #include "struct2json.h" | ||||
|  | ||||
| {% for var_name, var_info in structs.items() -%} | ||||
| void generateJsonObject_{{ var_name }}(JsonObject data) | ||||
| { | ||||
|     {% for field_name, field_type in var_info['fields'].items() -%} | ||||
|     data["{{ field_name }}"] = {{ var_name }}.{{ field_name }}; | ||||
|     {% endfor -%} | ||||
| } | ||||
|  | ||||
| {% endfor %} | ||||
|  | ||||
| // CODEGENERATOR_CHECKSUM: {{ checksum }} | ||||
							
								
								
									
										26
									
								
								Software/codegen/templates/struct2json.h.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Software/codegen/templates/struct2json.h.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| /** | ||||
|  * @file struct2json.h | ||||
|  * | ||||
|  * @brief Header file for converting structs to JSON objects. | ||||
|  * | ||||
|  * @note This file is auto-generated by a script on {{ timestamp }}. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date   {{ date }} | ||||
|  */ | ||||
|  | ||||
| #ifndef _STRUCT2JSON_H_ | ||||
| #define _STRUCT2JSON_H_ | ||||
|  | ||||
| #include <Arduino.h> | ||||
| #include <ArduinoJson.h> | ||||
|  | ||||
| #include "eeprom.h" | ||||
|  | ||||
| {% for var_name, var_info in structs.items() -%} | ||||
| void generateJsonObject_{{ var_name }}(JsonObject data); | ||||
| {% endfor %} | ||||
|  | ||||
| #endif /* _STRUCT2JSON_H_ */ | ||||
|  | ||||
| // CODEGENERATOR_CHECKSUM: {{ checksum }} | ||||
| @@ -3,7 +3,7 @@ | ||||
|  | ||||
| <head> | ||||
|   <meta charset="utf-8" /> | ||||
|   <title>%DEVICENAME%</title> | ||||
|   <title>Dark Emergency Timer</title> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|   <link rel="stylesheet" href="static/css/bootstrap.min.css"> | ||||
|   <link rel="stylesheet" href="static/css/custom.css"> | ||||
| @@ -11,6 +11,8 @@ | ||||
|   <script src="static/js/jquery.min.js"></script> | ||||
|   <script src="static/js/bootstrap.min.js"></script> | ||||
|   <script src="static/js/websocket.js"></script> | ||||
|   <script src="static/js/dtc_table.js"></script> | ||||
|   <script src="static/js/script.js"></script> | ||||
|   <link rel="apple-touch-icon" sizes="180x180" href="static/img/apple-touch-icon.png"> | ||||
|   <link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon-32x32.png"> | ||||
|   <link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon-16x16.png"> | ||||
| @@ -18,11 +20,22 @@ | ||||
| </head> | ||||
|  | ||||
| <body> | ||||
|   <!-- Connection-Overlay --> | ||||
|   <div id="overlay"> | ||||
|     <div class="overlay-content"> | ||||
|       <p>Verbinde...</p> | ||||
|       <span class="loader"></span> | ||||
|     </div> | ||||
|   </div> | ||||
|   <!-- Connection-Overlay --> | ||||
|   <!-- Notification-Container --> | ||||
|   <div id="notification-container" class="notification-container"></div> | ||||
|   <!-- Notification-Container --> | ||||
|  | ||||
|   <nav class="navbar fixed-top navbar-dark bg-primary" id="navbar1"> | ||||
|     <a class="navbar-brand" href="#"> | ||||
|       <img src="static/img/logo.png" width="30" height="30" class="d-inline-block align-top mr-1" alt=""> | ||||
|       %DEVICENAME% | ||||
|       <span class="data-devicename">DE Airsoft Timer</span> | ||||
|     </a> | ||||
|     <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsingNavbar" | ||||
|       aria-controls="collapsingNavbar" aria-expanded="false" aria-label="Toggle navigation"> | ||||
| @@ -35,7 +48,6 @@ | ||||
|         <li class="nav-item"><a class="nav-link active" role="tab" data-toggle="tab" href="#tab_home">Home</a></li> | ||||
|         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_maintenance">Wartung</a></li> | ||||
|         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_source">Einstellungen</a></li> | ||||
|         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_sysinfo">Systeminfo</a></li> | ||||
|         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_fwupdate">Update</a></li> | ||||
|  | ||||
|       </ul> | ||||
| @@ -51,17 +63,17 @@ | ||||
|         <div class="col text-center"> | ||||
|           <div class="jumbotron"> | ||||
|             <img src="static/img/logo.png" width="120" height="120" class="img-fluid" alt=""> | ||||
|             <h3 class="pt-3">%DEVICENAME%</h3> | ||||
|             <h3 class="pt-3"><span class="data-devicename">DE Airsoft Timer</span></h3> | ||||
|           </div> | ||||
|         </div> | ||||
|         <!-- Div Group Battery remain --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>Akku Ladestand</h4> | ||||
|         <h4>Akkuladestand</h4> | ||||
|         <div class="progress"> | ||||
|           <div class="progress-bar text-light" role="progressbar" aria-valuenow="%BAT_REMAIN_CAPACITY%" | ||||
|             aria-valuemin="0" aria-valuemax="100" style="width: %BAT_REMAIN_CAPACITY%%"> | ||||
|             %BAT_REMAIN_CAPACITY%% | ||||
|           <div id="battery_level" class="data-battery_level progress-bar text-light" role="progressbar" aria-valuenow="0" | ||||
|             aria-valuemin="0" aria-valuemax="100" style="width: 0%"> | ||||
|             0 | ||||
|           </div> | ||||
|         </div> | ||||
|         </p> | ||||
| @@ -70,7 +82,7 @@ | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>aktueller Modus</h4> | ||||
|         <input class="form-control" type="text" placeholder="%SYSTEM_STATUS%" readonly> | ||||
|         <input class="data-systemstatus form-control" type="text" id="sysstatus" readonly> | ||||
|         </p> | ||||
|         <!-- Div Group current Mode --> | ||||
|         <!-- Div Group Faction Points --> | ||||
| @@ -79,35 +91,35 @@ | ||||
|         <h4>aktueller Punktestand</h4> | ||||
|         <div class="container-fluid"> | ||||
|           <div class="row"> | ||||
|             <div class="col text-center %FACTION_1_ACTIVE% text-white p-3">%NAME_FAC_1%</div> | ||||
|             <div class="col text-center %FACTION_2_ACTIVE% text-white p-3">%NAME_FAC_2%</div> | ||||
|             <div class="col text-center %FACTION_3_ACTIVE% text-white p-3">%NAME_FAC_3%</div> | ||||
|             <div id="header_faction1" class="col text-center data-name_faction1 text-white p-3">%NAME_FAC_1%</div> | ||||
|             <div id="header_faction2" class="col text-center data-name_faction2 text-white p-3">%NAME_FAC_2%</div> | ||||
|             <div id="header_faction3" class="col text-center data-name_faction3 text-white p-3">%NAME_FAC_3%</div> | ||||
|           </div> | ||||
|           <div class="row"> | ||||
|             <div class="col bg-dark text-white p-3"><img src="static/img/logo_fac1.png" | ||||
|                 class="rounded mx-auto img-fluid d-block" alt="..."> | ||||
|             <div class="col bg-dark text-white p-3 data-activefaction faction-logo faction1"> | ||||
|               <img src="static/img/logo_fac1.png" class="rounded mx-auto img-fluid d-block" alt="..."> | ||||
|             </div> | ||||
|             <div class="col bg-dark text-white p-3"><img src="static/img/logo_fac2.png" | ||||
|                 class="rounded mx-auto img-fluid d-block" alt="..."> | ||||
|             <div class="col bg-dark text-white p-3 data-activefaction faction-logo faction2"> | ||||
|               <img src="static/img/logo_fac2.png" class="rounded mx-auto img-fluid d-block" alt="..."> | ||||
|             </div> | ||||
|             <div class="col bg-dark text-white p-3"><img src="static/img/logo_fac3.png" | ||||
|                 class="rounded mx-auto img-fluid d-block" alt="..."> | ||||
|             <div class="col bg-dark text-white p-3 data-activefaction faction-logo faction3"> | ||||
|               <img src="static/img/logo_fac3.png" class="rounded mx-auto img-fluid d-block" alt="..."> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="row"> | ||||
|             <div class="col text-center bg-secondary text-white p-3">%POINTS_FAC_1%</div> | ||||
|             <div class="col text-center bg-secondary text-white p-3">%POINTS_FAC_2%</div> | ||||
|             <div class="col text-center bg-secondary text-white p-3">%POINTS_FAC_3%</div> | ||||
|             <div id="time_faction1" class="data-time_faction1 col text-center bg-secondary text-white p-3 format-time">0</div> | ||||
|             <div id="time_faction2" class="data-time_faction2 col text-center bg-secondary text-white p-3 format-time">0</div> | ||||
|             <div id="time_faction3" class="data-time_faction3 col text-center bg-secondary text-white p-3 format-time">0</div> | ||||
|           </div> | ||||
|         </div> | ||||
|         </p> | ||||
|         <!-- Div GroupFaction Points --> | ||||
|         <!-- Div Group DTC Table --> | ||||
|         <div %SHOW_DTC_TABLE%> | ||||
|         <div id="dtc_container" hidden> | ||||
|           <hr /> | ||||
|           <p> | ||||
|           <h4>Fehlercodes</h4> | ||||
|           <table class="table"> | ||||
|           <table class="table" id="dtc_table"> | ||||
|             <tbody> | ||||
|               <tr> | ||||
|                 <th class="col-6" scope="col">Zeitstempel</th> | ||||
| @@ -115,7 +127,6 @@ | ||||
|                 <th class="col-2" scope="col">Schwere</th> | ||||
|                 <th class="col-2" scope="col">Aktiv</th> | ||||
|               </tr> | ||||
|               %DTC_TABLE% | ||||
|             </tbody> | ||||
|           </table> | ||||
|           </p> | ||||
| @@ -131,435 +142,245 @@ | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>Punkte zurücksetzen</h4> | ||||
|         <form action="post.htm" method="POST" class="form-horizontal"> | ||||
|           <div class="form-group row"> | ||||
|             <div class="col text-center"> | ||||
|               <button name="resetpoints" type="submit" class="btn btn-outline-primary">Reset</button> | ||||
|             </div> | ||||
|         <div class="form-group row"> | ||||
|           <div class="col text-center"> | ||||
|             <button id="reset-timer" class="btn-wsevent btn btn-outline-primary ml-2">Timer zurücksetzen</button> | ||||
|           </div> | ||||
|         </form> | ||||
|         </div> | ||||
|         </p> | ||||
|         <!-- Div Group Reset Timers  --> | ||||
|         <!-- Div Group EEPROM formatting --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>EEPROM formatieren</h4> | ||||
|         <div class="alert alert-primary alert-dismissable show fade" role="alert"> | ||||
|           <button type="button" class="close" data-dismiss="alert" aria-label="Close"> | ||||
|             <span aria-hidden="true">×</span> | ||||
|           </button> | ||||
|           <strong>Achtung!</strong><br> | ||||
|           Das Formatieren der EEPROM-Bereiche sollte nur ausgeführt werden wenn es unbedingt erforderlich ist! | ||||
|           Hierdurch werden alle Einstellungen zurück gesetzt bzw. alle Betriebsdaten gehen verloren. | ||||
|           Folgende Situationen erfordern unter anderem eine Formatierung: | ||||
|           - Erstinitialisierung (bei neu aufgebautem Gerät) | ||||
|           - Firmware-Update (nur wenn es die Release-Notes fordern) | ||||
|         </div> | ||||
|         <form action="post.htm" method="POST" class="form-horizontal"> | ||||
|           <div class="form-group row"> | ||||
|             <div class="offset-4 col-8"> | ||||
|               <div class="form-check"> | ||||
|                 <input class="form-check-input" type="checkbox" name="reset_ee_cfg" id="reset_ee_cfg"> | ||||
|                 <label class="form-check-label" for="reset_ee_cfg"> | ||||
|                   Bereich "CFG" | ||||
|                 </label> | ||||
|               </div> | ||||
|               <div class="form-check"> | ||||
|                 <input class="form-check-input" type="checkbox" name="reset_ee_pds" id="reset_ee_pds"> | ||||
|                 <label class="form-check-label" for="reset_ee_pds"> | ||||
|                   Bereich "PDS" | ||||
|                 </label> | ||||
|      <!-- Div Group LiveDebug --> | ||||
|      <hr /> | ||||
|      <p> | ||||
|      <h4>Live Debug</h4> | ||||
|      <div class="form-group row"> | ||||
|        <textarea class="form-control" spellcheck="false" id="livedebug-out" rows="3" readonly></textarea> | ||||
|      </div> | ||||
|      <div class="form-group row"> | ||||
|        <div class="col text-center"> | ||||
|          <button id="debugstart" class="btn-wsevent btn btn-outline-primary ml-2">Start</button> | ||||
|          <button id="debugstop" class="btn-wsevent btn btn-outline-primary ml-2">Stop</button> | ||||
|        </div> | ||||
|      </div> | ||||
|      </p> | ||||
|      <!-- Div Group LiveDebug --> | ||||
|      <!-- Div Group Device Reboot --> | ||||
|      <hr /> | ||||
|      <p> | ||||
|      <h4>Gerät neustarten</h4> | ||||
|        <div class="form-group row"> | ||||
|          <div class="col text-center"> | ||||
|            <button id="reboot" class="btn-wsevent confirm btn btn-outline-primary">Reboot</button> | ||||
|          </div> | ||||
|        </div> | ||||
|      </p> | ||||
|      <!-- Div Group Device Reboot --> | ||||
|    </div> | ||||
|    <!-- Div Tab Maintenance --> | ||||
|  | ||||
|          <!-- Div Tab Settings--> | ||||
|          <div id="tab_source" class="tab-pane fade" role="tabpanel"> | ||||
|           <h3>Einstellungen</h3> | ||||
|           <!-- Div Group Battery Type --> | ||||
|           <hr /> | ||||
|           <p> | ||||
|             <h4>Akku</h4> | ||||
|             <div class="form-group row"> | ||||
|               <label for="batterytype" class="control-label col-4">Akku-Variante</label> | ||||
|               <div class="col-8"> | ||||
|                 <select id="batterytype" class="set-wsevent data-batterytype select form-control">  | ||||
|                   <option value="Undefined">Undefined</option> | ||||
|                   <option value="LiPo 3S">LiPo 3S</option> | ||||
|                   <option value="LiPo 2S">LiPo 2S</option>             | ||||
|                 </select> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="form-group row"> | ||||
|             <div class="col text-center"> | ||||
|               <button name="reset_ee_btn" type="submit" class="btn btn-outline-primary">EEPROM formatieren</button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </form> | ||||
|         </p> | ||||
|         <!-- Div Group EEPROM formatting --> | ||||
|         <!-- Div Group Device Reboot --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>Gerät neustarten</h4> | ||||
|         <form action="post.htm" method="POST" class="form-horizontal"> | ||||
|           <div class="form-group row"> | ||||
|             <div class="col text-center"> | ||||
|               <button name="reboot" type="submit" class="btn btn-outline-primary">Reboot</button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </form> | ||||
|         </p> | ||||
|         <!-- Div Group Device Reboot --> | ||||
|       </div> | ||||
|       <!-- Div Tab Maintenance --> | ||||
|       <!-- Div Tab Settings--> | ||||
|       <div id="tab_source" class="tab-pane fade" role="tabpanel"> | ||||
|         <h3>Einstellungen</h3> | ||||
|         <!-- Div Group Battery Type --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <form action="post.htm" method="POST" class="form-horizontal"> | ||||
|           <h4>Akku-Variante</h4> | ||||
|           <div class="form-group row"> | ||||
|             <label for="battery_select" class="control-label col-4">Akku</label> | ||||
|             <div class="col-8"> | ||||
|               <select id="battery_select" name="battery_select" class="select form-control"> | ||||
|                 %BATTERY_SELECT_OPTIONS% | ||||
|               </select> | ||||
|             </div> | ||||
|           </div> | ||||
|           <h4>Timer-Einstellungen</h4> | ||||
|           <div class="form-group row"> | ||||
|             <label for="factionreboot_cont" class="control-label col-4">active Faction Recovery</label> | ||||
|             <div class="col-8"> | ||||
|               <div class="form-check"> | ||||
|                 <input class="form-check-input" type="checkbox" name="factionreboot_cont" id="factionreboot_cont" | ||||
|                   %FACTIONREBOOT_CHECKED%> | ||||
|                 <label class="form-check-label" for="factionreboot_cont"> | ||||
|                   aktive Faktion beim booten wiederherstellen ? | ||||
|                 </label> | ||||
|           </p> | ||||
|           <!-- Div Group Battery Type --> | ||||
|           <!-- Div Group Timer Settings --> | ||||
|           <hr /> | ||||
|           <p> | ||||
|           <h4>Timer Einstellungen</h4> | ||||
|             <div class="form-group row"> | ||||
|               <label for="active_faction_on_reboot" class="control-label col-4">Aktive Fraktion wiederherstellen</label> | ||||
|               <div class="col-8"> | ||||
|                 <div class="form-check"> | ||||
|                   <input class="set-wsevent data-active_faction_on_reboot form-check-input" type="checkbox" id="active_faction_on_reboot"> | ||||
|                   <label class="form-check-label" for="active_faction_on_reboot"> | ||||
|                     aktivieren | ||||
|                   </label> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <h4>Faktionsbezeichnungen</h4> | ||||
|           <div class="alert alert-primary alert-dismissable show fade" role="alert"> | ||||
|             <button type="button" class="close" data-dismiss="alert" aria-label="Close"> | ||||
|               <span aria-hidden="true">×</span> | ||||
|             </button> | ||||
|             <strong>Achtung!</strong><br> | ||||
|             Faktionsbezeichnungen können nur aus ASCII-Zeichen bestehen, also A-Z, a-z und 0-9 | ||||
|           </div> | ||||
|           <div class="form-group row"> | ||||
|             <label for="faction_1_name" class="control-label col-4">Faktion 1</label> | ||||
|             <div class="col-8"> | ||||
|               <div class="input-group"> | ||||
|                 <input id="faction_1_name" name="faction_1_name" value="%NAME_FAC_1%" type="text" class="form-control" pattern="[A-Za-z0-9 _-]{1,32}"> | ||||
|                 <div class="input-group-append"> | ||||
|                   <span class="input-group-text">max 32 Zeichen</span> | ||||
|             </div>       | ||||
|             <div class="form-group row"> | ||||
|               <label for="name_faction1" class="control-label col-4">Faktion 1</label> | ||||
|               <div class="col-8"> | ||||
|                 <div class="input-group"> | ||||
|                   <input id="name_faction1" type="text" class="set-wsevent data-name_faction1 form-control" required="required"> | ||||
|                   <div class="input-group-append"> | ||||
|                     <span class="input-group-text">max. 32 Zeichen</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="form-group row"> | ||||
|             <label for="faction_2_name" class="control-label col-4">Faktion 2</label> | ||||
|             <div class="col-8"> | ||||
|               <div class="input-group"> | ||||
|                 <input id="faction_2_name" name="faction_2_name" value="%NAME_FAC_2%" type="text" class="form-control" pattern="[A-Za-z0-9 _-]{1,32}"> | ||||
|                 <div class="input-group-append"> | ||||
|                   <span class="input-group-text">max 32 Zeichen</span> | ||||
|             <div class="form-group row"> | ||||
|               <label for="name_faction2" class="control-label col-4">Faktion 2</label> | ||||
|               <div class="col-8"> | ||||
|                 <div class="input-group"> | ||||
|                   <input id="name_faction2" type="text" class="set-wsevent data-name_faction2 form-control" required="required"> | ||||
|                   <div class="input-group-append"> | ||||
|                     <span class="input-group-text">max. 32 Zeichen</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="form-group row"> | ||||
|             <label for="faction_3_name" class="control-label col-4">Faktion 3</label> | ||||
|             <div class="col-8"> | ||||
|               <div class="input-group"> | ||||
|                 <input id="faction_3_name" name="faction_3_name" value="%NAME_FAC_3%" type="text" class="form-control" pattern="[A-Za-z0-9 _-]{1,32}"> | ||||
|                 <div class="input-group-append"> | ||||
|                   <span class="input-group-text">max 32 Zeichen</span> | ||||
|             <div class="form-group row"> | ||||
|               <label for="name_faction3" class="control-label col-4">Faktion 3</label> | ||||
|               <div class="col-8"> | ||||
|                 <div class="input-group"> | ||||
|                   <input id="name_faction3" type="text" class="set-wsevent data-name_faction3 form-control" required="required"> | ||||
|                   <div class="input-group-append"> | ||||
|                     <span class="input-group-text">max. 32 Zeichen</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <!-- Div Group Timer Settings --> | ||||
|         <!-- Div Group Save Button--> | ||||
|         <hr /> | ||||
|         <p> | ||||
|           <div class="form-group row"> | ||||
|             <div class="col text-center"> | ||||
|               <button name="settingssave" type="submit" class="btn btn-outline-primary">Übernehmen</button> | ||||
|               <button id="settingssave" class="btn-wsevent btn btn-outline-primary">Speichern</button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </form> | ||||
|         </p> | ||||
|         <!-- Div Group Battery Type --> | ||||
|       </div> | ||||
|       <!-- Div Tab Settings --> | ||||
|  | ||||
|       <!-- Div Tab SystemInfo --> | ||||
|       <div id="tab_sysinfo" class="tab-pane fade" role="tabpanel"> | ||||
|         <h3>Systeminfo</h3> | ||||
|         <!-- Div Group Sysinfo:Geraeteinfo --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>Gerät</h4> | ||||
|         <table class="table"> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <th class="col-7" scope="col">Parameter</td> | ||||
|               <th class="col-5" scope="col">Value</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>Hostname</td> | ||||
|               <td>%HOSTNAME%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>Device-ID</td> | ||||
|               <td>%DEVICENAME_ID%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>Battery Voltage</td> | ||||
|               <td>%BAT_VOLTAGE%V</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>Battery Remain</td> | ||||
|               <td>%BAT_REMAIN_CAPACITY%%</td> | ||||
|             </tr> | ||||
|         </table> | ||||
|         </p> | ||||
|         <!-- Div Group Sysinfo:Geraeteinfo --> | ||||
|         <!-- Div Group Sysinfo:Settings --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>Einstellungen</h4> | ||||
|         <table class="table"> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <th class="col-7" scope="col">Parameter</td> | ||||
|               <th class="col-5" scope="col">Value</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>Battery_type</td> | ||||
|               <td>%BATTERY_TYPE%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>Faction_recovery</td> | ||||
|               <td>%FACTION_RECOVERY%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>EEPROM Version</td> | ||||
|               <td>%EEPROM_VERSION%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>Checksum</td> | ||||
|               <td>%CONFIG_CHECKSUM%</td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|         </p> | ||||
|         <!-- Div Group Sysinfo:Settings --> | ||||
|         <!-- Div Group Sysinfo:Persistance --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>Betriebsdaten</h4> | ||||
|         <table class="table"> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <th class="col-7" scope="col">Parameter</td> | ||||
|               <th class="col-5" scope="col">Value</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>writeCycleCounter</td> | ||||
|               <td>%WRITE_CYCLE_COUNT%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>PersistenceMarker</td> | ||||
|               <td>%PERSISTENCE_MARKER%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>activeFaction</td> | ||||
|               <td>%ACTIVE_FACTION%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>faction_1_timer</td> | ||||
|               <td>%POINTS_FAC_1%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>faction_2_timer</td> | ||||
|               <td>%POINTS_FAC_2%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>faction_3_timer</td> | ||||
|               <td>%POINTS_FAC_3%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>checksum</td> | ||||
|               <td>%PERSISTANCE_CHECKSUM%</td> | ||||
|             </tr> | ||||
|         </table> | ||||
|         </p> | ||||
|         <!-- Div Group Sysinfo:Persistance --> | ||||
|         <!-- Div Group LiveDebug --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>Live Debug</h4> | ||||
|     <!-- Div Tab Firmware Update--> | ||||
|     <div id="tab_fwupdate" class="tab-pane fade" role="tabpanel"> | ||||
|       <h3>Firmware</h3> | ||||
|       <!-- Div Group VersionInfo --> | ||||
|       <hr /> | ||||
|       <p> | ||||
|       <h4>Version-Info</h4> | ||||
|       <table class="table"> | ||||
|         <tbody> | ||||
|           <tr> | ||||
|             <th class="col-7" scope="col">Parameter</td> | ||||
|             <th class="col-5" scope="col">Value</td> | ||||
|           </tr> | ||||
|           <tr> | ||||
|             <td>Firmware Version</td> | ||||
|             <td><span class="data-fw-version"></span></td> | ||||
|           </tr> | ||||
|           <tr> | ||||
|             <td>Flash Version</td> | ||||
|             <td><span class="data-flash-version"></span></td> | ||||
|           </tr> | ||||
|           <tr> | ||||
|             <td>Git Revision</td> | ||||
|             <td><span class="data-git-revision"></span></td> | ||||
|           </tr> | ||||
|       </table> | ||||
|       </p> | ||||
|       <!-- Div Group VersionInfo --> | ||||
|       <!-- Div Group EEPROM Backup --> | ||||
|       <hr /> | ||||
|       <p> | ||||
|       <h4>EEPROM-Backup</h4> | ||||
|       <div class="form-group row"> | ||||
|         <div class="col text-center"> | ||||
|           <a class="btn btn-outline-primary" href="eejson" role="button" id="ee-backup-download">Download</a> | ||||
|         </div> | ||||
|       </div> | ||||
|       </p> | ||||
|       <!-- Div Group EEPROM Backup --> | ||||
|       <!-- Div Group EEPROM Restore --> | ||||
|       <hr /> | ||||
|       <p> | ||||
|       <h4>EEPROM-Restore</h4> | ||||
|       <form method='POST' action='eeRestore' enctype='multipart/form-data'> | ||||
|         <div class="form-group row"> | ||||
|           <textarea class="form-control" spellcheck="false" id="livedebug-out" rows="3" readonly></textarea> | ||||
|           <div class="custom-file"> | ||||
|             <input type="file" name="ee-restore-file" class="custom-file-input" id="ee-restore-file" accept=".ee.json" | ||||
|               required /> | ||||
|             <label class="custom-file-label" for="ee-restore-file">EEPROM-Backup auswählen</label> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="form-group row"> | ||||
|           <div class="col text-center"> | ||||
|             <button id="btn-ws-start" class="btn btn-outline-primary">Start</button> | ||||
|             <button id="btn-ws-stop" class="btn btn-outline-primary ml-2">Stop</button> | ||||
|             <button name="submit" type="submit" class="btn btn-outline-primary">Restore starten</button> | ||||
|           </div> | ||||
|         </div> | ||||
|       </form> | ||||
|       </p> | ||||
|       <!-- Div Group EEPROM Restore --> | ||||
|       <!-- Div Group Firmware Update --> | ||||
|       <hr /> | ||||
|       <p> | ||||
|       <h4>Firmware-Update</h4> | ||||
|       <form method='POST' action='doUpdate' enctype='multipart/form-data'> | ||||
|         <div class="form-group row"> | ||||
|           <div class="custom-file"> | ||||
|             <input type="file" name="fw-update-file" class="custom-file-input" id="fw-update-file" | ||||
|               accept=".fw.bin,.fs.gz" required /> | ||||
|             <label class="custom-file-label" for="fw-update-file">Firmware-Update auswählen</label> | ||||
|           </div> | ||||
|         </div> | ||||
|         </p> | ||||
|         <!-- Div Group LiveDebug --> | ||||
|       </div> | ||||
|       <!-- Div Tab SystemInfo --> | ||||
|  | ||||
|       <!-- Div Tab Firmware Update--> | ||||
|       <div id="tab_fwupdate" class="tab-pane fade" role="tabpanel"> | ||||
|         <h3>Firmware</h3> | ||||
|         <!-- Div Group VersionInfo --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>Version-Info</h4> | ||||
|         <table class="table"> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <th class="col-7" scope="col">Parameter</td> | ||||
|               <th class="col-5" scope="col">Value</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>Firmware Version</td> | ||||
|               <td>%SW_VERSION%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>Flash Version</td> | ||||
|               <td>%FS_VERSION%</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td>Git Revision</td> | ||||
|               <td>%GIT_REV%</td> | ||||
|             </tr> | ||||
|         </table> | ||||
|         </p> | ||||
|         <!-- Div Group VersionInfo --> | ||||
|         <!-- Div Group EEPROM Backup --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>EEPROM-Backup</h4> | ||||
|         <div class="form-group row"> | ||||
|           <div class="col text-center"> | ||||
|             <a class="btn btn-outline-primary" href="eejson" role="button" id="ee-backup-download">Download</a> | ||||
|             <button name="submit" type="submit" class="btn btn-outline-primary">Update starten</button> | ||||
|           </div> | ||||
|         </div> | ||||
|         </p> | ||||
|         <!-- Div Group EEPROM Backup --> | ||||
|         <!-- Div Group EEPROM Restore --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>EEPROM-Restore</h4> | ||||
|         <form method='POST' action='eeRestore' enctype='multipart/form-data'> | ||||
|           <div class="form-group row"> | ||||
|             <div class="custom-file"> | ||||
|               <input type="file" name="ee-restore-file" class="custom-file-input" id="ee-restore-file" accept=".ee.json" | ||||
|                 required /> | ||||
|               <label class="custom-file-label" for="ee-restore-file">EEPROM-Backup auswählen</label> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="form-group row"> | ||||
|             <div class="col text-center"> | ||||
|               <button name="submit" type="submit" class="btn btn-outline-primary">Restore starten</button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </form> | ||||
|         </p> | ||||
|         <!-- Div Group EEPROM Restore --> | ||||
|         <!-- Div Group Firmware Update --> | ||||
|         <hr /> | ||||
|         <p> | ||||
|         <h4>Firmware-Update</h4> | ||||
|         <form method='POST' action='doUpdate' enctype='multipart/form-data'> | ||||
|           <div class="form-group row"> | ||||
|             <div class="custom-file"> | ||||
|               <input type="file" name="fw-update-file" class="custom-file-input" id="fw-update-file" | ||||
|                 accept=".fw.bin,.fs.gz" required /> | ||||
|               <label class="custom-file-label" for="fw-update-file">Firmware-Update auswählen</label> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="form-group row"> | ||||
|             <div class="col text-center"> | ||||
|               <button name="submit" type="submit" class="btn btn-outline-primary">Update starten</button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </form> | ||||
|         </p> | ||||
|         <!-- Div Group Firmware Update --> | ||||
|       </div> | ||||
|       <!-- Div Tab Firmware Update--> | ||||
|       </form> | ||||
|       </p> | ||||
|       <!-- Div Group Firmware Update --> | ||||
|     </div> | ||||
|     <!-- Tabs Content --> | ||||
|   </main> | ||||
|     <!-- Div Tab Firmware Update--> | ||||
|   </div> | ||||
|   <!-- Tabs Content --> | ||||
| </main> | ||||
|  | ||||
| <!-- Footer --> | ||||
|  | ||||
|   <!-- Footer --> | ||||
|  | ||||
|   <footer class="page-footer navbar-dark bg-primary font-small fixed-bottom"> | ||||
|     <div class="container-fluid text-center"> | ||||
|       <div class="footer-copyright text-center py-3"> | ||||
|         <span class="text-muted">© 2023 - | ||||
|           <a class="text-reset fw-bold" href="https://hiabuto.de/">Hiabuto Defense Systems</a></span> | ||||
|       </div> | ||||
| <footer class="page-footer navbar-dark bg-primary font-small fixed-bottom"> | ||||
|   <div class="container-fluid text-center"> | ||||
|     <div class="footer-copyright text-center py-3"> | ||||
|       <span class="text-muted">© 2023 - | ||||
|         <a class="text-reset fw-bold" href="https://eventronics.de/">Marcel Peterkau</a></span> | ||||
|     </div> | ||||
|   </footer> | ||||
|   </div> | ||||
| </footer> | ||||
|  | ||||
|   <!-- Footer --> | ||||
| <!-- Footer --> | ||||
|  | ||||
|   <!-- Modal Dialog --> | ||||
| <!-- Modal Dialog --> | ||||
|  | ||||
|   <div class="modal fade" id="dtcModal" tabindex="-1" role="dialog" aria-labelledby="dtcModalLabel" aria-hidden="true"> | ||||
|     <div class="modal-dialog modal-dialog-centered" role="document"> | ||||
|       <div class="modal-content"> | ||||
|         <div class="modal-header"> | ||||
|           <h5 class="modal-title" id="dtcModalLabel">DTC-Description</h5> | ||||
|           <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||||
|             <span aria-hidden="true">×</span> | ||||
|           </button> | ||||
|         </div> | ||||
|         <div class="modal-body"> | ||||
|           <p class="dtc-desc">DTC Description</p> | ||||
|           <p class="dtc-debugval">DTC DebugVal</p> | ||||
|         </div> | ||||
|         <div class="modal-footer"> | ||||
|           <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Close</button> | ||||
|         </div> | ||||
| <div class="modal fade" id="dtcModal" tabindex="-1" role="dialog" aria-labelledby="dtcModalLabel" aria-hidden="true"> | ||||
|   <div class="modal-dialog modal-dialog-centered" role="document"> | ||||
|     <div class="modal-content"> | ||||
|       <div class="modal-header"> | ||||
|         <h5 class="modal-title" id="dtcModalLabel">DTC-Description</h5> | ||||
|         <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||||
|           <span aria-hidden="true">×</span> | ||||
|         </button> | ||||
|       </div> | ||||
|       <div class="modal-body"> | ||||
|         <p class="dtc-desc">DTC Description</p> | ||||
|         <p class="dtc-debugval">DTC DebugVal</p> | ||||
|       </div> | ||||
|       <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Close</button> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
|   <!-- Modal Dialog --> | ||||
| <!-- Modal Dialog --> | ||||
|  | ||||
|   <script> | ||||
|     $('.navbar-nav>li>a').on('click', function () { | ||||
|       $('.navbar-collapse').collapse('hide'); | ||||
|     }); | ||||
|  | ||||
|     document.querySelector('.custom-file-input').addEventListener('change', function (e) { | ||||
|       var fileName = document.getElementById("fw-update-file").files[0].name; | ||||
|       var nextSibling = e.target.nextElementSibling | ||||
|       nextSibling.innerText = fileName | ||||
|     }); | ||||
|  | ||||
|     $(document).ready(function () { | ||||
|       $("tr[data-dtc]").each(function (i) { | ||||
|         $(this).attr('data-toggle', "modal"); | ||||
|         $(this).attr('data-target', "#dtcModal"); | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     $('#dtcModal').on('show.bs.modal', function (event) { | ||||
|       var dtctr = $(event.relatedTarget) | ||||
|       var dtc = dtctr.data('dtc') | ||||
|       var debugval = dtctr.data('debugval') | ||||
|       var modal = $(this) | ||||
|       $.getJSON('static/tt_dtc/dtc_' + dtc + '.json', function (data) { | ||||
|         modal.find('.modal-title').text(data.title) | ||||
|         modal.find('.dtc-desc').text(data.description) | ||||
|         if (debugval > 0) { | ||||
|           modal.find('.dtc-debugval').text("Debugvalue: " + debugval) | ||||
|         } | ||||
|         else { | ||||
|           modal.find('.dtc-debugval').remove() | ||||
|         } | ||||
|       }).fail(function () { | ||||
|         console.log("An error has occurred."); | ||||
|         modal.find('.modal-title').text("Fehler") | ||||
|         modal.find('.dtc-desc').text("DTC-Beschreibung konnte nicht geladen werden") | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|   </script> | ||||
|  | ||||
| </body> | ||||
|  | ||||
|   | ||||
| @@ -1,28 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|  | ||||
| <head> | ||||
|   <meta charset="utf-8" /> | ||||
|   <title>KTM CAN Chain Oiler</title> | ||||
|   <meta http-equiv="content-type" content="text/html;charset=UTF-8"> | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|   <link rel="stylesheet" href="static/css/bootstrap.min.css"> | ||||
|   <link rel="stylesheet" href="static/css/custom.css"> | ||||
|   <script src="static/js/jquery.min.js"></script> | ||||
|   <script src="static/js/bootstrap.min.js"></script> | ||||
|   <link rel="apple-touch-icon" sizes="180x180" href="static/img/apple-touch-icon.png"> | ||||
|   <link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon-32x32.png"> | ||||
|   <link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon-16x16.png"> | ||||
|   <link rel="manifest" href="static/img/site.webmanifest"> | ||||
|   <meta http-equiv="refresh" content="3; url='index.htm'" /> | ||||
| </head> | ||||
|  | ||||
| <body> | ||||
|   <div class="container" style="display: flex; justify-content: center; align-items: center; height: 100vh"> | ||||
|     <div class="alert alert-success"> | ||||
|       <strong>Bitte warten!</strong> Änderungen werden übernommen. | ||||
|     </div> | ||||
|   </div> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
| @@ -4,7 +4,7 @@ | ||||
|  * Copyright 2011-2018 Twitter, Inc. | ||||
|  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||||
|  */ | ||||
| :root { | ||||
|  :root { | ||||
|     --blue: #007bff; | ||||
|     --indigo: #6610f2; | ||||
|     --purple: #6f42c1; | ||||
|   | ||||
| @@ -8438,4 +8438,23 @@ a.text-dark:hover { | ||||
|  | ||||
| .navbar-dark.bg-primary { | ||||
|     background-color: #111 !important | ||||
| } | ||||
| } | ||||
|  | ||||
| .glow-active-faction { | ||||
|     border: 3px solid #FFD700; /* Goldene Umrandung */ | ||||
|     box-shadow: 0 0 20px #FFD700; /* Leuchtender Glüheffekt */ | ||||
|     animation: glow 1.5s infinite alternate; | ||||
|     border-radius: 10px; /* Abgerundete Ecken */ | ||||
|     margin-bottom: 10px; /* Abstand nach unten */ | ||||
|   } | ||||
|    | ||||
|   @keyframes glow { | ||||
|     from { | ||||
|       box-shadow: 0 0 10px #FFD700; | ||||
|     } | ||||
|     to { | ||||
|       box-shadow: 0 0 20px #FFD700; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|    | ||||
| @@ -25,3 +25,66 @@ hr { | ||||
|     padding: 10px; | ||||
| } | ||||
|  | ||||
| #overlay { | ||||
|     display: none; | ||||
|     position: fixed; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     background: rgba(0, 0, 0, 0.8); /* Dunkler Hintergrund mit Transparenz */ | ||||
|     color: white; /* Textfarbe */ | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     z-index: 9999; /* Stellen Sie sicher, dass es über anderen Elementen liegt */ | ||||
| } | ||||
|  | ||||
| .overlay-content { | ||||
|     text-align: center; | ||||
|     font-size: 4rem; | ||||
| } | ||||
|  | ||||
| .loader { | ||||
|     width: 96px; | ||||
|     height: 96px; | ||||
|     border: 12px solid #FFF; | ||||
|     border-radius: 50%; | ||||
|     display: inline-block; | ||||
|     position: relative; | ||||
|     box-sizing: border-box; | ||||
|     animation: rotation 1s linear infinite; | ||||
|   }  | ||||
|   .loader::after { | ||||
|     content: '';   | ||||
|     box-sizing: border-box; | ||||
|     position: absolute; | ||||
|     left: 50%; | ||||
|     top: 50%; | ||||
|     transform: translate(-50%, -50%); | ||||
|     width: 120px; | ||||
|     height: 120px; | ||||
|     border-radius: 50%; | ||||
|     border: 12px solid transparent; | ||||
|     border-bottom-color: #FF3D00; | ||||
|   } | ||||
|    | ||||
|   @keyframes rotation { | ||||
|     0% { | ||||
|       transform: rotate(0deg); | ||||
|     } | ||||
|     100% { | ||||
|       transform: rotate(360deg); | ||||
|     } | ||||
|   }  | ||||
|  | ||||
|   .notification-container { | ||||
|     position: fixed; | ||||
|     top: 30%; | ||||
|     left: 50%; | ||||
|     transform: translateX(-50%); | ||||
|     z-index: 1000; | ||||
|   } | ||||
|    | ||||
|   .notification { | ||||
|     margin-bottom: 20px; /* Fügen Sie bei Bedarf weitere Stile hinzu */ | ||||
|   } | ||||
							
								
								
									
										96
									
								
								Software/data_src/static/dtc_table.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								Software/data_src/static/dtc_table.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| { | ||||
|     "codegenerator_checksum": "313d59949b074024df3c5d796f65e3bd518e34f0bb171185c30f008f21c19d30", | ||||
|     "timestamp": "2024-05-30 21:56:51", | ||||
|     "dtc_table_data": [ | ||||
|         { | ||||
|             "num": 0, | ||||
|             "title": "No Error", | ||||
|             "description": "No Error" | ||||
|         }, | ||||
|         { | ||||
|             "num": 1, | ||||
|             "title": "Akku leer", | ||||
|             "description": "Akku ist komplett leer. Den Akku aufladen!" | ||||
|         }, | ||||
|         { | ||||
|             "num": 2, | ||||
|             "title": "Akku niedrig", | ||||
|             "description": "Akku ist unter der Warnschwelle. Den Akku demnächst aufladen" | ||||
|         }, | ||||
|         { | ||||
|             "num": 3, | ||||
|             "title": "kein EEPROM erkannt", | ||||
|             "description": "Es wurde kein EEPROM gefunden. Dies lässt einen Hardware-Defekt vermuten." | ||||
|         }, | ||||
|         { | ||||
|             "num": 4, | ||||
|             "title": "EEPROM CFG Checksumme", | ||||
|             "description": "Die Checksumme der Config-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück" | ||||
|         }, | ||||
|         { | ||||
|             "num": 5, | ||||
|             "title": "EEPROM PDS Checksumme", | ||||
|             "description": "Die Checksumme der Betriebsdaten-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück" | ||||
|         }, | ||||
|         { | ||||
|             "num": 6, | ||||
|             "title": "EEPROM PDS Adresse", | ||||
|             "description": "Die Adresse der Betriebsdaten-Partition im EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück" | ||||
|         }, | ||||
|         { | ||||
|             "num": 7, | ||||
|             "title": "EEPROM Version falsch", | ||||
|             "description": "Die Layout-Version des EEPROM stimmt nicht mit der Firmware-Version überein. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück" | ||||
|         }, | ||||
|         { | ||||
|             "num": 8, | ||||
|             "title": "Flashspeicher Fehler", | ||||
|             "description": "Der Flashspeicher konnte nicht initialisiert werden. Aktualisieren sie Flash & Firmware" | ||||
|         }, | ||||
|         { | ||||
|             "num": 9, | ||||
|             "title": "Flashversion falsch", | ||||
|             "description": "Die Version des Flashspeicher stimmt nicht mit der Firmware-Version überein. Aktualisieren sie den Flash mit der passenden Update-Datei" | ||||
|         }, | ||||
|         { | ||||
|             "num": 10, | ||||
|             "title": "Keine Akkuüberwachung", | ||||
|             "description": "Es wurde keine Akkuüberwachung über I2C gefunden, Prüfen sie die Hardware!" | ||||
|         }, | ||||
|         { | ||||
|             "num": 11, | ||||
|             "title": "LoRa-Transceiver Error", | ||||
|             "description": "Es konnte keine Verbindung zum LoRa-Transceiver hergestellt werden. Prüfen Sie die Hardware auf Defekte" | ||||
|         }, | ||||
|         { | ||||
|             "num": 12, | ||||
|             "title": "Config-Validierung", | ||||
|             "description": "Ein oder mehrer Einstellungswerte sind ausserhalb plausibler Werte. Prüfen Sie Ihre Einstellungen" | ||||
|         }, | ||||
|         { | ||||
|             "num": 13, | ||||
|             "title": "EEPROM-Migration", | ||||
|             "description": "Es wurde ein altes EEPROm Image erkannt, konnte aber nicht migriert werden. EEPROM manuell zurück setzen und neue Einstellunge speichern." | ||||
|         }, | ||||
|         { | ||||
|             "num": 14, | ||||
|             "title": "Dummy-DTC Info", | ||||
|             "description": "Ein Dummy-DTC der Schwere \"Info\" für Debugging-Zwecke" | ||||
|         }, | ||||
|         { | ||||
|             "num": 15, | ||||
|             "title": "Dummy-DTC Warnung", | ||||
|             "description": "Ein Dummy-DTC der Schwere \"Warnung\" für Debugging-Zwecke" | ||||
|         }, | ||||
|         { | ||||
|             "num": 16, | ||||
|             "title": "Dummy-DTC Kritisch", | ||||
|             "description": "Ein Dummy-DTC der Schwere \"Kritisch\" für Debugging-Zwecke" | ||||
|         }, | ||||
|         { | ||||
|             "num": 17, | ||||
|             "title": "Last Error", | ||||
|             "description": "Last Error" | ||||
|         } | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										221
									
								
								Software/data_src/static/js/dtc_table.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								Software/data_src/static/js/dtc_table.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | ||||
| const jsonFilePath = "static/dtc_table.json"; | ||||
|  | ||||
| var dtcState = {}; | ||||
|  | ||||
| async function processDTCNotifications(dtcArray) { | ||||
|   // Entferne DTCs aus dtcState, die nicht im dtcArray enthalten sind | ||||
|   for (var key in dtcState) { | ||||
|     if (!dtcArray.some((dtc) => parseInt(dtc.split(",")[1]) == key)) { | ||||
|       delete dtcState[key]; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (dtcArray.length === 0 || dtcArray[0] == "0") { | ||||
|     dtcState = {}; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   for (var i = 0; i < dtcArray.length; i++) { | ||||
|     var dtcInfo = dtcArray[i].split(","); | ||||
|     var errorCode = parseInt(dtcInfo[1]); | ||||
|     var activity = parseInt(dtcInfo[3]); | ||||
|     var severity = parseInt(dtcInfo[2]); | ||||
|  | ||||
|     try { | ||||
|       var { title, description } = await getDescriptionForDTCNumber(errorCode); | ||||
|  | ||||
|       switch (severity) { | ||||
|         case 1: | ||||
|           severity = "info"; | ||||
|           break; | ||||
|         case 2: | ||||
|           severity = "warning"; | ||||
|           break; | ||||
|         case 3: | ||||
|           severity = "danger"; | ||||
|           break; | ||||
|       } | ||||
|  | ||||
|       if (dtcState[errorCode]) { | ||||
|         // Überprüfen, ob sich der Zustand von "previous" auf "active" geändert hat | ||||
|         if (activity !== dtcState[errorCode].activity) { | ||||
|           dtcState[errorCode].activity = activity; | ||||
|           if (activity === 1) showNotification(description, severity); | ||||
|         } | ||||
|       } else { | ||||
|         // DTC ist neu, Zustand speichern und wenn active, Benachrichtigung anzeigen | ||||
|         dtcState[errorCode] = { activity: activity }; | ||||
|         if (activity === 1) showNotification(description, severity); | ||||
|       } | ||||
|     } catch (error) { | ||||
|       console.error("Error processing DTC:", error); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| function getDescriptionForDTCNumber(number) { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     fetch(jsonFilePath) | ||||
|       .then((response) => response.json()) | ||||
|       .then((data) => { | ||||
|         const dtcList = data.dtc_table_data; | ||||
|         const foundEntry = dtcList.find((entry) => entry.num === number); | ||||
|  | ||||
|         if (foundEntry) { | ||||
|           const description = foundEntry.description; | ||||
|           const title = foundEntry.title; | ||||
|           resolve({ title, description }); | ||||
|         } else { | ||||
|           // Wenn die Nummer nicht gefunden wurde, geben Sie einen Fehler zurück | ||||
|           reject(`Beschreibung für Nummer ${number} nicht gefunden.`); | ||||
|         } | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         // Im Fehlerfall geben Sie den Fehler zurück | ||||
|         reject(error); | ||||
|       }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| async function showDTCModal(event) { | ||||
|   var dtc = parseInt(event.currentTarget.getAttribute("data-dtc")); | ||||
|   var debugval = event.currentTarget.getAttribute("data-debugval"); | ||||
|   var modal = $("#dtcModal"); | ||||
|  | ||||
|   try { | ||||
|     var { title, description } = await getDescriptionForDTCNumber(dtc); | ||||
|  | ||||
|     modal.find(".modal-title").text(title); | ||||
|     modal.find(".dtc-desc").text(description); | ||||
|  | ||||
|     if (debugval > 0) { | ||||
|       modal.find(".dtc-debugval").text("Debugvalue: " + debugval); | ||||
|     } else { | ||||
|       modal.find(".dtc-debugval").remove(); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error("Fehler beim Abrufen der Beschreibung:", error); | ||||
|     modal.find(".modal-title").text("Fehler"); | ||||
|     modal | ||||
|       .find(".dtc-desc") | ||||
|       .text("DTC-Beschreibung konnte nicht geladen werden"); | ||||
|   } | ||||
|  | ||||
|   // Modal anzeigen | ||||
|   modal.modal("show"); | ||||
| } | ||||
|  | ||||
| function fillDTCTable(dtcArray) { | ||||
|   // Referenz auf das Tabellen-Element | ||||
|   var table = document.getElementById("dtc_table"); | ||||
|   var tablediv = document.getElementById("dtc_container"); | ||||
|  | ||||
|   // Prüfen, ob DTC vorhanden sind | ||||
|   if (dtcArray.length === 0 || dtcArray[0] == "0") { | ||||
|     // Verstecke das Tabellen-Div, wenn keine DTC vorhanden sind | ||||
|     tablediv.hidden = true; | ||||
|     table.innerHTML = ""; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Prüfen, ob sich der Inhalt der Tabelle geändert hat | ||||
|   var tableContentChanged = false; | ||||
|   for (var i = 0; i < dtcArray.length; i++) { | ||||
|     var dtcInfo = dtcArray[i].split(","); | ||||
|     var errorCode = parseInt(dtcInfo[1]); | ||||
|     var activity = parseInt(dtcInfo[3]); | ||||
|  | ||||
|     if (!dtcState[errorCode] || dtcState[errorCode].activity !== activity) { | ||||
|       tableContentChanged = true; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (!tableContentChanged) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Zeige das Tabellen-Div, wenn DTC vorhanden sind | ||||
|   tablediv.hidden = false; | ||||
|  | ||||
|   // Tabelle leeren, bevor sie neu gefüllt wird | ||||
|   table.innerHTML = ""; | ||||
|  | ||||
|   // Überschriften für die Tabelle erstellen | ||||
|   var headerRow = table.insertRow(0); | ||||
|  | ||||
|   // Definition der Klassen und Scopes für die Spalten | ||||
|   var columnDefinitions = [ | ||||
|     { class: "col-6", scope: "Zeitstempel" }, | ||||
|     { class: "col-2", scope: "Fehlercode" }, | ||||
|     { class: "col-2", scope: "Schwere" }, | ||||
|     { class: "col-2", scope: "Aktiv" }, | ||||
|   ]; | ||||
|  | ||||
|   for (var i = 0; i < columnDefinitions.length; i++) { | ||||
|     var headerCell = headerRow.insertCell(i); | ||||
|     headerCell.className = `th ${columnDefinitions[i].class}`; | ||||
|     headerCell.scope = columnDefinitions[i].scope; | ||||
|     headerCell.innerHTML = columnDefinitions[i].scope; | ||||
|   } | ||||
|  | ||||
|   // DTC-Daten in die Tabelle einfügen | ||||
|   for (var i = 0; i < dtcArray.length; i++) { | ||||
|     var dtcInfo = dtcArray[i].split(","); | ||||
|  | ||||
|     var row = table.insertRow(i + 1); // +1 wegen der Überschriftenzeile | ||||
|  | ||||
|     // Zeitstempel | ||||
|     var timestampCell = row.insertCell(0); | ||||
|     timestampCell.innerHTML = formatTimestamp(parseInt(dtcInfo[0])); | ||||
|  | ||||
|     // Fehlercode | ||||
|     var errorCodeCell = row.insertCell(1); | ||||
|     errorCodeCell.innerHTML = dtcInfo[1]; | ||||
|  | ||||
|     // Schwere | ||||
|     var severityCell = row.insertCell(2); | ||||
|     var severity = parseInt(dtcInfo[2]); | ||||
|  | ||||
|     // Schwere | ||||
|     switch (severity) { | ||||
|       case 1: | ||||
|         severityCell.innerHTML = '<img src="static/img/info.png" alt="Info" />'; | ||||
|         break; | ||||
|       case 2: | ||||
|         severityCell.innerHTML = | ||||
|           '<img src="static/img/warn.png" alt="Warnung" />'; | ||||
|         break; | ||||
|       case 3: | ||||
|         severityCell.innerHTML = | ||||
|           '<img src="static/img/critical.png" alt="Kritisch" />'; | ||||
|         break; | ||||
|       default: | ||||
|         severityCell.innerHTML = | ||||
|           '<img src="static/img/none.png" alt="Unbekannt" />'; | ||||
|     } | ||||
|  | ||||
|     row.setAttribute("data-dtc", dtcInfo[1]); | ||||
|     row.setAttribute("data-debugval", dtcInfo[4]); | ||||
|     row.addEventListener("click", showDTCModal); | ||||
|  | ||||
|     // Aktivität | ||||
|     var activityCell = row.insertCell(3); | ||||
|     activityCell.innerHTML = parseInt(dtcInfo[3]) === 1 ? "active" : "previous"; | ||||
|   } | ||||
| } | ||||
|  | ||||
| function formatTimestamp(milliseconds) { | ||||
|   const date = new Date(milliseconds); | ||||
|  | ||||
|   const days = String(date.getUTCDate() - 1).padStart(2, "0"); | ||||
|   const hours = String(date.getUTCHours()).padStart(2, "0"); | ||||
|   const minutes = String(date.getUTCMinutes()).padStart(2, "0"); | ||||
|   const seconds = String(date.getUTCSeconds()).padStart(2, "0"); | ||||
|   const millisecondsFormatted = String(date.getUTCMilliseconds()).padStart( | ||||
|     3, | ||||
|     "0" | ||||
|   ); | ||||
|  | ||||
|   return `${days}-${hours}:${minutes}:${seconds}:${millisecondsFormatted}`; | ||||
| } | ||||
							
								
								
									
										27
									
								
								Software/data_src/static/js/script.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Software/data_src/static/js/script.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| $(document).ready(function () { | ||||
|     $(".navbar-nav a").on("click", function () { | ||||
|       $(".navbar-collapse").collapse("hide"); | ||||
|     }); | ||||
|    | ||||
|     $("#show_hide_password a").on("click", function (event) { | ||||
|       event.preventDefault(); | ||||
|       if ($("#show_hide_password input").attr("type") == "text") { | ||||
|         $("#show_hide_password input").attr("type", "password"); | ||||
|         $("#show_hide_password i").addClass("fa-eye-slash"); | ||||
|         $("#show_hide_password i").removeClass("fa-eye"); | ||||
|       } else if ($("#show_hide_password input").attr("type") == "password") { | ||||
|         $("#show_hide_password input").attr("type", "text"); | ||||
|         $("#show_hide_password i").removeClass("fa-eye-slash"); | ||||
|         $("#show_hide_password i").addClass("fa-eye"); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
|    | ||||
|   document | ||||
|     .querySelector(".custom-file-input") | ||||
|     .addEventListener("change", function (e) { | ||||
|       var fileName = document.getElementById("fw-update-file").files[0].name; | ||||
|       var nextSibling = e.target.nextElementSibling; | ||||
|       nextSibling.innerText = fileName; | ||||
|     }); | ||||
|    | ||||
| @@ -1,7 +1,15 @@ | ||||
| var gateway = `ws://${window.location.hostname}/ws`; | ||||
| var websocket; | ||||
|  | ||||
| window.addEventListener("load", onLoad); | ||||
| var statusMapping; | ||||
| var staticMapping; | ||||
| var overlay; | ||||
|  | ||||
| document.addEventListener("DOMContentLoaded", function () { | ||||
|   // Ihr JavaScript-Code hier, einschließlich der onLoad-Funktion | ||||
|   overlay = document.getElementById("overlay"); | ||||
|   onLoad(); | ||||
| }); | ||||
|  | ||||
| function initWebSocket() { | ||||
|   console.log("Trying to open a WebSocket connection..."); | ||||
| @@ -12,12 +20,27 @@ function initWebSocket() { | ||||
| } | ||||
|  | ||||
| function initButtons() { | ||||
|   document | ||||
|     .getElementById("btn-ws-stop") | ||||
|     .addEventListener("click", livedebug_stop); | ||||
|   document | ||||
|     .getElementById("btn-ws-start") | ||||
|     .addEventListener("click", livedebug_start); | ||||
|   var elements = document.getElementsByClassName("btn-wsevent"); | ||||
|  | ||||
|   if (elements.length > 0) { | ||||
|     for (var i = 0; i < elements.length; i++) { | ||||
|       let element = elements[i]; | ||||
|       element.addEventListener("click", sendButton); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| function initSettingInputs() { | ||||
|   var elements = document.getElementsByClassName("set-wsevent"); | ||||
|  | ||||
|   if (elements.length > 0) { | ||||
|     for (var i = 0; i < elements.length; i++) { | ||||
|       let element = elements[i]; | ||||
|       element.addEventListener("change", function () { | ||||
|         websocket_sendevent("set-" + element.id, element.value); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| function onOpen(event) { | ||||
| @@ -26,28 +49,94 @@ function onOpen(event) { | ||||
|  | ||||
| function onClose(event) { | ||||
|   console.log("Connection closed"); | ||||
|   setTimeout(initWebSocket, 2000); | ||||
|   setTimeout(initWebSocket, 1000); | ||||
|   overlay.style.display = "flex"; | ||||
| } | ||||
|  | ||||
| function sendButton(event) { | ||||
|   var targetElement = event.target; | ||||
|  | ||||
|   if ( | ||||
|     targetElement.classList.contains("confirm") && | ||||
|     window.confirm("Sicher?") == false | ||||
|   ) | ||||
|     return; | ||||
|  | ||||
|   websocket_sendevent("btn-" + targetElement.id, targetElement.value); | ||||
| } | ||||
|  | ||||
| function onMessage(event) { | ||||
|   var livedebug_out = document.getElementById("livedebug-out"); | ||||
|   var textarea_heigth = livedebug_out.scrollHeight; | ||||
|   livedebug_out.value += event.data; | ||||
|   livedebug_out.scrollTop = livedebug_out.scrollHeight; | ||||
|   do_resize(livedebug_out); | ||||
|   var data = event.data; | ||||
|  | ||||
|   if (data.startsWith("NOTIFY:")) { | ||||
|     var notify_data = data.slice(7).split(";")[1]; | ||||
|     var notify_type = data.slice(7).split(";")[0]; | ||||
|     showNotification(notify_data, notify_type); | ||||
|   } else if (data.startsWith("DEBUG:")) { | ||||
|     var addtext = data.slice(6); | ||||
|     var livedebug_out = document.getElementById("livedebug-out"); | ||||
|     livedebug_out.value += addtext; | ||||
|     livedebug_out.scrollTop = livedebug_out.scrollHeight; | ||||
|     do_resize(livedebug_out); | ||||
|   } else if (data.startsWith("DTC:")) { | ||||
|     const dtcs = data.slice(4); | ||||
|     const dtcArray = dtcs.trim() !== "" ? dtcs.split(";").filter(Boolean) : []; | ||||
|  | ||||
|     processDTCNotifications(dtcArray); | ||||
|     fillDTCTable(dtcArray); | ||||
|   } else if (data.startsWith("MAPPING_STATUS:")) { | ||||
|     const data_sliced = data.slice(15); | ||||
|     statusMapping = createMapping(data_sliced); | ||||
|   } else if (data.startsWith("MAPPING_STATIC:")) { | ||||
|     const data_sliced = data.slice(15); | ||||
|     staticMapping = createMapping(data_sliced); | ||||
|     console.log(staticMapping); | ||||
|   } else if (data.startsWith("STATUS:")) { | ||||
|     const data_sliced = data.slice(7); | ||||
|     const result = processDataString(data_sliced, statusMapping); | ||||
|     fillValuesToHTML(result); | ||||
|   } else if (data.startsWith("STATIC:")) { | ||||
|     const data_sliced = data.slice(7); | ||||
|     const result = processDataString(data_sliced, staticMapping); | ||||
|     fillValuesToHTML(result); | ||||
|     console.log(result); | ||||
|     overlay.style.display = "none"; | ||||
|   } | ||||
| } | ||||
|  | ||||
| function createMapping(mappingString) { | ||||
|   const mappingArray = mappingString.split(";"); | ||||
|   const mapping = []; | ||||
|  | ||||
|   mappingArray.forEach((variable) => { | ||||
|     if (variable !== null) mapping.push(variable.trim()); | ||||
|   }); | ||||
|   return mapping; | ||||
| } | ||||
|  | ||||
| function processDataString(dataString, mapping) { | ||||
|   const valuesArray = dataString.split(";"); | ||||
|   const dataObject = {}; | ||||
|  | ||||
|   valuesArray.forEach((value, index) => { | ||||
|     const variable = mapping[index]; | ||||
|     if (variable) { | ||||
|       dataObject[variable] = value.trim(); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   return dataObject; | ||||
| } | ||||
|  | ||||
| function onLoad(event) { | ||||
|   initWebSocket(); | ||||
|   initButtons(); | ||||
|   initSettingInputs(); | ||||
|   overlay.style.display = "flex"; | ||||
| } | ||||
|  | ||||
| function livedebug_start() { | ||||
|   websocket.send("start"); | ||||
| } | ||||
|  | ||||
| function livedebug_stop() { | ||||
|   websocket.send("stop"); | ||||
| function websocket_sendevent(element_id, element_value) { | ||||
|   websocket.send(element_id + ":" + element_value); | ||||
| } | ||||
|  | ||||
| function do_resize(textbox) { | ||||
| @@ -67,18 +156,120 @@ function do_resize(textbox) { | ||||
|   else textbox.rows = rows; | ||||
| } | ||||
|  | ||||
| function notifyMe() { | ||||
|   if (!("Notification" in window)) { | ||||
|     alert("This browser does not support desktop notification"); | ||||
|   } else if (Notification.permission === "granted") { | ||||
|     const notification = new Notification("Hi there!"); | ||||
|     // … | ||||
|   } else if (Notification.permission !== "denied") { | ||||
|     Notification.requestPermission().then((permission) => { | ||||
|       if (permission === "granted") { | ||||
|         const notification = new Notification("Hi there!"); | ||||
|         // … | ||||
| function fillValuesToHTML(dataset) { | ||||
|   for (var key in dataset) { | ||||
|     var key_prefixed = "data-" + key; | ||||
|     var elements = document.getElementsByClassName(key_prefixed); | ||||
|  | ||||
|     if (elements.length > 0) { | ||||
|       for (var i = 0; i < elements.length; i++) { | ||||
|         var element = elements[i]; | ||||
|  | ||||
|         if (element.type === "checkbox") { | ||||
|           // Wenn das Element ein Kontrollkästchen ist | ||||
|           element.checked = dataset[key] == 1 ? true : false; | ||||
|         } else if (element.tagName === "SELECT") { | ||||
|           // Wenn das Element ein Dropdown ist | ||||
|           setDropdownValue(element, dataset[key]); | ||||
|         } else if (element.classList.contains("progress-bar")) { | ||||
|           // Wenn das Element eine Fortschrittsleiste ist | ||||
|           updateProgressBar(element, dataset[key]); | ||||
|         } else if (element.classList.contains("hideable")) { | ||||
|           // Wenn das Element ein Settingsabschnitt-div ist | ||||
|           if (dataset[key] == 0) element.style.display = "none"; | ||||
|           else element.style.display = ""; | ||||
|         } else if (element.tagName === "DIV" || element.tagName === "SPAN") { | ||||
|           if (element.classList.contains("format-time")) { | ||||
|             element.innerText = formatTime(dataset[key]); | ||||
|           } else if (element.classList.contains("faction-logo")) { | ||||
|             // Faction-Logo-Logik | ||||
|             updateFactionLogo(element, dataset[key]); | ||||
|           } else { | ||||
|             element.innerText = dataset[key]; | ||||
|           } | ||||
|         } else { | ||||
|           // Standardmäßig für Textfelder und andere Elemente | ||||
|           element.value = dataset[key]; | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|     } | ||||
|     // Aktualisiere den <title>-Tag, wenn der Schlüssel 'devicename' im dataset vorhanden ist | ||||
|     if (key === "devicename") { | ||||
|       document.title = dataset[key]; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| function formatTime(seconds) { | ||||
|   var hrs = Math.floor(seconds / 3600); | ||||
|   var mins = Math.floor((seconds % 3600) / 60); | ||||
|   var secs = seconds % 60; | ||||
|  | ||||
|   return ( | ||||
|     String(hrs).padStart(2, "0") + | ||||
|     ":" + | ||||
|     String(mins).padStart(2, "0") + | ||||
|     ":" + | ||||
|     String(secs).padStart(2, "0") | ||||
|   ); | ||||
| } | ||||
|  | ||||
| function updateFactionLogo(element, faction) { | ||||
|   const glowClass = "glow-active-faction"; | ||||
|   const factionClasses = ["faction1", "faction2", "faction3"]; | ||||
|  | ||||
|   factionClasses.forEach((factionClass) => { | ||||
|     if ( | ||||
|       factionClass === "faction" + faction && | ||||
|       element.classList.contains(factionClass) | ||||
|     ) { | ||||
|       element.classList.add(glowClass); | ||||
|     } else { | ||||
|       element.classList.remove(glowClass); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
|  | ||||
| // Funktion zum Setzen des ausgewählten Werts für Dropdowns | ||||
| function setDropdownValue(selectElement, value) { | ||||
|   for (var i = 0; i < selectElement.options.length; i++) { | ||||
|     if (selectElement.options[i].value === value) { | ||||
|       selectElement.selectedIndex = i; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Funktion zum Aktualisieren der Fortschrittsleiste | ||||
| function updateProgressBar(progressBar, value) { | ||||
|   // Wert in das aria-valuenow-Attribut einfügen | ||||
|   progressBar.setAttribute("aria-valuenow", value); | ||||
|  | ||||
|   // Breite des Fortschrittsbalkens und inneren Text aktualisieren | ||||
|   progressBar.style.width = value + "%"; | ||||
|   progressBar.textContent = value + "%"; | ||||
| } | ||||
|  | ||||
| function showNotification(message, type) { | ||||
|   // Erstellen Sie ein Bootstrap-Alert-Element | ||||
|   var alertElement = $( | ||||
|     '<div class="alert alert-' + | ||||
|       type + | ||||
|       ' alert-dismissible fade show notification" role="alert">' + | ||||
|       "<strong>" + | ||||
|       message + | ||||
|       "</strong>" + | ||||
|       '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' + | ||||
|       '<span aria-hidden="true">×</span>' + | ||||
|       "</button>" + | ||||
|       "</div>" | ||||
|   ); | ||||
|  | ||||
|   // Fügen Sie das Alert-Element dem Container hinzu | ||||
|   $("#notification-container").append(alertElement); | ||||
|  | ||||
|   // Nach 5 Sekunden das Alert-Element ausblenden | ||||
|   setTimeout(function () { | ||||
|     alertElement.alert("close"); | ||||
|   }, 5000); | ||||
| } | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "kein EEPROM gefunden", | ||||
|     "description": "Es wurde kein EEPROM gefunden. Dies lässt einen Hardware-Defekt vermuten." | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "Power-Monitor Fehler", | ||||
|     "description": "Es gibt ein Problem mit dem Power-Monitoring. Die Akku-Überwachung ist ohne Funktion! Bitte Hardware prüfen" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "Akku-Spannung niedrig", | ||||
|     "description": "Die Akkuspannung ist niedrig. Bitte Akku bald aufladen!" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "Akku-Spannung kritisch", | ||||
|     "description": "Die Akkuspannung ist sehr niedrig. Bitte Akku umgehend ersetzen um eine schädliche Tiefentladung zu vermeiden!" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "EEPROM-Migration failed", | ||||
|     "description": "Migration of EEPROM-Image from an other FW-Version failed - you need to reset everything manually!" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "EEPROM CFG Checksumme", | ||||
|     "description": "Die Checksumme der Config-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "EEPROM PDS Checksumme", | ||||
|     "description": "Die Checksumme der Betriebsdaten-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "EEPROM PDS Adresse", | ||||
|     "description": "Die Adresse der Betriebsdaten-Partition im EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "EEPROM Version falsch", | ||||
|     "description": "Die Layout-Version des EEPROM stimmt nicht mit der Firmware-Version überein. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "Flashstorage Fehler", | ||||
|     "description": "Der Flashstorage konnte nicht initialisiert werden. Aktualisieren sie Flash & Firmware" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "Flashstorage Version falsch", | ||||
|     "description": "Die Version des Flashstorage stimmt nicht mit der Firmware-Version überein. Aktualisieren sie den Flash mit der passenden Update-Datei" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "Config-Validierung", | ||||
|     "description": "Ein oder mehrer Einstellungswerte sind ausserhalb plausibler Werte. Prüfen Sie Ihre Einstellungen" | ||||
| } | ||||
| @@ -1,4 +0,0 @@ | ||||
| { | ||||
|     "title": "LoRa-Modul Fehler", | ||||
|     "description": "Es gibt ein Problem mit dem LoRa-Modul. Es konnte keine LoRa-Verbindung aufgebaut werden. Bitte Hardware prüfen" | ||||
| } | ||||
| @@ -1 +0,0 @@ | ||||
| 1.03 | ||||
| @@ -1,8 +0,0 @@ | ||||
| import subprocess | ||||
|  | ||||
| revision = ( | ||||
|     subprocess.check_output(["git", "rev-parse", "--short=10", "HEAD"]) | ||||
|     .strip() | ||||
|     .decode("utf-8") | ||||
| ) | ||||
| print("-DGIT_REV='\"%s\"'" % revision) | ||||
| @@ -3,14 +3,20 @@ | ||||
| #define _COMMON_H_ | ||||
|  | ||||
| #include <stdint.h> | ||||
| #include <stddef.h> | ||||
|  | ||||
| #define Q(x) #x | ||||
| #define QUOTE(x) Q(x) | ||||
| #define SET_BIT(value, bitPosition) ((value) |= (1U << (bitPosition))) | ||||
|  | ||||
| #define TRUE 1 | ||||
| #define FALSE 0 | ||||
|  | ||||
| #define HOST_NAME "AirsoftTimer_%08X" | ||||
| #ifndef DEVICE_NAME | ||||
| #define HOST_NAME "AirsoftTimer" | ||||
| #else | ||||
| #define HOST_NAME DEVICE_NAME | ||||
| #endif | ||||
|  | ||||
| #define SHUTDOWN_DELAY_MS 5000 | ||||
| #define STARTUP_DELAY_MS 20000 | ||||
| @@ -44,6 +50,27 @@ | ||||
| #define OTA_DELAY 50 // ticks -> 10ms / tick | ||||
| #endif | ||||
|  | ||||
| typedef enum eSystem_Status | ||||
| { | ||||
|   sysStat_Init, | ||||
|   sysStat_Startup, | ||||
|   sysStat_Normal, | ||||
|   sysStat_Error, | ||||
|   sysStat_Shutdown | ||||
| } tSystem_Status; | ||||
|  | ||||
| typedef enum batteryType_e | ||||
| { | ||||
|   BATTERY_UNDEFINED, | ||||
|   BATTERY_LIPO_2S, | ||||
|   BATTERY_LIPO_3S | ||||
| } batteryType_t; | ||||
|  | ||||
| // String representation of SpeedSource enum | ||||
| extern const char *BatteryString[]; | ||||
| extern const size_t BatteryString_Elements; | ||||
|  | ||||
| #define STARTUP_DELAY 2500 | ||||
| #define SHUTDOWN_DELAY_MS 2500 | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,16 +1,31 @@ | ||||
| /** | ||||
|  * @file debugger.h | ||||
|  * | ||||
|  * @brief Header file for debugging functions and status in the DE-Timer application. | ||||
|  * | ||||
|  * This file declares functions and status definitions for debugging purposes in the DE-Timer project. | ||||
|  * It includes functions to print system information, WiFi information, format EEPROM data, | ||||
|  * handle debug messages, and manage the status of different debug ports. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date   09.01.2024 | ||||
|  */ | ||||
|  | ||||
| #ifndef _DEBUGGER_H_ | ||||
| #define _DEBUGGER_H_ | ||||
|  | ||||
| #include <Arduino.h> | ||||
| #include "webui.h" | ||||
|  | ||||
| const char PROGMEM helpCmd[] = "sysinfo     - System Info\n" | ||||
|                                "reboot      - System Reboot\n" | ||||
|                                "netinfo     - WiFi Info\n" | ||||
|                                "formatPDS   - Format Persistence EEPROM Data\n" | ||||
|                                "formatCFG   - Format Configuration EEPROM Data\n" | ||||
|                                "checkEE     - Check EEPROM with checksum\n" | ||||
|                                "dumpEE1k    - dump the first 1kb of EEPROM to Serial\n" | ||||
|                                "dumpEE      - dump the whole EPPROM to Serial\n" | ||||
|                                "killEE      - kill the first 1024 byte of EEPROM\n" | ||||
|                                "zeroEE      - zero the first 1024 byte of EEPROM\n" | ||||
|                                "resetPageEE - Reset the PersistenceData Page\n" | ||||
|                                "dumpCFG     - print Config struct\n" | ||||
|                                "dumpPDS     - print PersistanceStruct\n" | ||||
|   | ||||
| @@ -1,57 +1,39 @@ | ||||
| /** | ||||
|  * @file dtc.h | ||||
|  * | ||||
|  * @brief Header file for handling Diagnostic Trouble Codes (DTC) in the DE-Timer application. | ||||
|  * | ||||
|  * This file provides definitions and functions for handling Diagnostic Trouble Codes (DTC) | ||||
|  * in the DE-Timer project. It includes structures for DTC entries, severity levels, | ||||
|  * and functions for DTC maintenance and processing. DTCs are used to track system errors and issues. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date   09.01.2024 | ||||
|  */ | ||||
|  | ||||
| #ifndef _DTC_H_ | ||||
| #define _DTC_H_ | ||||
|  | ||||
| #include <Arduino.h> | ||||
| #include "dtc_defs.h" | ||||
|  | ||||
| #define MAX_DTC_STORAGE 12 | ||||
|  | ||||
| typedef enum DTCNums_e | ||||
| typedef struct | ||||
| { | ||||
|   DTC_NO_EEPROM_FOUND = 1, | ||||
|   DTC_EEPROM_CFG_BAD, | ||||
|   DTC_EEPROM_PDS_BAD, | ||||
|   DTC_EEPROM_PDSADRESS_BAD, | ||||
|   DTC_EEPROM_VERSION_BAD, | ||||
|   DTC_FLASHFS_ERROR, | ||||
|   DTC_FLASHFS_VERSION_ERROR, | ||||
|   DTC_EEPROM_CFG_SANITY, | ||||
|   DTC_NO_LORA_FOUND, | ||||
|   DTC_NO_BATMNON_FOUND, | ||||
|   DTC_BAT_LOW, | ||||
|   DTC_BAT_CRITICAL, | ||||
|   DTC_EEPROM_MIGRATE_FAILED, | ||||
|   DTC_LAST_DTC | ||||
| } DTCNums_t; | ||||
|  | ||||
| typedef enum DTCActive_e | ||||
| { | ||||
|   DTC_NONE, | ||||
|   DTC_ACTIVE, | ||||
|   DTC_PREVIOUS | ||||
| } DTCActive_t; | ||||
|  | ||||
| typedef enum DTCSeverity_e | ||||
| { | ||||
|   DTC_INFO, | ||||
|   DTC_WARN, | ||||
|   DTC_CRITICAL | ||||
| } DTCSeverity_t; | ||||
|  | ||||
| typedef struct DTCEntry_s | ||||
| { | ||||
|   DTCNums_t Number; | ||||
|   DTCNum_t Number; | ||||
|   uint32_t timestamp; | ||||
|   DTCActive_t active; | ||||
|   DTCSeverity_t severity; | ||||
|   uint32_t debugVal; | ||||
| } DTCEntry_t; | ||||
|  | ||||
| void MaintainDTC(DTCNums_t DTC_no, DTCSeverity_t DTC_severity, boolean active, uint32_t DebugValue = 0); | ||||
| void ClearDTC(DTCNums_t DTC_no); | ||||
| void MaintainDTC(DTCNum_t DTC_no, boolean active, uint32_t DebugValue = 0); | ||||
| void ClearDTC(DTCNum_t DTC_no); | ||||
| void ClearAllDTC(); | ||||
| DTCNums_t getlastDTC(boolean only_active); | ||||
| DTCNums_t getlastDTC_Severity(boolean only_active, DTCSeverity_t severity); | ||||
| DTCNum_t getlastDTC(boolean only_active); | ||||
| DTCNum_t ActiveDTCseverity(DTCSeverity_t severity); | ||||
| DTCSeverity_t getSeverityForDTC(DTCNum_t targetCode); | ||||
| void DTC_Process(); | ||||
|  | ||||
| extern DTCEntry_s DTCStorage[MAX_DTC_STORAGE]; | ||||
| extern DTCEntry_t DTCStorage[MAX_DTC_STORAGE]; | ||||
| #endif | ||||
							
								
								
									
										85
									
								
								Software/include/dtc_defs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								Software/include/dtc_defs.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| /** | ||||
|  * @file dtc_defs.h | ||||
|  * | ||||
|  * @brief Header file for Diagnostic Trouble Code (DTC) definitions in the DE-Timer application. | ||||
|  * | ||||
|  * This file contains definitions for Diagnostic Trouble Codes (DTC) in the DE-Timer project. | ||||
|  * It includes enums for DTC active status, severity levels, and specific DTC codes. | ||||
|  * The file also defines an array of DTC definitions and a timestamp indicating the generation time. | ||||
|  * | ||||
|  * @note This file is auto-generated by a script on 2024-05-30 21:56:51. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date   30.05.2024 | ||||
|  */ | ||||
|  | ||||
| #ifndef DTC_DEFS_H | ||||
| #define DTC_DEFS_H | ||||
|  | ||||
| #include <stdint.h> | ||||
|  | ||||
| typedef uint32_t DTCNum_t; | ||||
|  | ||||
| typedef enum | ||||
| { | ||||
|   DTC_INACTIVE, | ||||
|   DTC_ACTIVE, | ||||
|   DTC_PREVIOUS | ||||
| } DTCActive_t; | ||||
|  | ||||
| typedef enum | ||||
| { | ||||
|   DTC_NONE, | ||||
|   DTC_INFO, | ||||
|   DTC_WARN, | ||||
|   DTC_CRITICAL | ||||
| } DTCSeverity_t; | ||||
|  | ||||
| typedef struct { | ||||
|   DTCNum_t code; | ||||
|   DTCSeverity_t severity; | ||||
| } DTC_t; | ||||
|  | ||||
| #define DTC_NO_DTC                     0 | ||||
| #define DTC_BAT_CRITICAL               1 | ||||
| #define DTC_BAT_LOW                    2 | ||||
| #define DTC_NO_EEPROM_FOUND            3 | ||||
| #define DTC_EEPROM_CFG_BAD             4 | ||||
| #define DTC_EEPROM_PDS_BAD             5 | ||||
| #define DTC_EEPROM_PDSADRESS_BAD       6 | ||||
| #define DTC_EEPROM_VERSION_BAD         7 | ||||
| #define DTC_FLASHFS_ERROR              8 | ||||
| #define DTC_FLASHFS_VERSION_ERROR      9 | ||||
| #define DTC_NO_BATMNON_FOUND           10 | ||||
| #define DTC_NO_LORA_FOUND              11 | ||||
| #define DTC_EEPROM_CFG_SANITY          12 | ||||
| #define DTC_EEPROM_MIGRATE_FAILED      13 | ||||
| #define DTC_FAKE_DTC_INFO              14 | ||||
| #define DTC_FAKE_DTC_WARN              15 | ||||
| #define DTC_FAKE_DTC_CRIT              16 | ||||
| #define DTC_LAST_DTC                   17 | ||||
|  | ||||
| const DTC_t dtc_definitions[] = { | ||||
|     { DTC_NO_DTC                    , DTC_NONE     }, // No Error | ||||
|     { DTC_BAT_CRITICAL              , DTC_CRITICAL }, // Akku ist komplett leer. Den Akku aufladen! | ||||
|     { DTC_BAT_LOW                   , DTC_WARN     }, // Akku ist unter der Warnschwelle. Den Akku demnächst aufladen | ||||
|     { DTC_NO_EEPROM_FOUND           , DTC_CRITICAL }, // Es wurde kein EEPROM gefunden. Dies lässt einen Hardware-Defekt vermuten. | ||||
|     { DTC_EEPROM_CFG_BAD            , DTC_CRITICAL }, // Die Checksumme der Config-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück | ||||
|     { DTC_EEPROM_PDS_BAD            , DTC_CRITICAL }, // Die Checksumme der Betriebsdaten-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück | ||||
|     { DTC_EEPROM_PDSADRESS_BAD      , DTC_CRITICAL }, // Die Adresse der Betriebsdaten-Partition im EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück | ||||
|     { DTC_EEPROM_VERSION_BAD        , DTC_CRITICAL }, // Die Layout-Version des EEPROM stimmt nicht mit der Firmware-Version überein. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück | ||||
|     { DTC_FLASHFS_ERROR             , DTC_CRITICAL }, // Der Flashspeicher konnte nicht initialisiert werden. Aktualisieren sie Flash & Firmware | ||||
|     { DTC_FLASHFS_VERSION_ERROR     , DTC_CRITICAL }, // Die Version des Flashspeicher stimmt nicht mit der Firmware-Version überein. Aktualisieren sie den Flash mit der passenden Update-Datei | ||||
|     { DTC_NO_BATMNON_FOUND          , DTC_CRITICAL }, // Es wurde keine Akkuüberwachung über I2C gefunden, Prüfen sie die Hardware! | ||||
|     { DTC_NO_LORA_FOUND             , DTC_CRITICAL }, // Es konnte keine Verbindung zum LoRa-Transceiver hergestellt werden. Prüfen Sie die Hardware auf Defekte | ||||
|     { DTC_EEPROM_CFG_SANITY         , DTC_WARN     }, // Ein oder mehrer Einstellungswerte sind ausserhalb plausibler Werte. Prüfen Sie Ihre Einstellungen | ||||
|     { DTC_EEPROM_MIGRATE_FAILED     , DTC_CRITICAL }, // Es wurde ein altes EEPROm Image erkannt, konnte aber nicht migriert werden. EEPROM manuell zurück setzen und neue Einstellunge speichern. | ||||
|     { DTC_FAKE_DTC_INFO             , DTC_INFO     }, // Ein Dummy-DTC der Schwere "Info" für Debugging-Zwecke | ||||
|     { DTC_FAKE_DTC_WARN             , DTC_WARN     }, // Ein Dummy-DTC der Schwere "Warnung" für Debugging-Zwecke | ||||
|     { DTC_FAKE_DTC_CRIT             , DTC_CRITICAL }, // Ein Dummy-DTC der Schwere "Kritisch" für Debugging-Zwecke | ||||
|     { DTC_LAST_DTC                  , DTC_NONE     }  // Last Error | ||||
| }; | ||||
|  | ||||
| #endif // DTC_DEFS_H | ||||
|  | ||||
| // CODEGENERATOR_CHECKSUM: 313d59949b074024df3c5d796f65e3bd518e34f0bb171185c30f008f21c19d30 | ||||
| @@ -1,52 +1,63 @@ | ||||
| /** | ||||
|  * @file eeprom.h | ||||
|  * | ||||
|  * @brief Header file for configuration settings and EEPROM operations in the DE-Timer application. | ||||
|  * | ||||
|  * This file defines configuration settings for the DE-Timer project, including default values, | ||||
|  * EEPROM structures, and functions for EEPROM operations. It also defines enums for different Battery Types. | ||||
|  * Additionally, it includes functions for EEPROM handling such as storing, retrieving, and formatting configuration data. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date   09.01.2024 | ||||
|  */ | ||||
|  | ||||
| #ifndef _EEPROM_H_ | ||||
| #define _EEPROM_H_ | ||||
|  | ||||
| #include <Arduino.h> | ||||
| #include <Wire.h> | ||||
| #include <I2C_eeprom.h> | ||||
|  | ||||
| #include "globals.h" | ||||
| #include "dtc.h" | ||||
| #include "common.h" | ||||
| #include "debugger.h" | ||||
|  | ||||
| #define I2C_EE_ADDRESS 0x50 | ||||
| #define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC64 | ||||
| #define EEPROM_ENDURANCE 1000000 | ||||
| #define EEPROM_STRUCTURE_REVISION 4 // Increment this version when changing EEPROM structures | ||||
| #define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC256 | ||||
|  | ||||
| typedef enum | ||||
| typedef enum Factions_e | ||||
| { | ||||
|   NONE, | ||||
|   FACTION_1, | ||||
|   FACTION_2, | ||||
|   FACTION_3 | ||||
| } factions_t; | ||||
| } Factions_t; | ||||
|  | ||||
| typedef enum EERequest_e | ||||
| { | ||||
|   EE_IDLE, | ||||
|   EE_CFG_SAVE, | ||||
|   EE_CFG_LOAD, | ||||
|   EE_CFG_FORMAT, | ||||
|   EE_PDS_SAVE, | ||||
|   EE_PDS_LOAD, | ||||
|   EE_PDS_FORMAT, | ||||
|   EE_FORMAT_ALL, | ||||
|   EE_ALL_SAVE | ||||
|  | ||||
| } EERequest_t; | ||||
|  | ||||
| // Structure for persistence data stored in EEPROM | ||||
| typedef struct | ||||
| { | ||||
|   uint32_t writeCycleCounter = 0; | ||||
|   uint32_t faction_1_timer = 0; | ||||
|   uint32_t faction_2_timer = 0; | ||||
|   uint32_t faction_3_timer = 0; | ||||
|   factions_t activeFaction = NONE; | ||||
|   uint32_t checksum = 0; | ||||
|   uint32_t writeCycleCounter; | ||||
|   uint32_t faction_1_timer; | ||||
|   uint32_t faction_2_timer; | ||||
|   uint32_t faction_3_timer; | ||||
|   Factions_t activeFaction; | ||||
|   uint32_t checksum; | ||||
| } persistenceData_t; | ||||
|  | ||||
| extern persistenceData_t PersistenceData; | ||||
| typedef enum | ||||
| { | ||||
|   BATTERY_UNDEFINED, | ||||
|   BATTERY_LIPO_2S, | ||||
|   BATTERY_LIPO_3S | ||||
| } batteryType_t; | ||||
|  | ||||
| const char BatteryString[][10]{ | ||||
|     "Undefined", | ||||
|     "LiPo 2S", | ||||
|     "LiPo 3S"}; | ||||
|  | ||||
| const size_t BatteryString_Elements = sizeof(BatteryString) / sizeof(BatteryString[0]); | ||||
|  | ||||
| // Structure for configuration settings stored in EEPROM | ||||
| typedef struct | ||||
| { | ||||
|   uint8_t EEPROM_Version; | ||||
| @@ -55,25 +66,31 @@ typedef struct | ||||
|   char Faction_1_Name[33]; | ||||
|   char Faction_2_Name[33]; | ||||
|   char Faction_3_Name[33]; | ||||
|   char wifi_ap_ssid[33]; | ||||
|   char wifi_ap_password[64]; | ||||
|   char wifi_client_ssid[33]; | ||||
|   char wifi_client_password[64]; | ||||
|   bool wifi_autoconnect; | ||||
|   uint32_t checksum; | ||||
| } configData_t; | ||||
|  | ||||
| extern configData_t ConfigData; | ||||
|  | ||||
| // Default configuration settings | ||||
| const configData_t ConfigData_defaults = { | ||||
|     2,                      // EEPROM_Version (incerease this if anything on Layout changes!) | ||||
|     BATTERY_LIPO_3S,        // batteryType | ||||
|     false,                  // active_faction_on_reboot | ||||
|     "FACTION 1",            // Faction_1_Name | ||||
|     "FACTION 2",            // Faction_2_Name | ||||
|     "FACTION 3",            // Faction_3_Name | ||||
|     0                       // checksum | ||||
|     2,               // EEPROM_Version (incerease this if anything on Layout changes!) | ||||
|     BATTERY_LIPO_3S, // batteryType | ||||
|     false,           // active_faction_on_reboot | ||||
|     "FACTION 1",     // Faction_1_Name | ||||
|     "FACTION 2",     // Faction_2_Name | ||||
|     "FACTION 3",     // Faction_3_Name | ||||
|     HOST_NAME, | ||||
|     QUOTE(WIFI_AP_PASSWORD), | ||||
|     QUOTE(WIFI_SSID_CLIENT), | ||||
|     QUOTE(WIFI_PASSWORD_CLIENT), | ||||
|     true, | ||||
|     0 // checksum | ||||
| }; | ||||
|  | ||||
| const uint16_t startofConfigData = 16; | ||||
| const uint16_t startofPersistence = 16 + sizeof(ConfigData) + (sizeof(ConfigData) % 16); | ||||
|  | ||||
| void InitEEPROM(); | ||||
| boolean InitEEPROM(); | ||||
| void EEPROM_Process(); | ||||
| void StoreConfig_EEPROM(); | ||||
| void GetConfig_EEPROM(); | ||||
| @@ -85,5 +102,10 @@ uint32_t Checksum_EEPROM(uint8_t const *data, size_t len); | ||||
| void dumpEEPROM(uint16_t memoryAddress, uint16_t length); | ||||
| void MovePersistencePage_EEPROM(boolean reset); | ||||
| uint32_t ConfigSanityCheck(bool autocorrect = false); | ||||
| void writeSequentialToEEPROM(uint16_t memoryAddress, uint16_t length); | ||||
| void writeZeroToEEPROM(uint16_t memoryAddress, uint16_t length); | ||||
|  | ||||
| #endif // _EEPROM_H_ | ||||
| extern configData_t ConfigData; | ||||
| extern persistenceData_t PersistenceData; | ||||
| extern uint16_t eePersistenceMarker; | ||||
| #endif // _CONFIG_H_ | ||||
|   | ||||
| @@ -2,68 +2,44 @@ | ||||
| #define _GLOBALS_H_ | ||||
|  | ||||
| #include <Arduino.h> | ||||
|  | ||||
| typedef enum eSystem_Status | ||||
| { | ||||
|   sysStat_null, | ||||
|   sysStat_Startup, | ||||
|   sysStat_Normal, | ||||
|   sysStat_Error, | ||||
|   sysStat_Shutdown | ||||
| } tSystem_Status; | ||||
|  | ||||
| const char sSystem_Status_txt[][9] = { | ||||
|     "Null", | ||||
|     "Startup", | ||||
|     "Normal", | ||||
|     "Error", | ||||
|     "Shutdown" | ||||
| }; | ||||
|  | ||||
| typedef enum eEERequest | ||||
| { | ||||
|   EE_IDLE, | ||||
|   EE_CFG_SAVE, | ||||
|   EE_CFG_LOAD, | ||||
|   EE_CFG_FORMAT, | ||||
|   EE_PDS_SAVE, | ||||
|   EE_PDS_LOAD, | ||||
|   EE_PDS_FORMAT, | ||||
|   EE_FORMAT_ALL, | ||||
|   EE_ALL_SAVE | ||||
|  | ||||
| } tEERequest; | ||||
|  | ||||
| #include "eeprom.h" | ||||
| #include "common.h" | ||||
| typedef struct Globals_s | ||||
| { | ||||
|   char DeviceName[33]; | ||||
|   char DeviceName_ID[43]; | ||||
|   char FlashVersion[10]; | ||||
|   tSystem_Status systemStatus = sysStat_Startup; | ||||
|   eEERequest requestEEAction = EE_IDLE; | ||||
|   uint16_t eePersistanceAdress; | ||||
|   tSystem_Status systemStatus = sysStat_Startup; /**< Current system status */ | ||||
|   tSystem_Status resumeStatus = sysStat_Startup; /**< Status to resume after rain mode */ | ||||
|   char systemStatustxt[16] = "";                 /**< Text representation of system status */ | ||||
|   EERequest_t requestEEAction = EE_IDLE;         /**< EEPROM-related request */ | ||||
|   char DeviceName[25];                           /**< Device name */ | ||||
|   char DeviceNameId[sizeof(DeviceName) + 8];     /**< Device name plus 8 chars chipID */ | ||||
|   char FlashVersion[10];                         /**< Flash version */ | ||||
|   uint16_t eePersistanceAdress;                  /**< EEPROM persistence address */ | ||||
|   bool hasDTC; | ||||
|   int loadvoltage_mV = 0; | ||||
|   int battery_level = 0; | ||||
|   bool timer_disabled = false; | ||||
| } Globals_t; | ||||
|  | ||||
| extern Globals_t globals; | ||||
| extern Globals_t globals; /**< Global variable struct */ | ||||
|  | ||||
| typedef struct Constants_s | ||||
| { | ||||
|   uint8_t FW_Version_major; | ||||
|   uint8_t FW_Version_minor; | ||||
|   uint8_t Required_Flash_Version_major; | ||||
|   uint8_t Required_Flash_Version_minor; | ||||
|   char GitHash[11]; | ||||
|   uint8_t FW_Version_major;             /**< Firmware version major number */ | ||||
|   uint8_t FW_Version_minor;             /**< Firmware version minor number */ | ||||
|   uint8_t Required_Flash_Version_major; /**< Required flash version major number */ | ||||
|   uint8_t Required_Flash_Version_minor; /**< Required flash version minor number */ | ||||
|   char GitHash[11];                     /**< Git hash string */ | ||||
| } Constants_t; | ||||
|  | ||||
| const Constants_t constants PROGMEM = { | ||||
|  1,3,     // Firmware_Version | ||||
|  1,3,     // Required Flash Version | ||||
|  GIT_REV  // Git-Hash-String | ||||
|     FW_MAJOR, FW_MINOR,   // Firmware_Version | ||||
|     FL_MAJOR, FL_MINOR,   // Required Flash Version | ||||
|     GIT_REV // Git-Hash-String | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * @brief Initializes global variables. | ||||
|  */ | ||||
| void initGlobals(); | ||||
|  | ||||
| #endif | ||||
| #endif // _GLOBALS_H_ | ||||
|   | ||||
| @@ -1,20 +0,0 @@ | ||||
| #ifndef _OLED_DISPLAY_H_ | ||||
| #define _OLED_DISPLAY_H_ | ||||
|  | ||||
| #include <Arduino.h> | ||||
| #include <Wire.h> | ||||
| #include <Adafruit_GFX.h> | ||||
| #include <Adafruit_SSD1306.h> | ||||
| #include "globals.h" | ||||
| #include "eeprom.h" | ||||
|  | ||||
| #define OLED_SDA 4 | ||||
| #define OLED_SCL 15 | ||||
| #define OLED_RST 16 | ||||
| #define SCREEN_WIDTH 128 // OLED display width, in pixels | ||||
| #define SCREEN_HEIGHT 64 // OLED display height, in pixels | ||||
|  | ||||
| void OLED_Init(); | ||||
| void OLED_Process(); | ||||
|  | ||||
| #endif | ||||
| @@ -19,10 +19,10 @@ | ||||
| #endif | ||||
|  | ||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||
|     #ifndef WIFI_CLIENT_PASSWORD | ||||
|     #ifndef WIFI_PASSWORD_CLIENT | ||||
|         #error "You must define an WIFI_PASSWORD for OTA-Update" | ||||
|     #endif | ||||
|     #ifndef WIFI_CLIENT_SSID | ||||
|     #ifndef WIFI_SSID_CLIENT | ||||
|         #error "You must define an WIFI_SSID for OTA-Update" | ||||
|     #endif | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										26
									
								
								Software/include/struct2json.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Software/include/struct2json.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| /** | ||||
|  * @file struct2json.h | ||||
|  * | ||||
|  * @brief Header file for converting structs to JSON objects. | ||||
|  * | ||||
|  * @note This file is auto-generated by a script on 2024-05-30 22:54:25. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date   30.05.2024 | ||||
|  */ | ||||
|  | ||||
| #ifndef _STRUCT2JSON_H_ | ||||
| #define _STRUCT2JSON_H_ | ||||
|  | ||||
| #include <Arduino.h> | ||||
| #include <ArduinoJson.h> | ||||
|  | ||||
| #include "eeprom.h" | ||||
|  | ||||
| void generateJsonObject_ConfigData(JsonObject data); | ||||
| void generateJsonObject_PersistenceData(JsonObject data); | ||||
|  | ||||
|  | ||||
| #endif /* _STRUCT2JSON_H_ */ | ||||
|  | ||||
| // CODEGENERATOR_CHECKSUM: 735cd4daf9a46bd773bdf5e6cd5a58d61b0d877196399bc2784a0d0ea7af717d | ||||
							
								
								
									
										36
									
								
								Software/include/utilities.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Software/include/utilities.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| #ifndef UTILITIES_H | ||||
| #define UTILITIES_H | ||||
|  | ||||
| #include <Arduino.h> | ||||
|  | ||||
| /** | ||||
|  * @brief Validates whether a given string contains only characters allowed in WiFi SSIDs and passwords. | ||||
|  * | ||||
|  * This function checks each character in the provided string to ensure | ||||
|  * that it contains only characters allowed in WiFi SSIDs and passwords. | ||||
|  * It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as | ||||
|  * the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ | ||||
|  * | ||||
|  * @param string Pointer to the string to be validated. | ||||
|  * @param size Size of the string including the null-terminator. | ||||
|  * @return true if the string contains only allowed characters or is NULL, | ||||
|  *         false otherwise. | ||||
|  */ | ||||
| bool validateWiFiString(char *string, size_t size); | ||||
|  | ||||
| /** | ||||
|  * @brief Copies a string to a buffer, replacing invalid WiFi SSID characters with a placeholder. | ||||
|  * | ||||
|  * This function checks each character in the provided input string to ensure | ||||
|  * that it contains only characters allowed in WiFi SSIDs and passwords. If a character | ||||
|  * is invalid, it replaces it with a placeholder character (e.g., '_'). | ||||
|  * It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as | ||||
|  * the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ | ||||
|  * | ||||
|  * @param input Pointer to the input string to be validated and copied. | ||||
|  * @param buffer Pointer to the buffer where the output string will be copied. | ||||
|  * @param bufferSize Size of the buffer including the null-terminator. | ||||
|  */ | ||||
| void sanitizeWiFiString(const char *input, char *buffer, size_t bufferSize); | ||||
|  | ||||
| #endif // UTILITIES_H | ||||
| @@ -1,3 +1,16 @@ | ||||
| /** | ||||
|  * @file webui.h | ||||
|  * | ||||
|  * @brief Header file for the web-based user interface (WebUI) in the DE-Timer application. | ||||
|  * | ||||
|  * This file contains declarations for functions related to the initialization and processing of the | ||||
|  * web-based user interface (WebUI). It includes the necessary libraries and dependencies for handling | ||||
|  * web server functionality, asynchronous JSON operations, and live debugging through WebSockets. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date   09.01.2024 | ||||
|  */ | ||||
|  | ||||
| #ifndef _WEBUI_H_ | ||||
| #define _WEBUI_H_ | ||||
|  | ||||
| @@ -11,14 +24,26 @@ | ||||
| #include <AsyncJson.h> | ||||
| #include <ArduinoJson.h> | ||||
|  | ||||
| #include "eeprom.h" | ||||
| #include "config.h" | ||||
| #include "globals.h" | ||||
| #include "dtc.h" | ||||
| #include "common.h" | ||||
| #include "debugger.h" | ||||
| #include "struct2json.h" | ||||
|  | ||||
| typedef enum | ||||
| { | ||||
|     info, | ||||
|     success, | ||||
|     warning, | ||||
|     error | ||||
| } NotificationType_t; | ||||
|  | ||||
| void initWebUI(); | ||||
| void Webserver_Process(); | ||||
| void Websocket_PushLiveDebug(String Message); | ||||
| void Webserver_Shutdown(); | ||||
|  | ||||
| #endif | ||||
| void Websocket_PushLiveDebug(String Message); | ||||
| void Websocket_PushNotification(String Message, NotificationType_t type); | ||||
|  | ||||
| #endif // _WEBUI_H_ | ||||
|   | ||||
| @@ -17,25 +17,26 @@ platform = espressif8266 | ||||
| framework = arduino | ||||
| board = d1_mini | ||||
|  | ||||
| custom_firmware_version = 1.07 | ||||
| custom_flash_version = 1.07 | ||||
|  | ||||
| upload_protocol = esptool | ||||
| upload_speed = 921600 | ||||
| ;upload_port = 10.0.1.48 | ||||
| ;upload_protocol = espota | ||||
| ;upload_flags =   | ||||
| ;   --auth=${wifi_cred.ota_password} | ||||
| ;upload_flags = | ||||
| ;  --port=8266 | ||||
| ;  --auth=${wifi_cred.admin_password} | ||||
|  | ||||
| build_flags= | ||||
|   !python git_rev_macro.py | ||||
|   -DATOMIC_FS_UPDATE | ||||
|   ;-DFEATURE_ENABLE_WIFI_CLIENT | ||||
|   ;-DFEATURE_ENABLE_LORA | ||||
|   ;-DCAPTIVE | ||||
|   -DFEATURE_ENABLE_UARTLORA | ||||
|   -DWIFI_AP_IP_GW=10,0,0,1 | ||||
|   -DADMIN_PASSWORD=${wifi_cred.ota_password} | ||||
|   -DWIFI_CLIENT_SSID=${wifi_cred.wifi_client_ssid} | ||||
|   -DWIFI_CLIENT_PASSWORD=${wifi_cred.wifi_client_password} | ||||
|   -DWIFI_AP_SSID=${wifi_cred.wifi_ap_ssid} | ||||
|   -DADMIN_PASSWORD=${wifi_cred.admin_password} | ||||
|   -DWIFI_SSID_CLIENT=${wifi_cred.wifi_ssid_client} | ||||
|   -DWIFI_PASSWORD_CLIENT=${wifi_cred.wifi_password_client} | ||||
|   -DADMIN_PASSWORD=${wifi_cred.admin_password} | ||||
|   -DWIFI_AP_PASSWORD=${wifi_cred.wifi_ap_password} | ||||
|   -DDEVICE_NAME='"Dark Emergency Timer"' | ||||
|  | ||||
| @@ -43,18 +44,22 @@ build_flags= | ||||
|  | ||||
| board_build.filesystem = littlefs | ||||
| board_build.ldscript = eagle.flash.4m1m.ld | ||||
| extra_scripts = post:prepare_littlefs.py | ||||
| extra_scripts =  | ||||
|   post:codegen/prepare_littlefs.py | ||||
|   pre:codegen/run_pre.py | ||||
|  | ||||
| monitor_filters = esp8266_exception_decoder | ||||
| monitor_speed = 115200 | ||||
|  | ||||
| lib_ldf_mode = deep | ||||
| lib_deps =  | ||||
| 	;xreef/EByte LoRa E220 library@^1.0.6   ; made Lib local, due to changes for I2C-controller M0,M1-Pins | ||||
| 	sstaub/Ticker@^4.4.0 | ||||
|   robtillaart/PCF8574 @ ^0.3.7 | ||||
| 	adafruit/Adafruit INA219 @ ^1.1.1 | ||||
|   akj7/TM1637 Driver @ ^2.1.2 | ||||
|   me-no-dev/ESPAsyncTCP @ ^1.2.2 | ||||
|   robtillaart/I2C_EEPROM @ ^1.5.2 | ||||
|   sstaub/Ticker @ ^4.4.0 | ||||
|   robtillaart/I2C_EEPROM @ ^1.8.2 | ||||
|   esphome/ESPAsyncWebServer-esphome @ ^3.2.2 | ||||
|   sandeepmistry/LoRa @ ^0.8.0 | ||||
|   bblanchon/ArduinoJson @ ^6.19.4 | ||||
|   bblanchon/ArduinoJson @ ^7.0.4 | ||||
							
								
								
									
										9
									
								
								Software/src/common.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Software/src/common.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| #include "common.h" | ||||
|  | ||||
| const char *BatteryString[] = { | ||||
|     "Undefined", | ||||
|     "LiPo 2S", | ||||
|     "LiPo 3S" | ||||
|     }; | ||||
|  | ||||
| const size_t BatteryString_Elements = sizeof(BatteryString) / sizeof(BatteryString[0]); | ||||
| @@ -1,52 +1,178 @@ | ||||
| /** | ||||
|  * @file debugger.cpp | ||||
|  * @brief Implementation of debugging functions for monitoring and diagnostics. | ||||
|  * | ||||
|  * This file contains the implementation of various debugging functions to monitor | ||||
|  * and diagnose the system. It includes functions to print system information, WiFi | ||||
|  * details, EEPROM status, dump configuration settings, dump persistence data, show | ||||
|  * Diagnostic Trouble Codes (DTCs), and more. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date 09.04.2024 | ||||
|  */ | ||||
|  | ||||
| #include "debugger.h" | ||||
|  | ||||
| DebugStatus_t DebuggerStatus[dbg_cntElements]; | ||||
|  | ||||
| String IpAddress2String(const IPAddress &ipAddress); | ||||
| void processCmdDebug(String command); | ||||
| // Funktionszeiger | ||||
| typedef void (*CommandFunction)(); | ||||
|  | ||||
| // Struktur zur Zuordnung von Commands zu Funktionen | ||||
| struct CommandMapping | ||||
| { | ||||
|     const char *command; | ||||
|     CommandFunction function; | ||||
| }; | ||||
|  | ||||
| void processCmdDebug(const char *command); | ||||
| void Debug_formatCFG(); | ||||
| void Debug_formatPersistence(); | ||||
| void Debug_printSystemInfo(); | ||||
| void Debug_printWifiInfo(); | ||||
| void Debug_CheckEEPOM(); | ||||
| void Debug_CheckEEPOM(bool autocorrect); | ||||
| void Debug_dumpConfig(); | ||||
| void Debug_dumpPersistance(); | ||||
| void Debug_ShowDTCs(); | ||||
| void Debug_dumpGlobals(); | ||||
| void Debug_printHelp(); | ||||
| void Debug_Reboot(); | ||||
| const char *uint32_to_binary_string(uint32_t num); | ||||
|  | ||||
| // Adapter-Functions for Debug-Commands | ||||
| void adapterCheckEEPOM() { Debug_CheckEEPOM(false); } | ||||
| void adapterCheckEEPOMFix() { Debug_CheckEEPOM(true); } | ||||
| void adapterDumpEEPROM1k() { dumpEEPROM(0, 1024); } | ||||
| void adapterDumpEEPROMAll() { dumpEEPROM(0, EEPROM_SIZE_BYTES); } | ||||
| void adapterKillEEPROM() { writeSequentialToEEPROM(0, 1024); } | ||||
| void adapterZeroEEPROM() { writeZeroToEEPROM(0, 1024); } | ||||
| void adapterResetPageEEPROM() { MovePersistencePage_EEPROM(true); } | ||||
| void adapterSaveEEPROM() { globals.requestEEAction = EE_ALL_SAVE; } | ||||
| void adapterSetDebugPort() { SetDebugportStatus(dbg_Serial, enabled); } | ||||
| void adapterCritDTC() { MaintainDTC(DTC_FAKE_DTC_CRIT, true, millis()); } | ||||
| void adapterWarnDTC() { MaintainDTC(DTC_FAKE_DTC_WARN, true, millis()); } | ||||
| void adapterInfoDTC() { MaintainDTC(DTC_FAKE_DTC_INFO, true, millis()); } | ||||
| void adapterNotifyError() { Websocket_PushNotification("Debug Error Notification", error); } | ||||
| void adapterNotifyWarning() { Websocket_PushNotification("Debug Warning Notification", warning); } | ||||
| void adapterNotifySuccess() { Websocket_PushNotification("Debug Success Notification", success); } | ||||
| void adapterNotifyInfo() { Websocket_PushNotification("Debug Info Notification", info); } | ||||
|  | ||||
| // Definition der Command-Mapping-Tabelle | ||||
| const CommandMapping commandMappings[] = { | ||||
|     {"help", Debug_printHelp}, | ||||
|     {"reboot", Debug_Reboot}, | ||||
|     {"sysinfo", Debug_printSystemInfo}, | ||||
|     {"netinfo", Debug_printWifiInfo}, | ||||
|     {"formatCFG", Debug_formatCFG}, | ||||
|     {"formatPDS", Debug_formatPersistence}, | ||||
|     {"checkEE", adapterCheckEEPOM}, | ||||
|     {"checkEEfix", adapterCheckEEPOMFix}, | ||||
|     {"dumpEE1k", adapterDumpEEPROM1k}, | ||||
|     {"dumpEE", adapterDumpEEPROMAll}, | ||||
|     {"killEE", adapterKillEEPROM}, | ||||
|     {"zeroEE", adapterZeroEEPROM}, | ||||
|     {"resetPageEE", adapterResetPageEEPROM}, | ||||
|     {"dumpCFG", Debug_dumpConfig}, | ||||
|     {"dumpPDS", Debug_dumpPersistance}, | ||||
|     {"saveEE", adapterSaveEEPROM}, | ||||
|     {"dumpGlobals", Debug_dumpGlobals}, | ||||
|     {"sdbg", adapterSetDebugPort}, | ||||
|     {"dtc_show", Debug_ShowDTCs}, | ||||
|     {"dtc_clear", ClearAllDTC}, | ||||
|     {"dtc_crit", adapterCritDTC}, | ||||
|     {"dtc_warn", adapterWarnDTC}, | ||||
|     {"dtc_info", adapterInfoDTC}, | ||||
|     {"notify_error", adapterNotifyError}, | ||||
|     {"notify_warning", adapterNotifyWarning}, | ||||
|     {"notify_success", adapterNotifySuccess}, | ||||
|     {"notify_info", adapterNotifyInfo}, | ||||
| }; | ||||
|  | ||||
| const size_t NUM_COMMANDS = sizeof(commandMappings) / sizeof(commandMappings[0]); | ||||
|  | ||||
| const char helpText[][64] PROGMEM = { | ||||
|     "help           - Print this help text", | ||||
|     "sysinfo        - System Info", | ||||
|     "reboot         - System Reboot", | ||||
|     "netinfo        - WiFi Info", | ||||
|     "formatPDS      - Format Persistence EEPROM Data", | ||||
|     "formatCFG      - Format Configuration EEPROM Data", | ||||
|     "checkEE        - Check EEPROM with checksum", | ||||
|     "checkEEfix     - Check and fix EEPROM with checksum", | ||||
|     "dumpEE1k       - Dump the first 1kb of EEPROM to Serial", | ||||
|     "dumpEE         - Dump the whole EEPROM to Serial", | ||||
|     "killEE         - Kill the first 1024 bytes of EEPROM", | ||||
|     "zeroEE         - Zero the first 1024 bytes of EEPROM", | ||||
|     "resetPageEE    - Reset the PersistenceData Page", | ||||
|     "dumpCFG        - Print Config struct", | ||||
|     "dumpPDS        - Print PersistenceStruct", | ||||
|     "saveEE         - Save EE-Data", | ||||
|     "dumpGlobals    - Print globals", | ||||
|     "sdbg           - Set debug port status", | ||||
|     "dtc_show       - Show all DTCs", | ||||
|     "dtc_clear      - Clear all DTCs", | ||||
|     "dtc_crit       - Maintain critical DTC", | ||||
|     "dtc_warn       - Maintain warning DTC", | ||||
|     "dtc_info       - Maintain info DTC", | ||||
|     "notify_error   - Send error notification", | ||||
|     "notify_warning - Send warning notification", | ||||
|     "notify_success - Send success notification", | ||||
|     "notify_info    - Send info notification" | ||||
| }; | ||||
|  | ||||
| const size_t NUM_HELP_LINES = sizeof(helpText) / sizeof(helpText[0]); | ||||
|  | ||||
| // Überprüfen, ob die Anzahl der Commands und Hilfetext-Zeilen übereinstimmen | ||||
| static_assert(NUM_COMMANDS == NUM_HELP_LINES, "Number of commands and help text lines do not match!"); | ||||
|  | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * @brief Initializes the debugger by setting the initial status for different debug ports. | ||||
|  *        Serial debug output is turned off. | ||||
|  */ | ||||
| void initDebugger() | ||||
| { | ||||
|     // Set the initial status of debug ports | ||||
|     DebuggerStatus[dbg_Serial] = disabled; | ||||
|     DebuggerStatus[dbg_Webui] = disabled; | ||||
|  | ||||
|     // Disable serial debug output | ||||
|     Serial.setDebugOutput(false); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Processes incoming debug commands from the Serial interface. | ||||
|  *        It reads characters from Serial and interprets them as commands. | ||||
|  *        The recognized commands are processed accordingly. | ||||
|  */ | ||||
| void Debug_Process() | ||||
| { | ||||
|     // Enumeration for tracking the state of input processing | ||||
|     typedef enum InputProcessed_e | ||||
|     { | ||||
|         IDLE, | ||||
|         CMD_COMPLETE, | ||||
|         CMD_ABORT, | ||||
|         CMD_OVERFLOW | ||||
|         IDLE,         ///< No command processing is in progress | ||||
|         CMD_COMPLETE, ///< Received a complete command | ||||
|         CMD_ABORT,    ///< Received an abort command (Esc) | ||||
|         CMD_OVERFLOW  ///< Input buffer overflow occurred | ||||
|     } InputProcessed_t; | ||||
|  | ||||
|     static unsigned int inputCnt = 0; | ||||
|     static char inputBuffer[32]; | ||||
|     InputProcessed_t InputProcessed = IDLE; | ||||
|     static unsigned int inputCnt = 0;       ///< Counter for characters in the input buffer | ||||
|     static char inputBuffer[32];            ///< Buffer to store the received characters | ||||
|     InputProcessed_t InputProcessed = IDLE; ///< State variable for input processing | ||||
|  | ||||
|     // Check if there are characters available in the Serial input buffer | ||||
|     if (Serial.available()) | ||||
|     { | ||||
|         char inputChar = Serial.read(); | ||||
|  | ||||
|         // Process the received character based on its value | ||||
|         switch (inputChar) | ||||
|         { | ||||
|         case '\n': | ||||
|             inputBuffer[inputCnt] = 0; // terminate the String | ||||
|             inputCnt = 0; | ||||
|             InputProcessed = CMD_COMPLETE; | ||||
|             Serial.write(inputChar); | ||||
|             break; | ||||
|  | ||||
|         case 0x1B: // Esc | ||||
| @@ -55,15 +181,17 @@ void Debug_Process() | ||||
|             InputProcessed = CMD_ABORT; | ||||
|             break; | ||||
|  | ||||
|         case 0x21 ... 0x7E: // its a real letter or sign and not some control-chars | ||||
|         case 0x21 ... 0x7E: // it's a real letter or sign and not some control-chars | ||||
|             inputBuffer[inputCnt] = inputChar; | ||||
|             inputCnt++; | ||||
|             Serial.write(inputChar); | ||||
|             break; | ||||
|  | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         // Check for input buffer overflow | ||||
|         if (inputCnt > sizeof(inputBuffer)) | ||||
|         { | ||||
|             inputCnt = 0; | ||||
| @@ -72,6 +200,7 @@ void Debug_Process() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Process the command based on the detected state of input processing | ||||
|     switch (InputProcessed) | ||||
|     { | ||||
|     case CMD_ABORT: | ||||
| @@ -79,44 +208,69 @@ void Debug_Process() | ||||
|         break; | ||||
|  | ||||
|     case CMD_COMPLETE: | ||||
|         processCmdDebug(String(inputBuffer)); | ||||
|         processCmdDebug(inputBuffer); | ||||
|         break; | ||||
|  | ||||
|     case CMD_OVERFLOW: | ||||
|         Debug_pushMessage("input Buffer overflow\n"); | ||||
|         Debug_pushMessage("Input buffer overflow\n"); | ||||
|         break; | ||||
|  | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     if (InputProcessed != IDLE) | ||||
|         Serial.print(">"); | ||||
|  | ||||
|     InputProcessed = IDLE; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Sets the status of a specific debug port (Serial or WebUI). | ||||
|  *        Updates the status in the DebuggerStatus array and provides debug messages. | ||||
|  * | ||||
|  * @param port   The debug port to set the status for (dbg_Serial or dbg_Webui). | ||||
|  * @param status The status to set (enabled or disabled). | ||||
|  */ | ||||
| void SetDebugportStatus(DebugPorts_t port, DebugStatus_t status) | ||||
| { | ||||
|     // Display a debug message based on the provided status | ||||
|     if (status == disabled) | ||||
|         Debug_pushMessage("disable DebugPort %s\n", sDebugPorts[port]); | ||||
|         Debug_pushMessage("Disable DebugPort %s\n", sDebugPorts[port]); | ||||
|  | ||||
|     // Update the status in the DebuggerStatus array | ||||
|     DebuggerStatus[port] = status; | ||||
|  | ||||
|     // Display a debug message based on the updated status | ||||
|     if (status == enabled) | ||||
|         Debug_pushMessage("enabled DebugPort %s\n", sDebugPorts[port]); | ||||
|         Debug_pushMessage("Enabled DebugPort %s\n", sDebugPorts[port]); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Pushes a formatted debug message to the enabled debug ports (Serial or WebUI). | ||||
|  * | ||||
|  * @param format The format string for the debug message. | ||||
|  * @param ...    Additional arguments for formatting the message. | ||||
|  */ | ||||
| void Debug_pushMessage(const char *format, ...) | ||||
| { | ||||
|     // Check if either the Serial or WebUI debug port is enabled | ||||
|     if ((DebuggerStatus[dbg_Serial] == enabled) || (DebuggerStatus[dbg_Webui] == enabled)) | ||||
|     { | ||||
|         char buff[64]; | ||||
|         va_list arg; | ||||
|         char buff[128]; // Buffer to hold the formatted message | ||||
|         va_list arg;    // Variable argument list for vsnprintf | ||||
|         va_start(arg, format); | ||||
|  | ||||
|         // Format the message and store it in the buffer | ||||
|         vsnprintf(buff, sizeof(buff), format, arg); | ||||
|         va_end(arg); | ||||
|  | ||||
|         // Send the message to the Serial debug port if enabled | ||||
|         if (DebuggerStatus[dbg_Serial] == enabled) | ||||
|         { | ||||
|             Serial.print(buff); | ||||
|         } | ||||
|  | ||||
|         // Push the message to the WebUI debug port if enabled | ||||
|         if (DebuggerStatus[dbg_Webui] == enabled) | ||||
|         { | ||||
|             Websocket_PushLiveDebug(String(buff)); | ||||
| @@ -124,54 +278,52 @@ void Debug_pushMessage(const char *format, ...) | ||||
|     } | ||||
| } | ||||
|  | ||||
| void processCmdDebug(String command) | ||||
| /** | ||||
|  * @brief Processes a debug command and performs corresponding actions. | ||||
|  * | ||||
|  * @param command The debug command to be processed. | ||||
|  */ | ||||
| void processCmdDebug(const char *command) | ||||
| { | ||||
|     if (command == "help") | ||||
|         Debug_printHelp(); | ||||
|     else if (command == "sysinfo") | ||||
|         Debug_printSystemInfo(); | ||||
|     else if (command == "netinfo") | ||||
|         Debug_printWifiInfo(); | ||||
|     else if (command == "formatCFG") | ||||
|         Debug_formatCFG(); | ||||
|     else if (command == "formatPDS") | ||||
|         Debug_formatPersistence(); | ||||
|     else if (command == "checkEE") | ||||
|         Debug_CheckEEPOM(); | ||||
|     else if (command == "dumpEE1k") | ||||
|         dumpEEPROM(0, 1024); | ||||
|     else if (command == "dumpEE") | ||||
|         dumpEEPROM(0, EEPROM_SIZE_BYTES); | ||||
|     else if (command == "resetPageEE") | ||||
|         MovePersistencePage_EEPROM(true); | ||||
|     else if (command == "dumpCFG") | ||||
|         Debug_dumpConfig(); | ||||
|     else if (command == "dumpPDS") | ||||
|         Debug_dumpPersistance(); | ||||
|     else if (command == "saveEE") | ||||
|         globals.requestEEAction = EE_ALL_SAVE; | ||||
|     else if (command == "showdtc") | ||||
|         Debug_ShowDTCs(); | ||||
|     else if (command == "dumpGlobals") | ||||
|         Debug_dumpGlobals(); | ||||
|     else if (command == "sdbg") | ||||
|         SetDebugportStatus(dbg_Serial, enabled); | ||||
|     else | ||||
|     bool commandFound = false; | ||||
|     for (size_t i = 0; i < NUM_COMMANDS; ++i) | ||||
|     { | ||||
|         if (strcmp(command, commandMappings[i].command) == 0) | ||||
|         { | ||||
|             commandMappings[i].function(); | ||||
|             commandFound = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     if (!commandFound) | ||||
|     { | ||||
|         Debug_pushMessage("unknown Command\n"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Formats the Config-EEPROM and resets it to default values. | ||||
|  *        Prints a debug message after formatting. | ||||
|  */ | ||||
| void Debug_formatCFG() | ||||
| { | ||||
|     Debug_pushMessage("Formatting Config-EEPROM and reseting to default\n"); | ||||
|     Debug_pushMessage("Formatting Config-EEPROM and resetting to default\n"); | ||||
|     FormatConfig_EEPROM(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Formats the Persistence-EEPROM and resets it to default values. | ||||
|  *        Prints a debug message after formatting. | ||||
|  */ | ||||
| void Debug_formatPersistence() | ||||
| { | ||||
|     Debug_pushMessage("Formatting Persistence-EEPROM and reseting to default\n"); | ||||
|     Debug_pushMessage("Formatting Persistence-EEPROM and resetting to default\n"); | ||||
|     FormatPersistence_EEPROM(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Prints system information and status to the debug output. | ||||
|  */ | ||||
| void Debug_printSystemInfo() | ||||
| { | ||||
|     Debug_pushMessage("Hostname: %s\n", globals.DeviceName); | ||||
| @@ -190,17 +342,44 @@ void Debug_printSystemInfo() | ||||
|                                                                         : ideMode == FM_DOUT   ? "DOUT" | ||||
|                                                                                                : "UNKNOWN")); | ||||
|     Debug_pushMessage("OTA-Pass: %s\n", QUOTE(ADMIN_PASSWORD)); | ||||
|     Debug_pushMessage("Git-Revison: %s\n", constants.GitHash); | ||||
|     Debug_pushMessage("Git-Revision: %s\n", constants.GitHash); | ||||
|     Debug_pushMessage("Sw-Version: %d.%02d\n", constants.FW_Version_major, constants.FW_Version_minor); | ||||
|  | ||||
|     Debug_pushMessage("globals.systemStatus: %d\n", globals.systemStatus); | ||||
|     Debug_pushMessage("globals.resumeStatus: %d\n", globals.resumeStatus); | ||||
|     Debug_pushMessage("globals.systemStatustxt: %s\n", globals.systemStatustxt); | ||||
|     Debug_pushMessage("globals.requestEEAction: %d\n", globals.requestEEAction); | ||||
|     Debug_pushMessage("globals.DeviceName: %s\n", globals.DeviceName); | ||||
|     Debug_pushMessage("globals.FlashVersion: %s\n", globals.FlashVersion); | ||||
|     Debug_pushMessage("globals.eePersistanceAdress: %u\n", globals.eePersistanceAdress); | ||||
|     Debug_pushMessage("globals.hasDTC: %d\n", globals.hasDTC); | ||||
|     Debug_pushMessage("globals.loadvoltage_mV: %d\n", globals.loadvoltage_mV); | ||||
|     Debug_pushMessage("globals.battery_level: %d\n", globals.battery_level); | ||||
|     Debug_pushMessage("globals.timer_disabled: %d\n", globals.timer_disabled); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Dumps the current configuration parameters to the debug output. | ||||
|  */ | ||||
| void Debug_dumpConfig() | ||||
| { | ||||
|     Debug_pushMessage("batteryType: %d\n", ConfigData.batteryType); | ||||
|     Debug_pushMessage("Faction_1_Name: %s\n", ConfigData.Faction_1_Name); | ||||
|     Debug_pushMessage("Faction_1_Name: %s\n", ConfigData.Faction_2_Name); | ||||
|     Debug_pushMessage("Faction_1_Name: %s\n", ConfigData.Faction_3_Name); | ||||
|     Debug_pushMessage("active_faction_on_reboot: %d\n", ConfigData.active_faction_on_reboot); | ||||
|     Debug_pushMessage("wifi_autoconnect: %d\n", ConfigData.wifi_autoconnect); | ||||
|     Debug_pushMessage("wifi_ap_password: %s\n", ConfigData.wifi_ap_password); | ||||
|     Debug_pushMessage("wifi_ap_ssid: %s\n", ConfigData.wifi_ap_ssid); | ||||
|     Debug_pushMessage("wifi_client_ssid: %s\n", ConfigData.wifi_client_ssid); | ||||
|     Debug_pushMessage("wifi_client_password: %s\n", ConfigData.wifi_client_password); | ||||
|     Debug_pushMessage("EEPROM_Version: %d\n", ConfigData.EEPROM_Version); | ||||
|     Debug_pushMessage("checksum: 0x%08X\n", ConfigData.checksum); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Dumps the global variables and their values to the debug output. | ||||
|  */ | ||||
| void Debug_dumpGlobals() | ||||
| { | ||||
|     Debug_pushMessage("systemStatus: %d\n", globals.systemStatus); | ||||
| @@ -208,12 +387,14 @@ void Debug_dumpGlobals() | ||||
|     Debug_pushMessage("loadvoltage_mV: %d\n", globals.loadvoltage_mV); | ||||
|     Debug_pushMessage("requestEEAction: %d\n", globals.requestEEAction); | ||||
|     Debug_pushMessage("DeviceName: %s\n", globals.DeviceName); | ||||
|     Debug_pushMessage("DeviceName_ID: %s\n", globals.DeviceName_ID); | ||||
|     Debug_pushMessage("FlashVersion: %s\n", globals.FlashVersion); | ||||
|     Debug_pushMessage("eePersistanceAdress: %d\n", globals.eePersistanceAdress); | ||||
|     Debug_pushMessage("hasDTC: %d\n", globals.hasDTC); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Dumps the persistence data variables and their values to the debug output. | ||||
|  */ | ||||
| void Debug_dumpPersistance() | ||||
| { | ||||
|     Debug_pushMessage("writeCycleCounter: %d\n", PersistenceData.writeCycleCounter); | ||||
| @@ -225,12 +406,21 @@ void Debug_dumpPersistance() | ||||
|     Debug_pushMessage("PSD Adress: 0x%04X\n", globals.eePersistanceAdress); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Prints information related to WiFi to the debug output. | ||||
|  */ | ||||
| void Debug_printWifiInfo() | ||||
| { | ||||
|     Debug_pushMessage("IP Adress: %s\n", WiFi.localIP().toString().c_str()); | ||||
| } | ||||
|  | ||||
| void Debug_CheckEEPOM() | ||||
| /** | ||||
|  * @brief Checks the EEPROM data integrity by calculating and comparing checksums. | ||||
|  *        Prints the result to the debug output. | ||||
|  */ | ||||
| void Debug_CheckEEPOM(bool autocorrect) | ||||
| { | ||||
|     // Check PersistenceData EEPROM checksum | ||||
|     uint32_t checksum = PersistenceData.checksum; | ||||
|     PersistenceData.checksum = 0; | ||||
|  | ||||
| @@ -245,6 +435,7 @@ void Debug_CheckEEPOM() | ||||
|  | ||||
|     PersistenceData.checksum = checksum; | ||||
|  | ||||
|     // Check ConfigData EEPROM checksum | ||||
|     checksum = ConfigData.checksum; | ||||
|     ConfigData.checksum = 0; | ||||
|  | ||||
| @@ -257,26 +448,45 @@ void Debug_CheckEEPOM() | ||||
|         Debug_pushMessage("ConfigData EEPROM Checksum BAD\n"); | ||||
|     } | ||||
|     ConfigData.checksum = checksum; | ||||
|  | ||||
|     uint32_t sanitycheck = ConfigSanityCheck(autocorrect); | ||||
|  | ||||
|     if (sanitycheck == 0) | ||||
|     { | ||||
|         Debug_pushMessage("ConfigData Sanity Check OK\n"); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         Debug_pushMessage("ConfigData Sanity Check BAD: %s\n", uint32_to_binary_string(sanitycheck)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Displays Diagnostic Trouble Codes (DTCs) along with their timestamps, | ||||
|  *        status, and severity in a formatted manner. | ||||
|  */ | ||||
| void Debug_ShowDTCs() | ||||
| { | ||||
|     char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx | ||||
|     char buff_active[9]; | ||||
|  | ||||
|     Debug_pushMessage("\n      timestamp | DTC-Nr. |   status | severity | debugVal\n"); | ||||
|     // Header for the DTC display | ||||
|     Debug_pushMessage("\n      timestamp | DTC-Nr. |   status | debugval\n"); | ||||
|  | ||||
|     // Iterate through DTCStorage and display each entry | ||||
|     for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) | ||||
|     { | ||||
|         if (DTCStorage[i].Number < DTC_LAST_DTC) | ||||
|         { | ||||
|             // Format timestamp | ||||
|             sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d", | ||||
|                     DTCStorage[i].timestamp / 86400000,    // Days | ||||
|                     DTCStorage[i].timestamp / 360000 % 24, // Hours | ||||
|                     DTCStorage[i].timestamp / 60000 % 60,  // Minutes | ||||
|                     DTCStorage[i].timestamp / 1000 % 60,   // Seconds | ||||
|                     DTCStorage[i].timestamp % 1000);       // milliseconds | ||||
|                     DTCStorage[i].timestamp % 1000);       // Milliseconds | ||||
|  | ||||
|             // Determine DTC status | ||||
|             if (DTCStorage[i].active == DTC_ACTIVE) | ||||
|                 strcpy(buff_active, "active"); | ||||
|             else if (DTCStorage[i].active == DTC_PREVIOUS) | ||||
| @@ -284,19 +494,63 @@ void Debug_ShowDTCs() | ||||
|             else | ||||
|                 strcpy(buff_active, "none"); | ||||
|  | ||||
|             Debug_pushMessage("%s   %7d   %8s   %8d   %8d\n", buff_timestamp, DTCStorage[i].Number, buff_active, DTCStorage[i].severity, DTCStorage[i].debugVal); | ||||
|             // Display DTC information | ||||
|             Debug_pushMessage("%s   %7d   %8s   %8d\n", buff_timestamp, DTCStorage[i].Number, buff_active, DTCStorage[i].debugVal); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Prints the help information stored in PROGMEM. | ||||
|  */ | ||||
| void Debug_printHelp() | ||||
| { | ||||
|     char buff[64]; | ||||
|  | ||||
|     for (unsigned int i = sizeof(helpCmd) / 63; i < sizeof(helpCmd) / 63; i++) | ||||
|     char buffer[64]; | ||||
|     for (size_t i = 0; i < NUM_HELP_LINES; ++i) | ||||
|     { | ||||
|         memcpy_P(buff, (helpCmd + (i * 63)), 63); | ||||
|         buff[63] = 0; | ||||
|         Debug_pushMessage(buff); | ||||
|         strcpy_P(buffer, (PGM_P)pgm_read_word(&(helpText[i]))); | ||||
|  | ||||
|         // Display the help command | ||||
|         Debug_pushMessage(buffer); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Initiates a system reboot by setting the system status to shutdown. | ||||
|  * | ||||
|  * This function sets the global system status to `sysStat_Shutdown`, | ||||
|  * which will trigger a system reboot sequence. | ||||
|  */ | ||||
| void Debug_Reboot() | ||||
| { | ||||
|     globals.systemStatus = sysStat_Shutdown; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Convert a uint32_t value to a binary string with nibbles separated by a space. | ||||
|  * | ||||
|  * This function takes a uint32_t value and converts it to a binary string | ||||
|  * representation. The binary string is stored in a static buffer and returned | ||||
|  * as a const char pointer. Each nibble (4 bits) in the binary representation | ||||
|  * is separated by a space. The buffer is overwritten on subsequent calls to | ||||
|  * this function. | ||||
|  * | ||||
|  * @param num The uint32_t value to convert. | ||||
|  * @return A pointer to a const char string containing the binary representation | ||||
|  * of the input number with nibbles separated by a space. | ||||
|  */ | ||||
| const char *uint32_to_binary_string(uint32_t num) | ||||
| { | ||||
|     static char binary_str[65]; // 32 bits + 31 spaces + null terminator | ||||
|     int i, j; | ||||
|     for (i = 31, j = 0; i >= 0; i--, j++) | ||||
|     { | ||||
|         binary_str[j] = ((num >> i) & 1) ? '1' : '0'; | ||||
|         if (i % 4 == 0 && i != 0) | ||||
|         { | ||||
|             binary_str[++j] = ' '; // Insert space after every nibble | ||||
|         } | ||||
|     } | ||||
|     binary_str[j] = '\0'; // Null terminator | ||||
|     return binary_str; | ||||
| } | ||||
| @@ -1,37 +1,61 @@ | ||||
| /** | ||||
|  * @file dtc.cpp | ||||
|  * @brief Implementation of functions related to Diagnostic Trouble Codes (DTCs). | ||||
|  * | ||||
|  * This file contains the implementation of functions that manage the status | ||||
|  * and registration of Diagnostic Trouble Codes in the system. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date 09.01.2024 | ||||
|  */ | ||||
| #include "dtc.h" | ||||
| #include "debugger.h" | ||||
|  | ||||
| DTCEntry_s DTCStorage[MAX_DTC_STORAGE]; | ||||
| DTCEntry_t DTCStorage[MAX_DTC_STORAGE]; | ||||
|  | ||||
| void MaintainDTC(DTCNums_t DTC_no, DTCSeverity_t DTC_severity, boolean active, uint32_t DebugValue) | ||||
| // Function implementations... | ||||
|  | ||||
| /** | ||||
|  * @brief Maintains the status of Diagnostic Trouble Codes (DTCs) in the DTCStorage array. | ||||
|  *        Updates the status of existing DTCs or adds new ones based on their activity. | ||||
|  * | ||||
|  * @param DTC_no      The number of the Diagnostic Trouble Code. | ||||
|  * @param active      Indicates whether the DTC is active (true) or inactive (false). | ||||
|  * @param DebugValue  Additional debugging information associated with the DTC. | ||||
|  */ | ||||
| void MaintainDTC(DTCNum_t DTC_no, boolean active, uint32_t DebugValue) | ||||
| { | ||||
|     // Iterate through the existing DTCs in the storage | ||||
|     for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||
|     { | ||||
|         // Check if the DTC with the specified number exists | ||||
|         if (DTCStorage[i].Number == DTC_no) | ||||
|         { | ||||
|             // If the DTC is active and was not active before, update its status | ||||
|             if (active && DTCStorage[i].active != DTC_ACTIVE) | ||||
|             { | ||||
|                 Debug_pushMessage("DTC gone active: %d, DebugVal: %d\n", DTC_no, DebugValue); | ||||
|                 DTCStorage[i].timestamp = millis(); | ||||
|                 DTCStorage[i].active = DTC_ACTIVE; | ||||
|                 DTCStorage[i].severity = DTC_severity; | ||||
|                 DTCStorage[i].debugVal = DebugValue; | ||||
|             } | ||||
|             // If the DTC is not active anymore, update its status to previous | ||||
|             if (!active && DTCStorage[i].active == DTC_ACTIVE) | ||||
|             { | ||||
|                 Debug_pushMessage("DTC gone previous: %d\n", DTC_no); | ||||
|                 DTCStorage[i].active = DTC_PREVIOUS; | ||||
|             } | ||||
|             return; | ||||
|             return; // DTC found and processed, exit the function | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // DTC was not found with upper iteration, but is active | ||||
|     // so we need to look for free space to store DTC | ||||
|     // DTC was not found in the existing storage, but it is active, | ||||
|     // so look for free space to store the new DTC | ||||
|     if (active == true) | ||||
|     { | ||||
|         for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||
|         { | ||||
|             // Check for an empty slot in the storage | ||||
|             if (DTCStorage[i].Number == DTC_LAST_DTC) | ||||
|             { | ||||
|                 Debug_pushMessage("new DTC registered: %d, DebugVal: %d\n", DTC_no, DebugValue); | ||||
| @@ -39,37 +63,59 @@ void MaintainDTC(DTCNums_t DTC_no, DTCSeverity_t DTC_severity, boolean active, u | ||||
|                 DTCStorage[i].timestamp = millis(); | ||||
|                 DTCStorage[i].active = DTC_ACTIVE; | ||||
|                 DTCStorage[i].debugVal = DebugValue; | ||||
|                 DTCStorage[i].severity = DTC_severity; | ||||
|                 return; | ||||
|                 return; // New DTC registered, exit the function | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ClearDTC(DTCNums_t DTC_no) | ||||
| /** | ||||
|  * @brief Clears a specific Diagnostic Trouble Code (DTC) entry. | ||||
|  * | ||||
|  * This function clears the information related to a specific DTC entry, | ||||
|  * setting its status to inactive and timestamp to zero. | ||||
|  * | ||||
|  * @param DTC_no The Diagnostic Trouble Code number to be cleared. | ||||
|  */ | ||||
| void ClearDTC(DTCNum_t DTC_no) | ||||
| { | ||||
|     for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||
|     { | ||||
|         if (DTCStorage[i].Number == DTC_no) | ||||
|         { | ||||
|             DTCStorage[i].Number = DTC_LAST_DTC; | ||||
|             DTCStorage[i].active = DTC_NONE; | ||||
|             DTCStorage[i].active = DTC_INACTIVE; | ||||
|             DTCStorage[i].timestamp = 0; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Clears all Diagnostic Trouble Code (DTC) entries. | ||||
|  * | ||||
|  * This function clears all DTC entries, setting their status to inactive and | ||||
|  * timestamps to zero. | ||||
|  */ | ||||
| void ClearAllDTC() | ||||
| { | ||||
|     for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||
|     { | ||||
|         DTCStorage[i].Number = DTC_LAST_DTC; | ||||
|         DTCStorage[i].active = DTC_NONE; | ||||
|         DTCStorage[i].active = DTC_INACTIVE; | ||||
|         DTCStorage[i].timestamp = 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| DTCNums_t getlastDTC(boolean only_active) | ||||
| /** | ||||
|  * @brief Gets the last recorded Diagnostic Trouble Code (DTC) number. | ||||
|  * | ||||
|  * This function retrieves the DTC number of the last recorded DTC based on the | ||||
|  * timestamp. Optionally, it can filter only active DTCs. | ||||
|  * | ||||
|  * @param only_active If true, considers only active DTCs; otherwise, considers all. | ||||
|  * @return The DTC number of the last recorded DTC or DTC_LAST_DTC if none found. | ||||
|  */ | ||||
| DTCNum_t getlastDTC(boolean only_active) | ||||
| { | ||||
|     int8_t pointer = -1; | ||||
|     uint32_t lasttimestamp = 0; | ||||
| @@ -89,34 +135,47 @@ DTCNums_t getlastDTC(boolean only_active) | ||||
|     return pointer >= 0 ? DTCStorage[pointer].Number : DTC_LAST_DTC; | ||||
| } | ||||
|  | ||||
| DTCNums_t getlastDTC_Severity(boolean only_active, DTCSeverity_t severity) | ||||
| /** | ||||
|  * @brief Gets the severity level for a specific Diagnostic Trouble Code (DTC). | ||||
|  * | ||||
|  * This function looks up the severity level associated with the provided DTC code | ||||
|  * from the predefined list of DTC definitions. | ||||
|  * | ||||
|  * @param targetCode The DTC code for which to retrieve the severity. | ||||
|  * @return The severity level of the specified DTC or DTC_NONE if not found. | ||||
|  */ | ||||
| DTCSeverity_t getSeverityForDTC(DTCNum_t targetCode) | ||||
| { | ||||
|     int8_t pointer = -1; | ||||
|     uint32_t lasttimestamp = 0; | ||||
|  | ||||
|     for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||
|     for (int i = 0; i < DTC_LAST_DTC; i++) | ||||
|     { | ||||
|         if (DTCStorage[i].Number > 0 && DTCStorage[i].timestamp > lasttimestamp) | ||||
|         if (dtc_definitions[i].code == targetCode) | ||||
|         { | ||||
|             if ((only_active == false || DTCStorage[i].active == DTC_ACTIVE) && DTCStorage[i].severity == severity) | ||||
|             { | ||||
|                 pointer = i; | ||||
|                 lasttimestamp = DTCStorage[i].timestamp; | ||||
|             } | ||||
|             return dtc_definitions[i].severity; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return pointer >= 0 ? DTCStorage[pointer].Number : DTC_LAST_DTC; | ||||
|     return DTC_NONE; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Processes Diagnostic Trouble Codes (DTCs) and updates system status accordingly. | ||||
|  * | ||||
|  * This function checks for the presence of active DTCs and adjusts the system status | ||||
|  * based on the severity of the most critical DTC. If a critical DTC is detected, | ||||
|  * the system status is set to sysStat_Error, potentially triggering a system shutdown. | ||||
|  * | ||||
|  * @note The function also preserves the original system status when transitioning to an error state | ||||
|  *       and restores it when all DTCs are cleared. | ||||
|  */ | ||||
| void DTC_Process() | ||||
| { | ||||
|     static tSystem_Status preserverSysStatusError; | ||||
|     DTCNum_t lastDTC = getlastDTC(true); | ||||
|  | ||||
|     if (getlastDTC(false) < DTC_LAST_DTC) | ||||
|     if (lastDTC < DTC_LAST_DTC) | ||||
|     { | ||||
|         globals.hasDTC = true; | ||||
|         if (getlastDTC_Severity(true, DTC_CRITICAL) < DTC_LAST_DTC && globals.systemStatus != sysStat_Shutdown) | ||||
|  | ||||
|         if (getSeverityForDTC(lastDTC) == DTC_CRITICAL && globals.systemStatus != sysStat_Shutdown) | ||||
|         { | ||||
|             if (globals.systemStatus != sysStat_Error) | ||||
|             { | ||||
| @@ -124,16 +183,14 @@ void DTC_Process() | ||||
|             } | ||||
|             globals.systemStatus = sysStat_Error; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if (globals.systemStatus == sysStat_Error) | ||||
|             { | ||||
|                 globals.systemStatus = preserverSysStatusError; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         globals.hasDTC = false; | ||||
|  | ||||
|         if (globals.systemStatus == sysStat_Error) | ||||
|         { | ||||
|             globals.systemStatus = preserverSysStatusError; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
							
								
								
									
										18
									
								
								Software/src/dtc_defs.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Software/src/dtc_defs.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # No. |                 DTC-Constant |      Severity |                 Title | Description      | ||||
| #-----|------------------------------|---------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------- | ||||
|     1;              DTC_BAT_CRITICAL;   DTC_CRITICAL;              Akku leer;  Akku ist komplett leer. Den Akku aufladen! | ||||
|     2;                   DTC_BAT_LOW;       DTC_WARN;           Akku niedrig;  Akku ist unter der Warnschwelle. Den Akku demnächst aufladen | ||||
|     3;           DTC_NO_EEPROM_FOUND;   DTC_CRITICAL;    kein EEPROM erkannt;  Es wurde kein EEPROM gefunden. Dies lässt einen Hardware-Defekt vermuten. | ||||
|     4;            DTC_EEPROM_CFG_BAD;   DTC_CRITICAL;  EEPROM CFG Checksumme;  Die Checksumme der Config-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück | ||||
|     5;            DTC_EEPROM_PDS_BAD;   DTC_CRITICAL;  EEPROM PDS Checksumme;  Die Checksumme der Betriebsdaten-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück | ||||
|     6;      DTC_EEPROM_PDSADRESS_BAD;   DTC_CRITICAL;     EEPROM PDS Adresse;  Die Adresse der Betriebsdaten-Partition im EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück | ||||
|     7;        DTC_EEPROM_VERSION_BAD;   DTC_CRITICAL;  EEPROM Version falsch;  Die Layout-Version des EEPROM stimmt nicht mit der Firmware-Version überein. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück | ||||
|     8;             DTC_FLASHFS_ERROR;   DTC_CRITICAL;   Flashspeicher Fehler;  Der Flashspeicher konnte nicht initialisiert werden. Aktualisieren sie Flash & Firmware | ||||
|     9;     DTC_FLASHFS_VERSION_ERROR;   DTC_CRITICAL;    Flashversion falsch;  Die Version des Flashspeicher stimmt nicht mit der Firmware-Version überein. Aktualisieren sie den Flash mit der passenden Update-Datei | ||||
|    10;          DTC_NO_BATMNON_FOUND;   DTC_CRITICAL;  Keine Akkuüberwachung;  Es wurde keine Akkuüberwachung über I2C gefunden, Prüfen sie die Hardware! | ||||
|    11;             DTC_NO_LORA_FOUND;   DTC_CRITICAL; LoRa-Transceiver Error;  Es konnte keine Verbindung zum LoRa-Transceiver hergestellt werden. Prüfen Sie die Hardware auf Defekte | ||||
|    12;         DTC_EEPROM_CFG_SANITY;       DTC_WARN;     Config-Validierung;  Ein oder mehrer Einstellungswerte sind ausserhalb plausibler Werte. Prüfen Sie Ihre Einstellungen | ||||
|    13;     DTC_EEPROM_MIGRATE_FAILED;   DTC_CRITICAL;       EEPROM-Migration;  Es wurde ein altes EEPROm Image erkannt, konnte aber nicht migriert werden. EEPROM manuell zurück setzen und neue Einstellunge speichern. | ||||
|    14;             DTC_FAKE_DTC_INFO;       DTC_INFO;         Dummy-DTC Info;  Ein Dummy-DTC der Schwere "Info" für Debugging-Zwecke | ||||
|    15;             DTC_FAKE_DTC_WARN;       DTC_WARN;      Dummy-DTC Warnung;  Ein Dummy-DTC der Schwere "Warnung" für Debugging-Zwecke | ||||
|    16;             DTC_FAKE_DTC_CRIT;   DTC_CRITICAL;     Dummy-DTC Kritisch;  Ein Dummy-DTC der Schwere "Kritisch" für Debugging-Zwecke | ||||
| @@ -1,72 +1,105 @@ | ||||
| #include "eeprom.h" | ||||
| /** | ||||
|  * @file config.cpp | ||||
|  * @brief Implementation of EEPROM and configuration-related functions. | ||||
|  * | ||||
|  * This file contains functions for managing EEPROM storage and handling configuration data. | ||||
|  * It includes the definitions of configuration structures, EEPROM access, and utility functions. | ||||
|  */ | ||||
|  | ||||
| #include "eeprom.h" | ||||
| #include "debugger.h" | ||||
| #include "globals.h" | ||||
| #include "utilities.h" | ||||
|  | ||||
| // Instance of I2C_eeprom for EEPROM access | ||||
| I2C_eeprom ee(I2C_EE_ADDRESS, EEPROM_SIZE_BYTES); | ||||
|  | ||||
| // Configuration and persistence data structures | ||||
| configData_t ConfigData; | ||||
| persistenceData_t PersistenceData; | ||||
| bool eeAvailable = false; | ||||
|  | ||||
| bool checkEEPROMavailable(); | ||||
| bool ValidateEEPROM_Version(); | ||||
| bool MigrateEEPROM(uint8_t fromVersion); | ||||
| // EEPROM version identifier | ||||
| const uint16_t eeVersion = EEPROM_STRUCTURE_REVISION; | ||||
|  | ||||
| void InitEEPROM() | ||||
| // Flag indicating whether EEPROM is available | ||||
| boolean eeAvailable = false; | ||||
|  | ||||
| // Offsets within EEPROM for ConfigData and PersistenceData | ||||
| const uint16_t startofConfigData = 16; | ||||
| const uint16_t startofPersistence = 16 + sizeof(ConfigData) + (sizeof(ConfigData) % 16); | ||||
|  | ||||
| // Function prototype to check EEPROM availability | ||||
| boolean checkEEPROMavailable(); | ||||
|  | ||||
| /** | ||||
|  * @brief Initializes EEPROM and checks its availability. | ||||
|  * | ||||
|  * This function initializes the EEPROM using the I2C_eeprom instance and checks if it's available. | ||||
|  */ | ||||
| boolean InitEEPROM() | ||||
| { | ||||
|   Wire.begin(); | ||||
|   ConfigData = ConfigData_defaults; | ||||
|   PersistenceData = {0}; | ||||
|   ee.begin(); | ||||
|   eeAvailable = checkEEPROMavailable(); | ||||
|   eeAvailable = ValidateEEPROM_Version(); | ||||
| 	Serial.printf("Initialized EEPROM at Address 0x%02X\n", I2C_EE_ADDRESS); | ||||
|   return checkEEPROMavailable(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Processes EEPROM actions based on the request from the global state. | ||||
|  * | ||||
|  * This function processes EEPROM actions based on the request from the global state. | ||||
|  * It performs actions such as saving, loading, and formatting EEPROM data for both configuration and persistence. | ||||
|  */ | ||||
| void EEPROM_Process() | ||||
| { | ||||
|   if (eeAvailable == false) | ||||
|     return; | ||||
|  | ||||
|   switch (globals.requestEEAction) | ||||
|   { | ||||
|   case EE_CFG_SAVE: | ||||
|     StoreConfig_EEPROM(); | ||||
|     globals.requestEEAction = EE_IDLE; | ||||
|     Serial.println("Stored EEPROM CFG"); | ||||
|     Debug_pushMessage("Stored EEPROM CFG\n"); | ||||
|     break; | ||||
|   case EE_CFG_LOAD: | ||||
|     GetConfig_EEPROM(); | ||||
|     globals.requestEEAction = EE_IDLE; | ||||
|     Serial.println("Loaded EEPROM CFG"); | ||||
|     Debug_pushMessage("Loaded EEPROM CFG\n"); | ||||
|     break; | ||||
|   case EE_CFG_FORMAT: | ||||
|     FormatConfig_EEPROM(); | ||||
|     globals.requestEEAction = EE_IDLE; | ||||
|     globals.systemStatus = sysStat_Shutdown; | ||||
|     Serial.println("Formated EEPROM CFG"); | ||||
|     GetConfig_EEPROM(); | ||||
|     Debug_pushMessage("Formatted EEPROM CFG\n"); | ||||
|     break; | ||||
|   case EE_PDS_SAVE: | ||||
|     StorePersistence_EEPROM(); | ||||
|     globals.requestEEAction = EE_IDLE; | ||||
|     Serial.println("Stored EEPROM PDS"); | ||||
|     Debug_pushMessage("Stored EEPROM PDS\n"); | ||||
|     break; | ||||
|   case EE_PDS_LOAD: | ||||
|     GetPersistence_EEPROM(); | ||||
|     globals.requestEEAction = EE_IDLE; | ||||
|     Serial.println("Loaded EEPROM PDS"); | ||||
|     Debug_pushMessage("Loaded EEPROM PDS\n"); | ||||
|     break; | ||||
|   case EE_PDS_FORMAT: | ||||
|     FormatPersistence_EEPROM(); | ||||
|     globals.requestEEAction = EE_IDLE; | ||||
|     Serial.println("Formated EEPROM PDS"); | ||||
|     GetPersistence_EEPROM(); | ||||
|     Debug_pushMessage("Formatted EEPROM PDS\n"); | ||||
|     break; | ||||
|   case EE_FORMAT_ALL: | ||||
|     FormatConfig_EEPROM(); | ||||
|     FormatPersistence_EEPROM(); | ||||
|     GetConfig_EEPROM(); | ||||
|     GetPersistence_EEPROM(); | ||||
|     globals.requestEEAction = EE_IDLE; | ||||
|     Serial.println("Formated EEPROM ALL"); | ||||
|     Debug_pushMessage("Formatted EEPROM ALL\n"); | ||||
|     break; | ||||
|   case EE_ALL_SAVE: | ||||
|     StorePersistence_EEPROM(); | ||||
|     StoreConfig_EEPROM(); | ||||
|     globals.requestEEAction = EE_IDLE; | ||||
|     Serial.println("Stored EEPROM ALL"); | ||||
|     Debug_pushMessage("Stored EEPROM ALL\n"); | ||||
|     break; | ||||
|   case EE_IDLE: | ||||
|   default: | ||||
| @@ -74,20 +107,46 @@ void EEPROM_Process() | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Stores the configuration data in EEPROM. | ||||
|  * | ||||
|  * This function calculates the checksum for the configuration data, updates it, and stores it in EEPROM. | ||||
|  * It also performs a sanity check on the configuration and raises a diagnostic trouble code (DTC) if needed. | ||||
|  */ | ||||
| void StoreConfig_EEPROM() | ||||
| { | ||||
|   if (eeAvailable == false) | ||||
|     return; | ||||
|  | ||||
|   // Berechnung der Prüfsumme | ||||
|   ConfigData.checksum = 0; | ||||
|   ConfigData.checksum = Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)); | ||||
|  | ||||
|   ee.updateBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); | ||||
|   // Überprüfung, ob der EEPROM verfügbar ist | ||||
|   if (!checkEEPROMavailable()) | ||||
|     return; | ||||
|  | ||||
|   // Byteweise in den EEPROM schreiben | ||||
|   uint8_t *dataPtr = (uint8_t *)&ConfigData; | ||||
|   for (uint16_t i = 0; i < sizeof(ConfigData); i++) | ||||
|   { | ||||
|     ee.writeByte(startofConfigData + i, dataPtr[i]); | ||||
|   } | ||||
|  | ||||
|   // Sanity Check der Konfiguration | ||||
|   uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); | ||||
|   if (ConfigSanityCheckResult > 0) | ||||
|   { | ||||
|     MaintainDTC(DTC_EEPROM_CFG_SANITY, true, ConfigSanityCheckResult); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Retrieves the configuration data from EEPROM. | ||||
|  * | ||||
|  * This function reads the configuration data from EEPROM, performs a checksum validation, | ||||
|  * and conducts a sanity check on the configuration. It raises a diagnostic trouble code (DTC) if needed. | ||||
|  */ | ||||
| void GetConfig_EEPROM() | ||||
| { | ||||
|   if (eeAvailable == false) | ||||
|   if (!checkEEPROMavailable()) | ||||
|     return; | ||||
|  | ||||
|   ee.readBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); | ||||
| @@ -95,26 +154,23 @@ void GetConfig_EEPROM() | ||||
|   uint32_t checksum = ConfigData.checksum; | ||||
|   ConfigData.checksum = 0; | ||||
|  | ||||
|   if (Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)) != checksum) | ||||
|   { | ||||
|     MaintainDTC(DTC_EEPROM_CFG_BAD, DTC_CRITICAL, true); | ||||
|   } | ||||
|   MaintainDTC(DTC_EEPROM_CFG_BAD, (Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)) != checksum)); | ||||
|  | ||||
|   ConfigData.checksum = checksum; | ||||
|  | ||||
|   uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); | ||||
|   uint32_t ConfigSanityCheckResult = ConfigSanityCheck(true); | ||||
|  | ||||
|   if (ConfigSanityCheckResult > 0) | ||||
|   { | ||||
|     MaintainDTC(DTC_EEPROM_CFG_SANITY, DTC_WARN, true, ConfigSanityCheckResult); | ||||
|     globals.requestEEAction = EE_CFG_SAVE; | ||||
|   } | ||||
|   MaintainDTC(DTC_EEPROM_CFG_SANITY, (ConfigSanityCheckResult > 0), ConfigSanityCheckResult); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Stores the persistence data in EEPROM. | ||||
|  * | ||||
|  * This function increments the write cycle counter, performs a checksum calculation on the persistence data, | ||||
|  * and stores it in EEPROM. It also handles EEPROM page movement when needed. | ||||
|  */ | ||||
| void StorePersistence_EEPROM() | ||||
| { | ||||
|   if (eeAvailable == false) | ||||
|     return; | ||||
|  | ||||
|   if (PersistenceData.writeCycleCounter >= 0xFFF0) | ||||
|     MovePersistencePage_EEPROM(false); | ||||
|   else | ||||
| @@ -123,12 +179,27 @@ void StorePersistence_EEPROM() | ||||
|   PersistenceData.checksum = 0; | ||||
|   PersistenceData.checksum = Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)); | ||||
|  | ||||
|   ee.updateBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData)); | ||||
|   if (!checkEEPROMavailable()) | ||||
|     return; | ||||
|  | ||||
|   // Byteweise in den EEPROM schreiben | ||||
|   uint8_t *dataPtr = (uint8_t *)&PersistenceData; | ||||
|   for (uint16_t i = 0; i < sizeof(PersistenceData); i++) | ||||
|   { | ||||
|     ee.writeByte(globals.eePersistanceAdress + i, dataPtr[i]); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Retrieves the persistence data from EEPROM. | ||||
|  * | ||||
|  * This function reads the EEPROM to get the start address of the persistence data. | ||||
|  * If the start address is out of range, it resets and stores defaults. Otherwise, | ||||
|  * it reads from EEPROM and checks if the data is correct. | ||||
|  */ | ||||
| void GetPersistence_EEPROM() | ||||
| { | ||||
|   if (eeAvailable == false) | ||||
|   if (!checkEEPROMavailable()) | ||||
|     return; | ||||
|  | ||||
|   ee.readBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); | ||||
| @@ -138,7 +209,7 @@ void GetPersistence_EEPROM() | ||||
|   { | ||||
|     MovePersistencePage_EEPROM(true); | ||||
|     FormatPersistence_EEPROM(); | ||||
|     MaintainDTC(DTC_EEPROM_PDSADRESS_BAD, DTC_CRITICAL, true); | ||||
|     MaintainDTC(DTC_EEPROM_PDSADRESS_BAD, true); | ||||
|   } | ||||
|   else | ||||
|   { | ||||
| @@ -147,45 +218,53 @@ void GetPersistence_EEPROM() | ||||
|     uint32_t checksum = PersistenceData.checksum; | ||||
|     PersistenceData.checksum = 0; | ||||
|  | ||||
|     if (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum) | ||||
|     { | ||||
|       MaintainDTC(DTC_EEPROM_PDS_BAD, DTC_CRITICAL, true); | ||||
|     } | ||||
|     MaintainDTC(DTC_EEPROM_PDS_BAD, (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum)); | ||||
|  | ||||
|     PersistenceData.checksum = checksum; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Formats the configuration partition in EEPROM. | ||||
|  * | ||||
|  * This function resets the configuration data to defaults and stores it in EEPROM. | ||||
|  */ | ||||
| void FormatConfig_EEPROM() | ||||
| { | ||||
|   if (eeAvailable == false) | ||||
|     return; | ||||
|  | ||||
|   Serial.println("Formatting Config-Partition"); | ||||
|   Debug_pushMessage("Formatting Config-Partition\n"); | ||||
|   ConfigData = ConfigData_defaults; | ||||
|   ConfigData.EEPROM_Version = eeVersion; | ||||
|   StoreConfig_EEPROM(); | ||||
|   GetConfig_EEPROM(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Formats the persistence partition in EEPROM. | ||||
|  * | ||||
|  * This function resets the persistence data to defaults and stores it in EEPROM. | ||||
|  */ | ||||
| void FormatPersistence_EEPROM() | ||||
| { | ||||
|   if (eeAvailable == false) | ||||
|     return; | ||||
|  | ||||
|   Serial.println("Formatting Persistance-Partition"); | ||||
|   Debug_pushMessage("Formatting Persistance-Partition\n"); | ||||
|   PersistenceData = {0}; | ||||
|   // memset(&PersistenceData, 0, sizeof(PersistenceData)); | ||||
|   StorePersistence_EEPROM(); | ||||
|   GetPersistence_EEPROM(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Moves the persistence page in EEPROM. | ||||
|  * | ||||
|  * This function adjusts the persistence page address and resets the write cycle counter. | ||||
|  * | ||||
|  * @param reset If true, the function resets the persistence page address to the start of the partition. | ||||
|  */ | ||||
| void MovePersistencePage_EEPROM(boolean reset) | ||||
| { | ||||
|   if (eeAvailable == false) | ||||
|   if (!checkEEPROMavailable()) | ||||
|     return; | ||||
|  | ||||
|   globals.eePersistanceAdress = +sizeof(PersistenceData); | ||||
|   globals.eePersistanceAdress += sizeof(PersistenceData); | ||||
|   PersistenceData.writeCycleCounter = 0; | ||||
|  | ||||
|   // check if we reached the End of the EEPROM and Startover at the beginning | ||||
|   // Check if we reached the end of the EEPROM and start over at the beginning | ||||
|   if ((globals.eePersistanceAdress + sizeof(PersistenceData)) > ee.getDeviceSize() || reset) | ||||
|   { | ||||
|     globals.eePersistanceAdress = startofPersistence; | ||||
| @@ -194,165 +273,202 @@ void MovePersistencePage_EEPROM(boolean reset) | ||||
|   ee.updateBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Calculate CRC-32 checksum for a block of data. | ||||
|  * | ||||
|  * This function implements the CRC-32 algorithm. | ||||
|  * | ||||
|  * @param data Pointer to the data block. | ||||
|  * @param len Length of the data block in bytes. | ||||
|  * @return CRC-32 checksum. | ||||
|  */ | ||||
| uint32_t Checksum_EEPROM(uint8_t const *data, size_t len) | ||||
| { | ||||
|   if (data == NULL) | ||||
|     return 0; | ||||
|   uint32_t crc, mask; | ||||
|   crc = 0xFFFFFFFF; | ||||
|  | ||||
|   uint32_t crc = 0xFFFFFFFF; | ||||
|   uint32_t mask; | ||||
|  | ||||
|   while (len--) | ||||
|   { | ||||
|     crc ^= *data++; | ||||
|  | ||||
|     for (uint8_t k = 0; k < 8; k++) | ||||
|     { | ||||
|       mask = -(crc & 1); | ||||
|       crc = (crc >> 1) ^ (0xEDB88320 & mask); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ~crc; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Dump a portion of EEPROM contents for debugging. | ||||
|  * | ||||
|  * This function prints the contents of a specified portion of EEPROM in a formatted way. | ||||
|  * | ||||
|  * @param memoryAddress Starting address in EEPROM. | ||||
|  * @param length Number of bytes to dump. | ||||
|  */ | ||||
| void dumpEEPROM(uint16_t memoryAddress, uint16_t length) | ||||
| { | ||||
| #define BLOCK_TO_LENGTH 16 | ||||
|  | ||||
|   if (eeAvailable == false) | ||||
|   if (!checkEEPROMavailable()) | ||||
|     return; | ||||
|  | ||||
|   char ascii_buf[BLOCK_TO_LENGTH + 1]; | ||||
|   char ascii_buf[BLOCK_TO_LENGTH + 1] = {0}; | ||||
|   sprintf(ascii_buf, "%*s", BLOCK_TO_LENGTH, "ASCII"); | ||||
|   Serial.print(PSTR("\nAddress ")); | ||||
|   for (int x = 0; x < BLOCK_TO_LENGTH; x++) | ||||
|     Serial.printf("%3d", x); | ||||
|  | ||||
|   // Print column headers | ||||
|   Debug_pushMessage(PSTR("\nAddress ")); | ||||
|   for (int x = 0; x < BLOCK_TO_LENGTH; x++) | ||||
|     Debug_pushMessage("%3d", x); | ||||
|  | ||||
|   // Align address and length to BLOCK_TO_LENGTH boundaries | ||||
|   memoryAddress = memoryAddress / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH; | ||||
|   length = (length + BLOCK_TO_LENGTH - 1) / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH; | ||||
|  | ||||
|   // Iterate through the specified portion of EEPROM | ||||
|   for (unsigned int i = 0; i < length; i++) | ||||
|   { | ||||
|     int blockpoint = memoryAddress % BLOCK_TO_LENGTH; | ||||
|  | ||||
|     // Print ASCII representation header for each block | ||||
|     if (blockpoint == 0) | ||||
|     { | ||||
|       ascii_buf[BLOCK_TO_LENGTH] = 0; | ||||
|       Serial.printf("  %s", ascii_buf); | ||||
|       Serial.printf("\n0x%05X:", memoryAddress); | ||||
|       if (i > 0) // Ensure we don't print an empty ASCII buffer on the first iteration | ||||
|       { | ||||
|         ascii_buf[BLOCK_TO_LENGTH] = 0; | ||||
|         Debug_pushMessage("  %s", ascii_buf); | ||||
|       } | ||||
|       Debug_pushMessage("\n0x%05X:", memoryAddress); | ||||
|       memset(ascii_buf, ' ', BLOCK_TO_LENGTH); // Clear the ASCII buffer with spaces | ||||
|     } | ||||
|     ascii_buf[blockpoint] = ee.readByte(memoryAddress); | ||||
|     Serial.printf(" %02X", ascii_buf[blockpoint]); | ||||
|     if (ascii_buf[blockpoint] < 0x20 || ascii_buf[blockpoint] > 0x7E) | ||||
|       ascii_buf[blockpoint] = '.'; | ||||
|  | ||||
|     // Read and print each byte | ||||
|     uint8_t byte = ee.readByte(memoryAddress); | ||||
|     ascii_buf[blockpoint] = (byte >= 0x20 && byte <= 0x7E) ? byte : '.'; | ||||
|     Debug_pushMessage(" %02X", byte); | ||||
|  | ||||
|     memoryAddress++; | ||||
|   } | ||||
|   Serial.println(); | ||||
|  | ||||
|   // Print remaining ASCII buffer | ||||
|   ascii_buf[BLOCK_TO_LENGTH] = 0; | ||||
|   Debug_pushMessage("  %s\n", ascii_buf); // Final ASCII line | ||||
| } | ||||
|  | ||||
| bool checkEEPROMavailable() | ||||
| /** | ||||
|  * @brief Check if EEPROM is available and connected. | ||||
|  * | ||||
|  * This function checks if the EEPROM is available and connected. If not, it triggers | ||||
|  * a diagnostic trouble code (DTC) indicating the absence of EEPROM. | ||||
|  * | ||||
|  * @return true if EEPROM is available, false otherwise. | ||||
|  */ | ||||
| boolean checkEEPROMavailable() | ||||
| { | ||||
|   // Check if EEPROM is connected | ||||
|   if (!ee.isConnected()) | ||||
|   { | ||||
|     MaintainDTC(DTC_NO_EEPROM_FOUND, DTC_CRITICAL, true); | ||||
|     // Trigger DTC for no EEPROM found | ||||
|     MaintainDTC(DTC_NO_EEPROM_FOUND, true); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Clear DTC for no EEPROM found since it's available now | ||||
|   MaintainDTC(DTC_NO_EEPROM_FOUND, false); | ||||
|  | ||||
|   // EEPROM is available | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Perform sanity check on configuration settings. | ||||
|  * | ||||
|  * This function checks the validity of various configuration settings and returns a bitmask | ||||
|  * indicating which settings need to be reset. If autocorrect is enabled, it resets the settings | ||||
|  * to their default values. | ||||
|  * | ||||
|  * @param autocorrect If true, automatically correct invalid settings by resetting to defaults. | ||||
|  * @return A bitmask indicating which settings need to be reset. | ||||
|  */ | ||||
| uint32_t ConfigSanityCheck(bool autocorrect) | ||||
| { | ||||
|   uint32_t setting_reset_bits = 0; | ||||
|  | ||||
|   if ((ConfigData.batteryType != BATTERY_LIPO_2S) || (ConfigData.batteryType != BATTERY_LIPO_3S)) | ||||
|   if (!validateWiFiString(ConfigData.wifi_ap_ssid, sizeof(ConfigData.wifi_ap_ssid))) | ||||
|   { | ||||
|     setting_reset_bits = setting_reset_bits | (1 << 0); | ||||
|     SET_BIT(setting_reset_bits, 1); | ||||
|     if (autocorrect) | ||||
|       ConfigData.batteryType = ConfigData_defaults.batteryType; | ||||
|       strncpy(ConfigData.wifi_ap_ssid, ConfigData_defaults.wifi_ap_ssid, sizeof(ConfigData.wifi_ap_ssid)); | ||||
|   } | ||||
|  | ||||
|   if (!validateWiFiString(ConfigData.wifi_ap_password, sizeof(ConfigData.wifi_ap_password))) | ||||
|   { | ||||
|     SET_BIT(setting_reset_bits, 2); | ||||
|     if (autocorrect) | ||||
|       strncpy(ConfigData.wifi_ap_password, ConfigData_defaults.wifi_ap_password, sizeof(ConfigData.wifi_ap_password)); | ||||
|   } | ||||
|  | ||||
|   if (!validateWiFiString(ConfigData.wifi_client_ssid, sizeof(ConfigData.wifi_client_ssid))) | ||||
|   { | ||||
|     SET_BIT(setting_reset_bits, 3); | ||||
|     if (autocorrect) | ||||
|       strncpy(ConfigData.wifi_client_ssid, ConfigData_defaults.wifi_client_ssid, sizeof(ConfigData.wifi_client_ssid)); | ||||
|   } | ||||
|  | ||||
|   if (!validateWiFiString(ConfigData.wifi_client_password, sizeof(ConfigData.wifi_client_password))) | ||||
|   { | ||||
|     SET_BIT(setting_reset_bits, 4); | ||||
|     if (autocorrect) | ||||
|       strncpy(ConfigData.wifi_client_password, ConfigData_defaults.wifi_client_password, sizeof(ConfigData.wifi_client_password)); | ||||
|   } | ||||
|   // Return the bitmask indicating which settings need to be reset | ||||
|   return setting_reset_bits; | ||||
| } | ||||
|  | ||||
| bool ValidateEEPROM_Version() | ||||
|  | ||||
| /** | ||||
|  * @brief Write sequential numbers to a portion of EEPROM. | ||||
|  * | ||||
|  * This function writes sequential numbers starting from 0 to a specified portion of EEPROM. | ||||
|  * If the number reaches 255, it wraps around and starts again from 1. | ||||
|  * | ||||
|  * @param memoryAddress Starting address in EEPROM. | ||||
|  * @param length Number of bytes to write. | ||||
|  */ | ||||
| void writeSequentialToEEPROM(uint16_t memoryAddress, uint16_t length) | ||||
| { | ||||
|   if (eeAvailable == false) | ||||
|     return false; | ||||
|   if (!checkEEPROMavailable()) | ||||
|     return; | ||||
|  | ||||
|   uint8_t EEPROMVersionOnChip = ee.readByte(startofConfigData); | ||||
|  | ||||
|   if (EEPROMVersionOnChip < ConfigData_defaults.EEPROM_Version) | ||||
|   uint8_t value = 0; | ||||
|   for (uint16_t i = 0; i < length; i++) | ||||
|   { | ||||
|     Serial.printf("EEPROM Image Version is %d, but %d expected - trying to migrate\n", EEPROMVersionOnChip, ConfigData_defaults.EEPROM_Version); | ||||
|     if (!MigrateEEPROM(EEPROMVersionOnChip)) | ||||
|     { | ||||
|       Serial.print("Error\n"); | ||||
|       MaintainDTC(DTC_EEPROM_MIGRATE_FAILED, DTC_CRITICAL, true, EEPROMVersionOnChip); | ||||
|       return false; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       Serial.print("Success\n"); | ||||
|     } | ||||
|     ee.writeByte(memoryAddress + i, value); | ||||
|     value = (value == 255) ? 1 : value + 1; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool MigrateEEPROM(uint8_t fromVersion) | ||||
| /** | ||||
|  * @brief Write 0 to a portion of EEPROM. | ||||
|  * | ||||
|  * This function writes 0 to a specified portion of EEPROM. | ||||
|  * | ||||
|  * @param memoryAddress Starting address in EEPROM. | ||||
|  * @param length Number of bytes to write. | ||||
|  */ | ||||
| void writeZeroToEEPROM(uint16_t memoryAddress, uint16_t length) | ||||
| { | ||||
|   uint16_t persistanceMarker_onChip; | ||||
|   if (!checkEEPROMavailable()) | ||||
|     return; | ||||
|  | ||||
|   switch (fromVersion) | ||||
|   for (uint16_t i = 0; i < length; i++) | ||||
|   { | ||||
|  | ||||
|     // Version 1 EEPROM Layout:                             startAdress     size (byte) | ||||
|     //  const uint16_t startofConfigData  =                 16               16 | ||||
|     //  const uint16_t startofPersistence =                 32               24 | ||||
|     // | ||||
|     //  typedef struct | ||||
|     //  { | ||||
|     //    uint8_t EEPROM_Version = 1;                       16                1 | ||||
|     //    batteryType_t batteryType = BATTERY_UNDEFINED;    17                4 | ||||
|     //    bool active_faction_on_reboot = false;            21                1 | ||||
|     //    uint32_t checksum = 0;                            22                4 | ||||
|     //  } configData_t; | ||||
|     // | ||||
|     //  typedef struct                                      offset | ||||
|     //  { | ||||
|     //    uint32_t writeCycleCounter = 0;                   0                 4 | ||||
|     //    uint32_t faction_1_timer = 0;                     4                 4 | ||||
|     //    uint32_t faction_2_timer = 0;                     8                 4 | ||||
|     //    uint32_t faction_3_timer = 0;                     12                4 | ||||
|     //    factions_t activeFaction = NONE;                  16                4 | ||||
|     //    uint32_t checksum = 0;                            20                4 | ||||
|     //  } persistenceData_t; | ||||
|  | ||||
|   case 1: | ||||
|     // Migrate Persistance-Data | ||||
|     ee.readBlock(0, (uint8_t *)&persistanceMarker_onChip, sizeof(uint16_t)); | ||||
|     if (persistanceMarker_onChip < startofPersistence) | ||||
|     { | ||||
|       ee.readBlock(persistanceMarker_onChip + 0, (uint8_t *)&PersistenceData.writeCycleCounter, 4); | ||||
|       ee.readBlock(persistanceMarker_onChip + 4, (uint8_t *)&PersistenceData.faction_1_timer, 4); | ||||
|       ee.readBlock(persistanceMarker_onChip + 8, (uint8_t *)&PersistenceData.faction_2_timer, 4); | ||||
|       ee.readBlock(persistanceMarker_onChip + 12, (uint8_t *)&PersistenceData.faction_3_timer, 4); | ||||
|       ee.readBlock(persistanceMarker_onChip + 16, (uint8_t *)&PersistenceData.activeFaction, 4); | ||||
|       ee.readBlock(persistanceMarker_onChip + 20, (uint8_t *)&PersistenceData.checksum, 4); | ||||
|       MovePersistencePage_EEPROM(true); | ||||
|       StorePersistence_EEPROM(); | ||||
|     } | ||||
|  | ||||
|     // Migrate Config-Data and set defaults for Values which doesn't exists in this earlier Version | ||||
|     ConfigData.EEPROM_Version = ConfigData_defaults.EEPROM_Version; | ||||
|     strncpy(ConfigData.Faction_1_Name, ConfigData_defaults.Faction_1_Name, sizeof(ConfigData.Faction_1_Name)); | ||||
|     strncpy(ConfigData.Faction_2_Name, ConfigData_defaults.Faction_2_Name, sizeof(ConfigData.Faction_2_Name)); | ||||
|     strncpy(ConfigData.Faction_3_Name, ConfigData_defaults.Faction_3_Name, sizeof(ConfigData.Faction_3_Name)); | ||||
|     ee.readBlock(17, (uint8_t *)&ConfigData.batteryType, 4); | ||||
|     ee.readBlock(21, (uint8_t *)&ConfigData.active_faction_on_reboot, 1); | ||||
|     StoreConfig_EEPROM(); | ||||
|  | ||||
|     return true; | ||||
|     break; | ||||
|  | ||||
|   default: | ||||
|     return false; | ||||
|     break; | ||||
|     ee.writeByte(memoryAddress + i, 0); | ||||
|   } | ||||
| } | ||||
| @@ -6,4 +6,5 @@ void initGlobals() | ||||
| { | ||||
|   globals.systemStatus = sysStat_Startup; | ||||
|   globals.requestEEAction = EE_IDLE; | ||||
|   globals.systemStatustxt[0] = 0; | ||||
| } | ||||
|   | ||||
| @@ -68,6 +68,7 @@ bool InitLoRa(void (*MPinHelper)(int, int)) | ||||
|     } | ||||
| #elif defined(FEATURE_ENABLE_UARTLORA) | ||||
|     SerialLoRa.begin(9600); | ||||
|     returnval = true; | ||||
| #endif | ||||
|     return returnval; | ||||
| } | ||||
| @@ -78,7 +79,7 @@ void LoRa_Process() | ||||
|     if (e220ttl.available() > 1) | ||||
|     { | ||||
|         ResponseContainer rc = e220ttl.receiveMessageRSSI(); | ||||
|         // Is something goes wrong print error | ||||
|         // If something goes wrong, print error | ||||
|         if (rc.status.code != 1) | ||||
|         { | ||||
|             Serial.println(rc.status.getResponseDescription()); | ||||
| @@ -94,41 +95,47 @@ void LoRa_Process() | ||||
|     } | ||||
| #elif defined(FEATURE_ENABLE_UARTLORA) | ||||
|  | ||||
|     char packageInput[16]; | ||||
|     bool packageRecieved = false; | ||||
|     unsigned int bufferPtr = 0; | ||||
|     static char packageInput[32]; | ||||
|     static bool packageReceived = false; | ||||
|     static unsigned int bufferPtr = 0; | ||||
|     int receivedSize = 0; | ||||
|  | ||||
|     if (SerialLoRa.available() && packageRecieved == false) | ||||
|     while (SerialLoRa.available() && !packageReceived) | ||||
|     { | ||||
|         while (bufferPtr < sizeof(packageInput)) | ||||
|         if (bufferPtr < sizeof(packageInput) - 1) | ||||
|         { | ||||
|             packageInput[bufferPtr] = SerialLoRa.read(); | ||||
|             if (packageInput[bufferPtr] == '\n') | ||||
|             char c = SerialLoRa.read(); | ||||
|             packageInput[bufferPtr] = c; | ||||
|             packageInput[bufferPtr + 1] = '\0'; // always terminate string | ||||
|  | ||||
|             if (c == '\n') | ||||
|             { | ||||
|                 packageInput[bufferPtr] = 0; // terminate String | ||||
|                 packageRecieved = true; | ||||
|                 packageReceived = true; | ||||
|                 receivedSize = bufferPtr; | ||||
|                 bufferPtr = 0; | ||||
|                 Debug_pushMessage("Got LoRa UART: %s\n", packageInput); | ||||
|                 break; | ||||
|             } | ||||
|             else if ((packageInput[bufferPtr] >= 0x30) || (packageInput[bufferPtr] <= 0x5A)) // only accept Numbers, UpperCase-Letters and some special chars | ||||
|             else if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c == ' ' || c == ',' || c == '.')) // Accept numbers, uppercase letters, and some special chars | ||||
|             { | ||||
|                 if (bufferPtr < sizeof(packageInput) - 1) | ||||
|                 { | ||||
|                     bufferPtr++; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     packageInput[bufferPtr] = 0; // terminate String, bc Buffer is full (package to long) | ||||
|                     packageRecieved = true;      // send it anyway to the parser | ||||
|                     Debug_pushMessage("Got LoRa UART: %s\n", packageInput); | ||||
|                     break; | ||||
|                 } | ||||
|                 bufferPtr++; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 Debug_pushMessage("Invalid character received: %c\n", c); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             Debug_pushMessage("Buffer overflow. Resetting buffer.\n"); | ||||
|             bufferPtr = 0; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (packageRecieved) | ||||
|         Parse_LoRa_UartCommand(packageInput, bufferPtr); | ||||
|     if (packageReceived) | ||||
|     { | ||||
|         Parse_LoRa_UartCommand(packageInput, receivedSize); | ||||
|         packageReceived = false; | ||||
|     } | ||||
|  | ||||
| #endif | ||||
| } | ||||
| @@ -242,6 +249,7 @@ void printParameters(struct Configuration configuration) | ||||
| void Parse_LoRa_UartCommand(char input[], int size) | ||||
| { | ||||
|  | ||||
|     Debug_pushMessage("Start parsing, size: %d\n", size); | ||||
|     char delimiter[] = ";"; | ||||
|     char *ptr; | ||||
|     char command[8]; | ||||
| @@ -249,11 +257,28 @@ void Parse_LoRa_UartCommand(char input[], int size) | ||||
|  | ||||
|     ptr = strtok(input, delimiter); | ||||
|  | ||||
|     ptr = strtok(input, delimiter); | ||||
|  | ||||
|     while (ptr != NULL) | ||||
|     { | ||||
|         strncpy(command, ptr, sizeof(command)); | ||||
|         strncpy(command, ptr, sizeof(command) - 1); // Platz für Nullterminator lassen | ||||
|         command[sizeof(command) - 1] = '\0';        // Nullterminator setzen | ||||
|  | ||||
|         ptr = strtok(NULL, delimiter); | ||||
|         strncpy(value, ptr, sizeof(value)); | ||||
|  | ||||
|         if (ptr != NULL) | ||||
|         { | ||||
|             strncpy(value, ptr, sizeof(value) - 1); // Platz für Nullterminator lassen | ||||
|             value[sizeof(value) - 1] = '\0';        // Nullterminator setzen | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Wenn ptr NULL ist, setze value auf leeren String | ||||
|             value[0] = '\0'; | ||||
|         } | ||||
|  | ||||
|         // Hier kannst du den Wert und das Kommando verarbeiten | ||||
|         Debug_pushMessage("Command: %s, Value: %s\n", command, value); | ||||
|     } | ||||
|  | ||||
|     Debug_pushMessage("Parsed LoRa UART Command:  %s Value: %s\n", command, value); | ||||
| @@ -261,10 +286,12 @@ void Parse_LoRa_UartCommand(char input[], int size) | ||||
|     if (!strcmp(command, "ENABLE")) | ||||
|     { | ||||
|         globals.timer_disabled = false; | ||||
|         Debug_pushMessage("Enabled by LoRa\n"); | ||||
|     } | ||||
|     else if (!strcmp(command, "DISABLE")) | ||||
|     { | ||||
|         globals.timer_disabled = false; | ||||
|         globals.timer_disabled = true; | ||||
|         Debug_pushMessage("Disabled by LoRa\n"); | ||||
|     } | ||||
|     else if (!strcmp(command, "RESET")) | ||||
|     { | ||||
| @@ -272,6 +299,7 @@ void Parse_LoRa_UartCommand(char input[], int size) | ||||
|         PersistenceData.faction_1_timer = 0; | ||||
|         PersistenceData.faction_2_timer = 0; | ||||
|         PersistenceData.faction_3_timer = 0; | ||||
|         Debug_pushMessage("Reset by LoRa\n"); | ||||
|     } | ||||
|     else if (!strcmp(command, "TMRSTP")) | ||||
|     { | ||||
|   | ||||
| @@ -19,6 +19,7 @@ | ||||
| #include "globals.h" | ||||
| #include "dtc.h" | ||||
| #include "debugger.h" | ||||
| #include "utilities.h" | ||||
| #if defined(FEATURE_ENABLE_LORA) || defined(FEATURE_ENABLE_UARTLORA) | ||||
| #include "lora_net.h" | ||||
| #endif | ||||
| @@ -42,12 +43,11 @@ TM1637 disp_FAC_3(GPIO_7SEG_CLK, GPIO_7SEG_EN_FAC3); | ||||
|  | ||||
| void SevenSeg_Output(); | ||||
| void toggleWiFiAP(boolean shutdown = false); | ||||
| void SystemShutdown(); | ||||
| void SystemShutdown(bool restart = false); | ||||
| void SetBatteryType(batteryType_t type); | ||||
| void ProcessKeyCombos(bool *btnState); | ||||
| void OverrideDisplay(uint32_t time, const char *message1, const char *message2, const char *message3); | ||||
| void initGlobals(); | ||||
| void maintainSysStat(); | ||||
| void EEPROMCyclicPDS_callback(); | ||||
|  | ||||
| #if defined(FEATURE_ENABLE_UARTLORA) || defined(FEATURE_ENABLE_LORA) | ||||
| void setMPins_Helper(int pin, int status); | ||||
| @@ -61,12 +61,12 @@ void tmrCallback_FactionTicker(); | ||||
| Ticker tmrFactionTicker(tmrCallback_FactionTicker, 1000, 0, MILLIS); | ||||
| void tmrCallback_InputGetter(); | ||||
| Ticker tmrInputGetter(tmrCallback_InputGetter, 250, 0, MILLIS); | ||||
| void tmrCallback_EEPROMCyclicPDS(); | ||||
| Ticker tmrEEPROMCyclicPDS(tmrCallback_EEPROMCyclicPDS, 60000, 0, MILLIS); | ||||
| void EEPROMCyclicPDS_callback(); | ||||
| Ticker tmrEEPROMCyclicPDS(EEPROMCyclicPDS_callback, 60000, 0, MILLIS); | ||||
|  | ||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||
| void tmrCallback_WiFiMaintainConnection(); | ||||
| Ticker tmrWiFiMaintainConnection(tmrCallback_WiFiMaintainConnection, 1000, 0, MILLIS); | ||||
| void wifiMaintainConnectionTicker_callback(); | ||||
| Ticker tmrWiFiMaintainConnection(wifiMaintainConnectionTicker_callback, 1000, 0, MILLIS); | ||||
| #endif | ||||
|  | ||||
| uint32_t DisplayOverrideFlag = 0; | ||||
| @@ -81,24 +81,49 @@ void setMPins_Helper(int pin, int status) | ||||
|  | ||||
| void setup() | ||||
| { | ||||
| 	// Set CPU frequency to 80MHz | ||||
| 	system_update_cpu_freq(SYS_CPU_80MHZ); | ||||
| 	strcpy(globals.DeviceName, DEVICE_NAME); | ||||
| 	snprintf(globals.DeviceName_ID, 42, "%s_%08X", globals.DeviceName, ESP.getChipId()); | ||||
| 	WiFi.persistent(false); | ||||
| 	WiFi.disconnect(); | ||||
|  | ||||
| 	// Generate a unique device name based on ESP chip ID | ||||
| 	strncpy(globals.DeviceName, HOST_NAME, sizeof(globals.DeviceName)); | ||||
| 	snprintf(globals.DeviceNameId, sizeof(globals.DeviceNameId), "%s_%08X", globals.DeviceName, ESP.getChipId()); | ||||
|  | ||||
| 	// Disable WiFi persistent storage | ||||
| 	WiFi.persistent(false); | ||||
|  | ||||
| 	// Initialize and clear Diagnostic Trouble Code (DTC) storage | ||||
| 	ClearAllDTC(); | ||||
|  | ||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||
| 	// Configure WiFi settings for client mode if enabled | ||||
| 	WiFi.mode(WIFI_STA); | ||||
| 	WiFi.setHostname(globals.DeviceNameId); | ||||
| 	wifiMulti.addAP(QUOTE(WIFI_SSID_CLIENT), QUOTE(WIFI_PASSWORD_CLIENT)); | ||||
| 	tmrWiFiMaintainConnection.start(); | ||||
| #else | ||||
| 	// Disable WiFi if WiFi client feature is not enabled | ||||
| 	WiFi.mode(WIFI_OFF); | ||||
| #endif | ||||
|  | ||||
| 	// Initialize Serial communication | ||||
| 	Serial.begin(115200); | ||||
| 	Serial.setDebugOutput(false); | ||||
|  | ||||
| 	Serial.print("\n\n-------------------START-------------------\n"); | ||||
| 	Serial.print(globals.DeviceName); | ||||
| 	Serial.print("\nby Hiabuto Defense\n"); | ||||
| 	Serial.print(globals.DeviceNameId); | ||||
| 	Serial.print("\nby Hiabuto Defense\n\n"); | ||||
|  | ||||
| 	ClearAllDTC(); // Init DTC-Storage | ||||
|  | ||||
| 	InitEEPROM(); | ||||
| 	GetConfig_EEPROM(); | ||||
| 	GetPersistence_EEPROM(); | ||||
| 	// Initialize EEPROM, load configuration, and persistence data from EEPROM | ||||
| 	if (InitEEPROM()) | ||||
| 	{ | ||||
| 		GetConfig_EEPROM(); | ||||
| 		GetPersistence_EEPROM(); | ||||
| 		Serial.printf("Initialized EEPROM at Address 0x%02X\n", I2C_EE_ADDRESS); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		Serial.print("EEPROM not Initialized\n"); | ||||
| 	} | ||||
|  | ||||
| 	if (i2c_io.begin()) | ||||
| 	{ | ||||
| @@ -131,18 +156,9 @@ void setup() | ||||
| 	} | ||||
| #endif | ||||
|  | ||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||
| 	WiFi.mode(WIFI_STA); | ||||
| 	WiFi.setHostname(globals.DeviceName); | ||||
| 	wifiMulti.addAP(QUOTE(WIFI_CLIENT_SSID), QUOTE(WIFI_CLIENT_PASSWORD)); | ||||
| 	tmrWiFiMaintainConnection.start(); | ||||
| 	Serial.print("WiFi-Client Initialized\n"); | ||||
| #else | ||||
| 	WiFi.mode(WIFI_OFF); | ||||
| #endif | ||||
|  | ||||
| 	// Set up OTA updates | ||||
| 	ArduinoOTA.setPort(8266); | ||||
| 	ArduinoOTA.setHostname(globals.DeviceName_ID); | ||||
| 	ArduinoOTA.setHostname(globals.DeviceNameId); | ||||
| 	ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD)); | ||||
|  | ||||
| 	ArduinoOTA.onStart([]() | ||||
| @@ -186,15 +202,20 @@ void setup() | ||||
|                        else if (error == OTA_END_ERROR) | ||||
|                          Serial.println("End Failed"); }); | ||||
|  | ||||
| 	// Begin OTA updates | ||||
| 	ArduinoOTA.begin(); | ||||
| 	Serial.print("\nOTA-Init done"); | ||||
|  | ||||
| 	// Initialize the web user interface | ||||
| 	initWebUI(); | ||||
| 	Serial.print("\nWebUI-Init done"); | ||||
|  | ||||
| 	// Initialize global variables | ||||
| 	initGlobals(); | ||||
| 	Serial.print("\nglobals-Init done"); | ||||
| #ifdef CAPTIVE | ||||
| 	dnsServer.start(53, "*", WiFi.softAPIP()); | ||||
| #endif | ||||
|  | ||||
| 	// Start cyclic EEPROM updates for Persistence Data Structure (PDS) | ||||
| 	tmrEEPROMCyclicPDS.start(); | ||||
|  | ||||
| 	disp_FAC_1.init(); | ||||
| 	disp_FAC_1.setBrightness(5); | ||||
| @@ -203,7 +224,6 @@ void setup() | ||||
| 	disp_FAC_3.init(); | ||||
| 	disp_FAC_3.setBrightness(5); | ||||
|  | ||||
| 	tmrEEPROMCyclicPDS.start(); | ||||
| 	tmrFactionTicker.start(); | ||||
| 	tmrInputGetter.start(); | ||||
|  | ||||
| @@ -215,8 +235,6 @@ void setup() | ||||
|  | ||||
| void loop() | ||||
| { | ||||
| 	maintainSysStat(); | ||||
|  | ||||
| 	tmrEEPROMCyclicPDS.update(); | ||||
| 	tmrFactionTicker.update(); | ||||
| 	tmrInputGetter.update(); | ||||
| @@ -234,13 +252,64 @@ void loop() | ||||
| 	tmrStatusSender.update(); | ||||
| #endif | ||||
|  | ||||
| #ifdef CAPTIVE | ||||
| 	dnsServer.processNextRequest(); | ||||
| #endif | ||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||
| 	// Update WiFi connection maintenance ticker if WiFi client feature is enabled | ||||
| 	tmrWiFiMaintainConnection.update(); | ||||
| #endif | ||||
|  | ||||
| 	static tSystem_Status lastStatus = sysStat_Init; | ||||
|  | ||||
| 	// Handle different system statuses | ||||
| 	switch (globals.systemStatus) | ||||
| 	{ | ||||
| 	case sysStat_Startup: | ||||
| 		if (lastStatus != globals.systemStatus) | ||||
| 		{ | ||||
| 			strcpy_P(globals.systemStatustxt, PSTR("Startup")); | ||||
| 			lastStatus = globals.systemStatus; | ||||
| 		} | ||||
| 		// Transition to Normal status after startup delay | ||||
| 		if (millis() > STARTUP_DELAY) | ||||
| 		{ | ||||
| 			globals.systemStatus = sysStat_Normal; | ||||
| 			globals.resumeStatus = sysStat_Normal; | ||||
| 		} | ||||
| 		break; | ||||
|  | ||||
| 	case sysStat_Normal: | ||||
| 		if (lastStatus != globals.systemStatus) | ||||
| 		{ | ||||
| 			strcpy_P(globals.systemStatustxt, PSTR("Normal")); | ||||
| 			lastStatus = globals.systemStatus; | ||||
| 		} | ||||
| 		break; | ||||
|  | ||||
| 		if (lastStatus != globals.systemStatus) | ||||
| 		{ | ||||
| 			strcpy_P(globals.systemStatustxt, PSTR("Error")); | ||||
| 			lastStatus = globals.systemStatus; | ||||
| 		} | ||||
| 		break; | ||||
|  | ||||
| 	case sysStat_Shutdown: | ||||
| 		if (lastStatus != globals.systemStatus) | ||||
| 		{ | ||||
| 			strcpy_P(globals.systemStatustxt, PSTR("Shutdown")); | ||||
| 			lastStatus = globals.systemStatus; | ||||
| 		} | ||||
| 		SystemShutdown(false); | ||||
| 		break; | ||||
| 	case sysStat_Error: | ||||
| 		if (lastStatus != globals.systemStatus) | ||||
| 		{ | ||||
| 			strcpy_P(globals.systemStatustxt, PSTR("Error")); | ||||
| 			lastStatus = globals.systemStatus; | ||||
| 		} | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	// Yield to allow other tasks to run | ||||
| 	yield(); | ||||
| } | ||||
|  | ||||
| @@ -254,7 +323,7 @@ String macToString(const unsigned char *mac) | ||||
|  | ||||
| void SevenSeg_Output() | ||||
| { | ||||
| 	char sevenSegBuff[5] = ""; | ||||
| 	char sevenSegBuff[9] = ""; | ||||
|  | ||||
| 	if (DisplayOverrideFlag > millis()) | ||||
| 	{ | ||||
| @@ -270,9 +339,11 @@ void SevenSeg_Output() | ||||
| 		if (globals.battery_level < BAT_LOW_PERCENT && millis() % 10000 > 7000) | ||||
| 		{ | ||||
| 			if (millis() % 3000 < 1500) | ||||
| 				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", globals.battery_level); | ||||
| 				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4u", globals.battery_level); | ||||
| 			else | ||||
| 				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%3d.%1d", (globals.loadvoltage_mV / 1000), ((globals.loadvoltage_mV % 1000) / 100)); | ||||
| 				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%3u.%1u", (globals.loadvoltage_mV / 1000), ((globals.loadvoltage_mV % 1000) / 100)); | ||||
|  | ||||
| 			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected | ||||
|  | ||||
| 			disp_FAC_1.setBrightness(1); | ||||
| 			disp_FAC_1.display(" Bat"); | ||||
| @@ -288,18 +359,21 @@ void SevenSeg_Output() | ||||
| 			disp_FAC_1.setBrightness(PersistenceData.activeFaction == FACTION_1 ? 5 : 1); | ||||
| 			disp_FAC_1.refresh(); | ||||
| 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_1_timer / 60); | ||||
| 			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected | ||||
| 			disp_FAC_1.display(String(sevenSegBuff), false, false); | ||||
| 			disp_FAC_1.setDp((PersistenceData.activeFaction == FACTION_1) && (millis() % 1000 > 500) ? 0x08 : 0x00); | ||||
|  | ||||
| 			disp_FAC_2.setBrightness(PersistenceData.activeFaction == FACTION_2 ? 5 : 1); | ||||
| 			disp_FAC_2.refresh(); | ||||
| 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_2_timer / 60); | ||||
| 			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected | ||||
| 			disp_FAC_2.display(String(sevenSegBuff), false, false); | ||||
| 			disp_FAC_2.setDp((PersistenceData.activeFaction == FACTION_2) && (millis() % 1000 > 500) ? 0x08 : 0x00); | ||||
|  | ||||
| 			disp_FAC_3.setBrightness(PersistenceData.activeFaction == FACTION_3 ? 5 : 1); | ||||
| 			disp_FAC_3.refresh(); | ||||
| 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_3_timer / 60); | ||||
| 			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected | ||||
| 			disp_FAC_3.display(String(sevenSegBuff), false, false); | ||||
| 			disp_FAC_3.setDp((PersistenceData.activeFaction == FACTION_3) && (millis() % 1000 > 500) ? 0x08 : 0x00); | ||||
| 		} | ||||
| @@ -347,36 +421,36 @@ void tmrCallback_InputGetter() | ||||
|  | ||||
| 	if (keysPressed > 1) | ||||
| 	{ | ||||
| 		Debug_pushMessage("ERROR: More than one Flag active - setting no Faction active"); | ||||
| 		Debug_pushMessage("ERROR: More than one Flag active - setting no Faction active\n"); | ||||
| 		PersistenceData.activeFaction = NONE; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (btnState[0] == FAC_1_TRG_PRESSED && globals.timer_disabled == false) | ||||
| 	if (btnState[0] == FAC_1_TRG_PRESSED && globals.timer_disabled != true) | ||||
| 	{ | ||||
| 		if (PersistenceData.activeFaction != FACTION_1) | ||||
| 		{ | ||||
| 			Debug_pushMessage("Faction 1 captured !"); | ||||
| 			Debug_pushMessage("Faction 1 captured !\n"); | ||||
| 			globals.requestEEAction = EE_PDS_SAVE; | ||||
| 		} | ||||
| 		PersistenceData.activeFaction = FACTION_1; | ||||
| 	} | ||||
|  | ||||
| 	if (btnState[1] == FAC_2_TRG_PRESSED && globals.timer_disabled == false) | ||||
| 	if (btnState[1] == FAC_2_TRG_PRESSED && globals.timer_disabled != true) | ||||
| 	{ | ||||
| 		if (PersistenceData.activeFaction != FACTION_2) | ||||
| 		{ | ||||
| 			Debug_pushMessage("Faction 2 captured !"); | ||||
| 			Debug_pushMessage("Faction 2 captured !\n"); | ||||
| 			globals.requestEEAction = EE_PDS_SAVE; | ||||
| 		} | ||||
| 		PersistenceData.activeFaction = FACTION_2; | ||||
| 	} | ||||
|  | ||||
| 	if (btnState[2] == FAC_3_TRG_PRESSED && globals.timer_disabled == false) | ||||
| 	if (btnState[2] == FAC_3_TRG_PRESSED && globals.timer_disabled != true) | ||||
| 	{ | ||||
| 		if (PersistenceData.activeFaction != FACTION_3) | ||||
| 		{ | ||||
| 			Debug_pushMessage("Faction 3 captured !"); | ||||
| 			Debug_pushMessage("Faction 3 captured !\n"); | ||||
| 			globals.requestEEAction = EE_PDS_SAVE; | ||||
| 		} | ||||
| 		PersistenceData.activeFaction = FACTION_3; | ||||
| @@ -425,8 +499,8 @@ void tmrCallback_PowerMonitor() | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	MaintainDTC(DTC_BAT_LOW, DTC_WARN, (battery_level < 15 ? true : false), battery_level); | ||||
| 	MaintainDTC(DTC_BAT_CRITICAL, DTC_CRITICAL, (battery_level < 5 ? true : false), battery_level); | ||||
| 	MaintainDTC(DTC_BAT_LOW, (battery_level < 15 ? true : false), battery_level); | ||||
| 	MaintainDTC(DTC_BAT_CRITICAL, (battery_level < 5 ? true : false), battery_level); | ||||
|  | ||||
| 	// Serial.printf("Battery Level: %d %%\n", globals.battery_level); | ||||
| 	// Serial.printf("Bus Voltage: %f V\n", busvoltage); | ||||
| @@ -436,74 +510,133 @@ void tmrCallback_PowerMonitor() | ||||
| 	// Serial.printf("Power: %f mW\n", power_mW); | ||||
| } | ||||
|  | ||||
| void tmrCallback_EEPROMCyclicPDS() | ||||
| /** | ||||
|  * @brief Callback function for cyclically storing Persistence Data Structure (PDS) to EEPROM. | ||||
|  * | ||||
|  * This callback function is invoked periodically to store the Persistence Data Structure (PDS) | ||||
|  * to the EEPROM. It ensures that essential data is saved persistently, allowing the system to | ||||
|  * recover its state after power cycles or resets. | ||||
|  */ | ||||
| void EEPROMCyclicPDS_callback() | ||||
| { | ||||
| 	StorePersistence_EEPROM(); | ||||
| } | ||||
|  | ||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||
| void tmrCallback_WiFiMaintainConnection() | ||||
| /** | ||||
|  * @brief Callback function for maintaining WiFi connection and handling connection failures. | ||||
|  * | ||||
|  * This callback function is used by a ticker to periodically check the WiFi connection status. | ||||
|  * If the device is not connected to WiFi, it counts connection failures. If the number of failures | ||||
|  * exceeds a defined threshold, the function triggers the initiation of an Access Point (AP) mode | ||||
|  * using the `toggleWiFiAP` function. | ||||
|  */ | ||||
| void wifiMaintainConnectionTicker_callback() | ||||
| { | ||||
| 	// Static variables to track WiFi connection failure count and maximum allowed failures | ||||
| 	static uint32_t WiFiFailCount = 0; | ||||
| 	const uint32_t WiFiFailMax = 20; | ||||
|  | ||||
| 	// Check if the device is connected to WiFi | ||||
| 	if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED) | ||||
| 	{ | ||||
| 		return; | ||||
| 		return; // Exit if connected | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// Increment WiFi connection failure count | ||||
| 		if (WiFiFailCount < WiFiFailMax) | ||||
| 		{ | ||||
| 			WiFiFailCount++; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			Debug_pushMessage("WiFi not connected! - Start AP"); | ||||
| 			// Trigger AP mode if the maximum failures are reached | ||||
| 			Debug_pushMessage("WiFi not connected! - Start AP\n"); | ||||
| 			toggleWiFiAP(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| void toggleWiFiAP(boolean shutdown) | ||||
| /** | ||||
|  * @brief Toggles the WiFi functionality based on the current status. | ||||
|  * | ||||
|  * This function manages the WiFi state, either turning it off or starting it as an Access Point (AP), | ||||
|  * depending on the current mode. If the WiFi is turned off, it can be started in AP mode with the | ||||
|  * device name and password configured. Additionally, it may stop certain operations related to WiFi | ||||
|  * maintenance or display debug messages based on the defined features. | ||||
|  * | ||||
|  * @param shutdown Flag indicating whether the system is in a shutdown state. | ||||
|  */ | ||||
| void toggleWiFiAP(bool shutdown) | ||||
| { | ||||
| 	char buffer[33]; | ||||
| 	// Check if WiFi is currently active | ||||
| 	if (WiFi.getMode() != WIFI_OFF) | ||||
| 	{ | ||||
| 		// Turn off WiFi | ||||
| 		WiFi.mode(WIFI_OFF); | ||||
| 		Serial.println("WiFi turned off"); | ||||
| 		Debug_pushMessage("WiFi turned off\n"); | ||||
|  | ||||
| 		// Stop WiFi maintenance connection ticker if enabled | ||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||
| 		tmrWiFiMaintainConnection.stop(); | ||||
| #endif | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// Start WiFi in Access Point (AP) mode | ||||
| 		WiFi.mode(WIFI_AP); | ||||
| 		WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0)); | ||||
| 		WiFi.softAP(QUOTE(WIFI_AP_SSID), QUOTE(WIFI_AP_PASSWORD)); | ||||
| 		sanitizeWiFiString(globals.DeviceNameId, buffer, sizeof(buffer)); | ||||
| 		WiFi.softAP(buffer, QUOTE(WIFI_AP_PASSWORD)); | ||||
|  | ||||
| 		// Stop WiFi maintenance connection ticker if enabled and display debug messages | ||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||
| 		tmrWiFiMaintainConnection.stop(); | ||||
| 		Serial.println("WiFi AP started, stopped Maintain-Timer"); | ||||
| 		Debug_pushMessage("WiFi AP started, stopped Maintain-Timer\n"); | ||||
| #else | ||||
| 		Serial.println("WiFi AP started"); | ||||
| 		Debug_pushMessage("WiFi AP started\n"); | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void SystemShutdown() | ||||
| /** | ||||
|  * @brief Performs necessary tasks before shutting down and optionally restarts the ESP. | ||||
|  * | ||||
|  * This function initiates a system shutdown, performing tasks such as storing configuration | ||||
|  * and persistence data to EEPROM before shutting down. If a restart is requested, the ESP | ||||
|  * will be restarted; otherwise, the system will enter an indefinite loop. | ||||
|  * | ||||
|  * @param restart Flag indicating whether to restart the ESP after shutdown (default: false). | ||||
|  */ | ||||
| void SystemShutdown(bool restart) | ||||
| { | ||||
| 	static uint32_t shutdown_delay = 0; | ||||
|  | ||||
| 	// Initialize shutdown delay on the first call | ||||
| 	if (shutdown_delay == 0) | ||||
| 	{ | ||||
| 		shutdown_delay = millis() + SHUTDOWN_DELAY_MS; | ||||
| 		Serial.printf("Shutdown requested - Restarting in %d seconds\n", SHUTDOWN_DELAY_MS / 1000); | ||||
| 	} | ||||
|  | ||||
| 	// Check if the shutdown delay has elapsed | ||||
| 	if (shutdown_delay < millis()) | ||||
| 	{ | ||||
| 		StoreConfig_EEPROM(); | ||||
| 		Webserver_Shutdown(); | ||||
|  | ||||
| 		// Store persistence data to EEPROM | ||||
| 		StorePersistence_EEPROM(); | ||||
| 		ESP.restart(); | ||||
|  | ||||
| 		// Perform restart if requested, otherwise enter an indefinite loop | ||||
| 		if (restart) | ||||
| 			ESP.restart(); | ||||
| 		else | ||||
| 			while (1) | ||||
| 				; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -571,7 +704,7 @@ void ProcessKeyCombos(bool *btnState) | ||||
|  | ||||
| 		if (keyCount_Fac2 == 2 && keyCount_Fac3 == 0) | ||||
| 		{ | ||||
| 			Debug_pushMessage("KeyCombo: WiFi AP ON"); | ||||
| 			Debug_pushMessage("KeyCombo: WiFi AP ON\n"); | ||||
| 			OverrideDisplay(5000, "NET ", "    ", "    "); | ||||
| 			toggleWiFiAP(false); | ||||
| 		} | ||||
| @@ -605,7 +738,7 @@ void ProcessKeyCombos(bool *btnState) | ||||
| void maintainSysStat() | ||||
| { | ||||
|  | ||||
| 	static tSystem_Status lastStat = sysStat_null; | ||||
| 	static tSystem_Status lastStat = sysStat_Startup; | ||||
|  | ||||
| 	// system Status Transistions | ||||
| 	switch (globals.systemStatus) | ||||
| @@ -621,7 +754,6 @@ void maintainSysStat() | ||||
|  | ||||
| 	case sysStat_Error: | ||||
| 	case sysStat_Normal: | ||||
| 	case sysStat_null: | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| @@ -641,7 +773,6 @@ void maintainSysStat() | ||||
|  | ||||
| 		case sysStat_Error: | ||||
| 		case sysStat_Normal: | ||||
| 		case sysStat_null: | ||||
| 		default: | ||||
| 			break; | ||||
| 		} | ||||
|   | ||||
| @@ -1,42 +0,0 @@ | ||||
| #include "oled_display.h" | ||||
|  | ||||
| Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST); | ||||
|  | ||||
| void OLED_Init() | ||||
| { | ||||
|  | ||||
|     // reset OLED display via software | ||||
|     pinMode(OLED_RST, OUTPUT); | ||||
|     digitalWrite(OLED_RST, LOW); | ||||
|     delay(20); | ||||
|     digitalWrite(OLED_RST, HIGH); | ||||
|  | ||||
|     // initialize OLED | ||||
|     Wire.begin(OLED_SDA, OLED_SCL); | ||||
|     if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false, false)) | ||||
|     { // Address 0x3C for 128x32 | ||||
|         Serial.println(F("SSD1306 allocation failed")); | ||||
|     } | ||||
|  | ||||
|     display.clearDisplay(); | ||||
|     display.setTextColor(WHITE); | ||||
|     display.setTextSize(1); | ||||
|     display.setCursor(0, 0); | ||||
|     display.print("DISPLAY INIT"); | ||||
|     display.display(); | ||||
| } | ||||
|  | ||||
| void OLED_Process() | ||||
| { | ||||
|  | ||||
|     display.clearDisplay(); | ||||
|     display.setCursor(0, 0); | ||||
|     display.printf("LiPo: %d%%\n", globals.battery_level); | ||||
|     display.print(PersistenceData.activeFaction == FACTION_1 ? "> " : "  "); | ||||
|     display.printf("%-5s: %02d:%02d:%02d\n", PersistenceData.faction_1_timer, PersistenceData.faction_1_timer / 3600, (PersistenceData.faction_1_timer / 60) % 60, PersistenceData.faction_1_timer % 60); | ||||
|     display.print(PersistenceData.activeFaction == FACTION_2 ? "> " : "  "); | ||||
|     display.printf("%-5s: %02d:%02d:%02d\n", PersistenceData.faction_2_timer, PersistenceData.faction_2_timer / 3600, (PersistenceData.faction_2_timer / 60) % 60, PersistenceData.faction_2_timer % 60); | ||||
|     display.print(PersistenceData.activeFaction == FACTION_3 ? "> " : "  "); | ||||
|     display.printf("%-5s: %02d:%02d:%02d\n", PersistenceData.faction_3_timer, PersistenceData.faction_3_timer / 3600, (PersistenceData.faction_3_timer / 60) % 60, PersistenceData.faction_3_timer % 60); | ||||
|     display.display(); | ||||
| } | ||||
							
								
								
									
										43
									
								
								Software/src/struct2json.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								Software/src/struct2json.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| /** | ||||
|  * @file struct2json.cpp | ||||
|  * | ||||
|  * @brief Implementation file for converting structs to JSON objects. | ||||
|  * | ||||
|  * @note This file is auto-generated by a script on 2024-05-30 22:54:25. | ||||
|  * | ||||
|  * @author Marcel Peterkau | ||||
|  * @date   30.05.2024 | ||||
|  */ | ||||
|  | ||||
|  | ||||
| #include "struct2json.h" | ||||
|  | ||||
| void generateJsonObject_ConfigData(JsonObject data) | ||||
| { | ||||
|     data["EEPROM_Version"] = ConfigData.EEPROM_Version; | ||||
|     data["batteryType"] = ConfigData.batteryType; | ||||
|     data["active_faction_on_reboot"] = ConfigData.active_faction_on_reboot; | ||||
|     data["Faction_1_Name"] = ConfigData.Faction_1_Name; | ||||
|     data["Faction_2_Name"] = ConfigData.Faction_2_Name; | ||||
|     data["Faction_3_Name"] = ConfigData.Faction_3_Name; | ||||
|     data["wifi_ap_ssid"] = ConfigData.wifi_ap_ssid; | ||||
|     data["wifi_ap_password"] = ConfigData.wifi_ap_password; | ||||
|     data["wifi_client_ssid"] = ConfigData.wifi_client_ssid; | ||||
|     data["wifi_client_password"] = ConfigData.wifi_client_password; | ||||
|     data["wifi_autoconnect"] = ConfigData.wifi_autoconnect; | ||||
|     data["checksum"] = ConfigData.checksum; | ||||
|     } | ||||
|  | ||||
| void generateJsonObject_PersistenceData(JsonObject data) | ||||
| { | ||||
|     data["writeCycleCounter"] = PersistenceData.writeCycleCounter; | ||||
|     data["faction_1_timer"] = PersistenceData.faction_1_timer; | ||||
|     data["faction_2_timer"] = PersistenceData.faction_2_timer; | ||||
|     data["faction_3_timer"] = PersistenceData.faction_3_timer; | ||||
|     data["activeFaction"] = PersistenceData.activeFaction; | ||||
|     data["checksum"] = PersistenceData.checksum; | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
| // CODEGENERATOR_CHECKSUM: 735cd4daf9a46bd773bdf5e6cd5a58d61b0d877196399bc2784a0d0ea7af717d | ||||
							
								
								
									
										94
									
								
								Software/src/utilities.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								Software/src/utilities.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| #include "utilities.h" | ||||
|  | ||||
| /** | ||||
|  * @brief Validates whether a given string contains only characters allowed in WiFi SSIDs and passwords. | ||||
|  * | ||||
|  * This function checks each character in the provided string to ensure | ||||
|  * that it contains only characters allowed in WiFi SSIDs and passwords. | ||||
|  * It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as | ||||
|  * the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ | ||||
|  * | ||||
|  * @param string Pointer to the string to be validated. | ||||
|  * @param size Size of the string including the null-terminator. | ||||
|  * @return true if the string contains only allowed characters or is NULL, | ||||
|  *         false otherwise. | ||||
|  */ | ||||
| bool validateWiFiString(char *string, size_t size) | ||||
| { | ||||
|     if (string == NULL) | ||||
|         return false; | ||||
|  | ||||
|     for (size_t i = 0; i < size; i++) | ||||
|     { | ||||
|         char c = string[i]; | ||||
|         if (c == '\0') | ||||
|         { | ||||
|             // Reached the end of the string, all characters were valid WiFi characters. | ||||
|             return true; | ||||
|         } | ||||
|         if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || | ||||
|               (c >= '0' && c <= '9') || c == '!' || c == '"' || c == '#' || | ||||
|               c == '$' || c == '%' || c == '&' || c == '\'' || c == '(' || | ||||
|               c == ')' || c == '*' || c == '+' || c == ',' || c == '-' || | ||||
|               c == '.' || c == '/' || c == ':' || c == ';' || c == '<' || | ||||
|               c == '=' || c == '>' || c == '?' || c == '@' || c == '[' || | ||||
|               c == '\\' || c == ']' || c == '^' || c == '_' || c == '`' || | ||||
|               c == '{' || c == '|' || c == '}' || c == '~')) | ||||
|         { | ||||
|             // Found a character that is not a valid WiFi character. | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     // If the loop completes without finding a null terminator, the string is invalid. | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @brief Copies a string to a buffer, replacing invalid WiFi SSID characters with a placeholder. | ||||
|  * | ||||
|  * This function checks each character in the provided input string to ensure | ||||
|  * that it contains only characters allowed in WiFi SSIDs and passwords. If a character | ||||
|  * is invalid, it replaces it with a placeholder character (e.g., '_'). | ||||
|  * It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as | ||||
|  * the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ | ||||
|  * | ||||
|  * @param input Pointer to the input string to be validated and copied. | ||||
|  * @param buffer Pointer to the buffer where the output string will be copied. | ||||
|  * @param bufferSize Size of the buffer including the null-terminator. | ||||
|  */ | ||||
| void sanitizeWiFiString(const char *input, char *buffer, size_t bufferSize) | ||||
| { | ||||
|     if (input == NULL || buffer == NULL || bufferSize == 0) | ||||
|         return; | ||||
|  | ||||
|     size_t i; | ||||
|     for (i = 0; i < bufferSize - 1; i++) // Leave space for null-terminator | ||||
|     { | ||||
|         char c = input[i]; | ||||
|         if (c == '\0') | ||||
|         { | ||||
|             // Reached the end of the input string, terminate the buffer | ||||
|             buffer[i] = '\0'; | ||||
|             return; | ||||
|         } | ||||
|         if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || | ||||
|               (c >= '0' && c <= '9') || c == '!' || c == '"' || c == '#' || | ||||
|               c == '$' || c == '%' || c == '&' || c == '\'' || c == '(' || | ||||
|               c == ')' || c == '*' || c == '+' || c == ',' || c == '-' || | ||||
|               c == '.' || c == '/' || c == ':' || c == ';' || c == '<' || | ||||
|               c == '=' || c == '>' || c == '?' || c == '@' || c == '[' || | ||||
|               c == '\\' || c == ']' || c == '^' || c == '_' || c == '`' || | ||||
|               c == '{' || c == '|' || c == '}' || c == '~')) | ||||
|         { | ||||
|             // Replace invalid character with placeholder | ||||
|             buffer[i] = '_'; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Copy valid character to buffer | ||||
|             buffer[i] = c; | ||||
|         } | ||||
|     } | ||||
|     // Null-terminate the buffer | ||||
|     buffer[i] = '\0'; | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,5 @@ | ||||
| [wifi_cred] | ||||
| wifi_ap_ssid = wifi-ap-ssid | ||||
| admin_password = chainlube | ||||
| wifi_ap_password = wifiappass | ||||
| wifi_client_ssid = wifi-ssid | ||||
| wifi_client_password = wifi-pass | ||||
| ota_password = ota-password | ||||
| wifi_ssid_client = wifi-ssid | ||||
| wifi_password_client = ota-password | ||||
		Reference in New Issue
	
	Block a user