Compare commits
	
		
			6 Commits
		
	
	
		
			1.04_RC1
			...
			ab2ab0e0c1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ab2ab0e0c1 | |||
| 5ee0a23a6d | |||
| cf76ea7cc7 | |||
| 7a2e95c126 | |||
| 1cf0560957 | |||
| b66d175948 | 
							
								
								
									
										1
									
								
								Software/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								Software/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -5,3 +5,4 @@ data/ | |||||||
| .vscode/launch.json | .vscode/launch.json | ||||||
| .vscode/ipch | .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 | ||||||
| @@ -6,9 +6,71 @@ import glob | |||||||
| import shutil | import shutil | ||||||
| import gzip | import gzip | ||||||
| import os | import os | ||||||
|  | import subprocess | ||||||
|  | import platform | ||||||
| Import("env") | Import("env") | ||||||
| Import("projenv") | 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): | def gzip_file(src_path, dst_path): | ||||||
| 
 | 
 | ||||||
| @@ -47,14 +109,16 @@ def gzip_webfiles(source, target, env): | |||||||
|     filetypes_to_gzip = ['.css', '.png', '.js', '.ico', '.woff2', '.json'] |     filetypes_to_gzip = ['.css', '.png', '.js', '.ico', '.woff2', '.json'] | ||||||
|     print('\nGZIP: Starting gzip-Process for LittleFS-Image...\n') |     print('\nGZIP: Starting gzip-Process for LittleFS-Image...\n') | ||||||
|     data_src_dir_path = os.path.join(env.get('PROJECT_DIR'), 'data_src') |     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') |     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 |     # 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 + |         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 + |         print('GZIP: Renaming "' + data_dir_path + | ||||||
|               '" to "' + data_src_dir_path + '"') |               '" to "' + data_temp_dir_path + '"') | ||||||
|         os.rename(data_dir_path, data_src_dir_path) |         os.rename(data_dir_path, data_temp_dir_path) | ||||||
|     # Delete the 'data' directory |     # Delete the 'data' directory | ||||||
|     if(os.path.exists(data_dir_path)): |     if(os.path.exists(data_dir_path)): | ||||||
|         print('GZIP: Deleting the "data" directory ' + data_dir_path) |         print('GZIP: Deleting the "data" directory ' + data_dir_path) | ||||||
| @@ -67,27 +131,27 @@ def gzip_webfiles(source, target, env): | |||||||
|     files_to_copy = [] |     files_to_copy = [] | ||||||
|     files_to_gzip = [] |     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: |     for file in all_data_src: | ||||||
|         file_name, file_extension = os.path.splitext(file) |         file_name, file_extension = os.path.splitext(file) | ||||||
|         print(file_name + " has filetype " + file_extension) |         print(file_name + " has filetype " + file_extension) | ||||||
|         if file_extension in filetypes_to_gzip: |         if file_extension in filetypes_to_gzip: | ||||||
|             files_to_gzip.append(file) |             files_to_gzip.append(file) | ||||||
|         else: |         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) |             files_to_copy.append(filename_subdir) | ||||||
| 
 | 
 | ||||||
|     for file in files_to_copy: |     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) |         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 |     # Compress and move files | ||||||
|      |      | ||||||
|     was_error = False |     was_error = False | ||||||
|     try: |     try: | ||||||
|         for source_file_path in files_to_gzip: |         for source_file_path in files_to_gzip: | ||||||
|             print('GZIP: compressing... ' + source_file_path) |             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 |             target_file_path = data_dir_path + filename_subdir | ||||||
|             os.makedirs(os.path.dirname(target_file_path), exist_ok=True) |             os.makedirs(os.path.dirname(target_file_path), exist_ok=True) | ||||||
|             print('GZIP: Compressed... ' + target_file_path) |             print('GZIP: Compressed... ' + target_file_path) | ||||||
| @@ -100,6 +164,7 @@ def gzip_webfiles(source, target, env): | |||||||
|         print('GZIP: Failure/Incomplete.\n') |         print('GZIP: Failure/Incomplete.\n') | ||||||
|     else: |     else: | ||||||
|         print('GZIP: Compressed correctly.\n') |         print('GZIP: Compressed correctly.\n') | ||||||
|  |         shutil.rmtree(data_temp_dir_path) | ||||||
| 
 | 
 | ||||||
|     return |     return | ||||||
|   |   | ||||||
							
								
								
									
										8
									
								
								Software/codegen/run_pre.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Software/codegen/run_pre.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | Import("env")  # pylint: disable=undefined-variable | ||||||
|  | env.Execute("\"$PYTHONEXE\" -m pip install jinja2") | ||||||
|  |  | ||||||
|  | import struct2json | ||||||
|  | import dtcs | ||||||
|  |  | ||||||
|  | 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 }} | ||||||
| @@ -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. |  * Copyright 2011-2018 Twitter, Inc. | ||||||
|  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) |  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||||||
|  */ |  */ | ||||||
| :root { |  :root { | ||||||
|     --blue: #007bff; |     --blue: #007bff; | ||||||
|     --indigo: #6610f2; |     --indigo: #6610f2; | ||||||
|     --purple: #6f42c1; |     --purple: #6f42c1; | ||||||
|   | |||||||
| @@ -25,3 +25,66 @@ hr { | |||||||
|     padding: 10px; |     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" | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										196
									
								
								Software/data_src/static/js/dtc_table.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								Software/data_src/static/js/dtc_table.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | |||||||
|  | const jsonFilePath = "static/dtc_table.json"; | ||||||
|  |  | ||||||
|  | var dtcState = {}; | ||||||
|  |  | ||||||
|  | async function processDTCNotifications(dtcArray) { | ||||||
|  |   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]) { | ||||||
|  |           dtcState[errorCode] = activity; | ||||||
|  |           if (activity === 1) showNotification(description, severity); | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         // DTC ist neu, Zustand speichern und Benachrichtigung anzeigen | ||||||
|  |         dtcState[errorCode] = activity; | ||||||
|  |         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; | ||||||
|  |   } else { | ||||||
|  |     // 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 gateway = `ws://${window.location.hostname}/ws`; | ||||||
| var websocket; | 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() { | function initWebSocket() { | ||||||
|   console.log("Trying to open a WebSocket connection..."); |   console.log("Trying to open a WebSocket connection..."); | ||||||
| @@ -12,12 +20,27 @@ function initWebSocket() { | |||||||
| } | } | ||||||
|  |  | ||||||
| function initButtons() { | function initButtons() { | ||||||
|   document |   var elements = document.getElementsByClassName("btn-wsevent"); | ||||||
|     .getElementById("btn-ws-stop") |  | ||||||
|     .addEventListener("click", livedebug_stop); |   if (elements.length > 0) { | ||||||
|   document |     for (var i = 0; i < elements.length; i++) { | ||||||
|     .getElementById("btn-ws-start") |       let element = elements[i]; | ||||||
|     .addEventListener("click", livedebug_start); |       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) { | function onOpen(event) { | ||||||
| @@ -26,28 +49,95 @@ function onOpen(event) { | |||||||
|  |  | ||||||
| function onClose(event) { | function onClose(event) { | ||||||
|   console.log("Connection closed"); |   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) { | function onMessage(event) { | ||||||
|   var livedebug_out = document.getElementById("livedebug-out"); |   var data = event.data; | ||||||
|   var textarea_heigth = livedebug_out.scrollHeight; |  | ||||||
|   livedebug_out.value += event.data; |   if (data.startsWith("NOTIFY:")) { | ||||||
|   livedebug_out.scrollTop = livedebug_out.scrollHeight; |     var notify_data = data.slice(7).split(";")[1]; | ||||||
|   do_resize(livedebug_out); |     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) { | function onLoad(event) { | ||||||
|   initWebSocket(); |   initWebSocket(); | ||||||
|   initButtons(); |   initButtons(); | ||||||
|  |   initSettingInputs(); | ||||||
|  |   overlay.style.display = "flex"; | ||||||
| } | } | ||||||
|  |  | ||||||
| function livedebug_start() { | function websocket_sendevent(element_id, element_value) { | ||||||
|   websocket.send("start"); |   websocket.send(element_id + ":" + element_value); | ||||||
| } |  | ||||||
|  |  | ||||||
| function livedebug_stop() { |  | ||||||
|   websocket.send("stop"); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function do_resize(textbox) { | function do_resize(textbox) { | ||||||
| @@ -67,18 +157,77 @@ function do_resize(textbox) { | |||||||
|   else textbox.rows = rows; |   else textbox.rows = rows; | ||||||
| } | } | ||||||
|  |  | ||||||
| function notifyMe() { | function fillValuesToHTML(dataset) { | ||||||
|   if (!("Notification" in window)) { |   for (var key in dataset) { | ||||||
|     alert("This browser does not support desktop notification"); |     var key_prefixed = "data-" + key; | ||||||
|   } else if (Notification.permission === "granted") { |     var elements = document.getElementsByClassName(key_prefixed); | ||||||
|     const notification = new Notification("Hi there!"); |  | ||||||
|     // … |     if (elements.length > 0) { | ||||||
|   } else if (Notification.permission !== "denied") { |       for (var i = 0; i < elements.length; i++) { | ||||||
|     Notification.requestPermission().then((permission) => { |         var element = elements[i]; | ||||||
|       if (permission === "granted") { |  | ||||||
|         const notification = new Notification("Hi there!"); |         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 { | ||||||
|  |           // Standardmäßig für Textfelder und andere Elemente | ||||||
|  |           element.value = dataset[key]; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }); |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 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 +1 @@ | |||||||
| 1.04 | 1.05 | ||||||
| @@ -3,14 +3,18 @@ | |||||||
| #define _COMMON_H_ | #define _COMMON_H_ | ||||||
|  |  | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
|  | #include <stddef.h> | ||||||
|  |  | ||||||
| #define Q(x) #x | #define Q(x) #x | ||||||
| #define QUOTE(x) Q(x) | #define QUOTE(x) Q(x) | ||||||
|  | #define SET_BIT(value, bitPosition) ((value) |= (1U << (bitPosition))) | ||||||
|  |  | ||||||
| #define TRUE 1 | #define TRUE 1 | ||||||
| #define FALSE 0 | #define FALSE 0 | ||||||
|  |  | ||||||
|  | #ifndef HOST_NAME | ||||||
| #define HOST_NAME "AirsoftTimer_%08X" | #define HOST_NAME "AirsoftTimer_%08X" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #define SHUTDOWN_DELAY_MS 5000 | #define SHUTDOWN_DELAY_MS 5000 | ||||||
| #define STARTUP_DELAY_MS 20000 | #define STARTUP_DELAY_MS 20000 | ||||||
| @@ -44,6 +48,17 @@ | |||||||
| #define OTA_DELAY 50 // ticks -> 10ms / tick | #define OTA_DELAY 50 // ticks -> 10ms / tick | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | typedef enum eSystem_Status | ||||||
|  | { | ||||||
|  |   sysStat_Startup, | ||||||
|  |   sysStat_Normal, | ||||||
|  |   sysStat_Rain, | ||||||
|  |   sysStat_Purge, | ||||||
|  |   sysStat_Error, | ||||||
|  |   sysStat_Shutdown | ||||||
|  | } tSystem_Status; | ||||||
|  |  | ||||||
|  | #define STARTUP_DELAY 2500 | ||||||
|  | #define SHUTDOWN_DELAY_MS 2500 | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -1,9 +1,21 @@ | |||||||
|  | /** | ||||||
|  |  * @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_ | #ifndef _DEBUGGER_H_ | ||||||
| #define _DEBUGGER_H_ | #define _DEBUGGER_H_ | ||||||
|  |  | ||||||
| #include <Arduino.h> | #include <Arduino.h> | ||||||
| #include "webui.h" | #include "webui.h" | ||||||
|  |  | ||||||
| const char PROGMEM helpCmd[] = "sysinfo     - System Info\n" | const char PROGMEM helpCmd[] = "sysinfo     - System Info\n" | ||||||
|                                "netinfo     - WiFi Info\n" |                                "netinfo     - WiFi Info\n" | ||||||
|                                "formatPDS   - Format Persistence EEPROM Data\n" |                                "formatPDS   - Format Persistence EEPROM Data\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_ | #ifndef _DTC_H_ | ||||||
| #define _DTC_H_ | #define _DTC_H_ | ||||||
|  |  | ||||||
| #include <Arduino.h> | #include <Arduino.h> | ||||||
|  | #include "dtc_defs.h" | ||||||
|  |  | ||||||
| #define MAX_DTC_STORAGE 12 | #define MAX_DTC_STORAGE 12 | ||||||
|  |  | ||||||
| typedef enum DTCNums_e | typedef struct | ||||||
| { | { | ||||||
|   DTC_NO_EEPROM_FOUND = 1, |   DTCNum_t Number; | ||||||
|   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; |  | ||||||
|   uint32_t timestamp; |   uint32_t timestamp; | ||||||
|   DTCActive_t active; |   DTCActive_t active; | ||||||
|   DTCSeverity_t severity; |  | ||||||
|   uint32_t debugVal; |   uint32_t debugVal; | ||||||
| } DTCEntry_t; | } DTCEntry_t; | ||||||
|  |  | ||||||
| void MaintainDTC(DTCNums_t DTC_no, DTCSeverity_t DTC_severity, boolean active, uint32_t DebugValue = 0); | void MaintainDTC(DTCNum_t DTC_no, boolean active, uint32_t DebugValue = 0); | ||||||
| void ClearDTC(DTCNums_t DTC_no); | void ClearDTC(DTCNum_t DTC_no); | ||||||
| void ClearAllDTC(); | void ClearAllDTC(); | ||||||
| DTCNums_t getlastDTC(boolean only_active); | DTCNum_t getlastDTC(boolean only_active); | ||||||
| DTCNums_t getlastDTC_Severity(boolean only_active, DTCSeverity_t severity); | DTCNum_t ActiveDTCseverity(DTCSeverity_t severity); | ||||||
|  | DTCSeverity_t getSeverityForDTC(DTCNum_t targetCode); | ||||||
| void DTC_Process(); | void DTC_Process(); | ||||||
|  |  | ||||||
| extern DTCEntry_s DTCStorage[MAX_DTC_STORAGE]; | extern DTCEntry_t DTCStorage[MAX_DTC_STORAGE]; | ||||||
| #endif | #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,38 +1,62 @@ | |||||||
|  | /** | ||||||
|  |  * @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_ | #ifndef _EEPROM_H_ | ||||||
| #define _EEPROM_H_ | #define _EEPROM_H_ | ||||||
|  |  | ||||||
| #include <Arduino.h> | #include <Arduino.h> | ||||||
| #include <Wire.h> | #include <Wire.h> | ||||||
| #include <I2C_eeprom.h> | #include <I2C_eeprom.h> | ||||||
|  |  | ||||||
| #include "globals.h" |  | ||||||
| #include "dtc.h" | #include "dtc.h" | ||||||
| #include "common.h" | #include "common.h" | ||||||
| #include "debugger.h" |  | ||||||
|  |  | ||||||
| #define I2C_EE_ADDRESS 0x50 | #define I2C_EE_ADDRESS 0x50 | ||||||
| #define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC64 | #define EEPROM_STRUCTURE_REVISION 4 // Increment this version when changing EEPROM structures | ||||||
| #define EEPROM_ENDURANCE 1000000 | #define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC256 | ||||||
|  |  | ||||||
| typedef enum | typedef enum Factions_e | ||||||
| { | { | ||||||
|   NONE, |   NONE, | ||||||
|   FACTION_1, |   FACTION_1, | ||||||
|   FACTION_2, |   FACTION_2, | ||||||
|   FACTION_3 |   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 | typedef struct | ||||||
| { | { | ||||||
|   uint32_t writeCycleCounter = 0; |   uint32_t writeCycleCounter; | ||||||
|   uint32_t faction_1_timer = 0; |   uint32_t faction_1_timer; | ||||||
|   uint32_t faction_2_timer = 0; |   uint32_t faction_2_timer; | ||||||
|   uint32_t faction_3_timer = 0; |   uint32_t faction_3_timer; | ||||||
|   factions_t activeFaction = NONE; |   Factions_t activeFaction; | ||||||
|   uint32_t checksum = 0; |   uint32_t checksum; | ||||||
| } persistenceData_t; | } persistenceData_t; | ||||||
|  |  | ||||||
| extern persistenceData_t PersistenceData; |  | ||||||
| typedef enum | typedef enum | ||||||
| { | { | ||||||
|   BATTERY_UNDEFINED, |   BATTERY_UNDEFINED, | ||||||
| @@ -47,6 +71,7 @@ const char BatteryString[][10]{ | |||||||
|  |  | ||||||
| const size_t BatteryString_Elements = sizeof(BatteryString) / sizeof(BatteryString[0]); | const size_t BatteryString_Elements = sizeof(BatteryString) / sizeof(BatteryString[0]); | ||||||
|  |  | ||||||
|  | // Structure for configuration settings stored in EEPROM | ||||||
| typedef struct | typedef struct | ||||||
| { | { | ||||||
|   uint8_t EEPROM_Version; |   uint8_t EEPROM_Version; | ||||||
| @@ -55,24 +80,30 @@ typedef struct | |||||||
|   char Faction_1_Name[33]; |   char Faction_1_Name[33]; | ||||||
|   char Faction_2_Name[33]; |   char Faction_2_Name[33]; | ||||||
|   char Faction_3_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; |   uint32_t checksum; | ||||||
| } configData_t; | } configData_t; | ||||||
|  |  | ||||||
| extern configData_t ConfigData; | // Default configuration settings | ||||||
|  |  | ||||||
| const configData_t ConfigData_defaults = { | const configData_t ConfigData_defaults = { | ||||||
|     2,                      // EEPROM_Version (incerease this if anything on Layout changes!) |     2,               // EEPROM_Version (incerease this if anything on Layout changes!) | ||||||
|     BATTERY_LIPO_3S,        // batteryType |     BATTERY_LIPO_3S, // batteryType | ||||||
|     false,                  // active_faction_on_reboot |     false,           // active_faction_on_reboot | ||||||
|     "FACTION 1",            // Faction_1_Name |     "FACTION 1",     // Faction_1_Name | ||||||
|     "FACTION 2",            // Faction_2_Name |     "FACTION 2",     // Faction_2_Name | ||||||
|     "FACTION 3",            // Faction_3_Name |     "FACTION 3",     // Faction_3_Name | ||||||
|     0                       // checksum |     "ArisoftTimer", | ||||||
|  |     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(); | void InitEEPROM(); | ||||||
| void EEPROM_Process(); | void EEPROM_Process(); | ||||||
| void StoreConfig_EEPROM(); | void StoreConfig_EEPROM(); | ||||||
| @@ -85,5 +116,9 @@ uint32_t Checksum_EEPROM(uint8_t const *data, size_t len); | |||||||
| void dumpEEPROM(uint16_t memoryAddress, uint16_t length); | void dumpEEPROM(uint16_t memoryAddress, uint16_t length); | ||||||
| void MovePersistencePage_EEPROM(boolean reset); | void MovePersistencePage_EEPROM(boolean reset); | ||||||
| uint32_t ConfigSanityCheck(bool autocorrect = false); | uint32_t ConfigSanityCheck(bool autocorrect = false); | ||||||
|  | bool validateWiFiString(char *string, size_t size); | ||||||
|  |  | ||||||
| #endif // _EEPROM_H_ | extern configData_t ConfigData; | ||||||
|  | extern persistenceData_t PersistenceData; | ||||||
|  | extern uint16_t eePersistenceMarker; | ||||||
|  | #endif // _CONFIG_H_ | ||||||
|   | |||||||
| @@ -2,68 +2,43 @@ | |||||||
| #define _GLOBALS_H_ | #define _GLOBALS_H_ | ||||||
|  |  | ||||||
| #include <Arduino.h> | #include <Arduino.h> | ||||||
|  | #include "eeprom.h" | ||||||
| typedef enum eSystem_Status | #include "common.h" | ||||||
| { |  | ||||||
|   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; |  | ||||||
|  |  | ||||||
| typedef struct Globals_s | typedef struct Globals_s | ||||||
| { | { | ||||||
|   char DeviceName[33]; |   tSystem_Status systemStatus = sysStat_Startup; /**< Current system status */ | ||||||
|   char DeviceName_ID[43]; |   tSystem_Status resumeStatus = sysStat_Startup; /**< Status to resume after rain mode */ | ||||||
|   char FlashVersion[10]; |   char systemStatustxt[16] = "";                 /**< Text representation of system status */ | ||||||
|   tSystem_Status systemStatus = sysStat_Startup; |   EERequest_t requestEEAction = EE_IDLE;         /**< EEPROM-related request */ | ||||||
|   eEERequest requestEEAction = EE_IDLE; |   char DeviceName[33];                           /**< Device name */ | ||||||
|   uint16_t eePersistanceAdress; |   char FlashVersion[10];                         /**< Flash version */ | ||||||
|  |   uint16_t eePersistanceAdress;                  /**< EEPROM persistence address */ | ||||||
|   bool hasDTC; |   bool hasDTC; | ||||||
|   int loadvoltage_mV = 0; |   int loadvoltage_mV = 0; | ||||||
|   int battery_level = 0; |   int battery_level = 0; | ||||||
|   bool timer_disabled = false; |   bool timer_disabled = false; | ||||||
| } Globals_t; | } Globals_t; | ||||||
|  |  | ||||||
| extern Globals_t globals; | extern Globals_t globals; /**< Global variable struct */ | ||||||
|  |  | ||||||
| typedef struct Constants_s | typedef struct Constants_s | ||||||
| { | { | ||||||
|   uint8_t FW_Version_major; |   uint8_t FW_Version_major;             /**< Firmware version major number */ | ||||||
|   uint8_t FW_Version_minor; |   uint8_t FW_Version_minor;             /**< Firmware version minor number */ | ||||||
|   uint8_t Required_Flash_Version_major; |   uint8_t Required_Flash_Version_major; /**< Required flash version major number */ | ||||||
|   uint8_t Required_Flash_Version_minor; |   uint8_t Required_Flash_Version_minor; /**< Required flash version minor number */ | ||||||
|   char GitHash[11]; |   char GitHash[11];                     /**< Git hash string */ | ||||||
| } Constants_t; | } Constants_t; | ||||||
|  |  | ||||||
| const Constants_t constants PROGMEM = { | const Constants_t constants PROGMEM = { | ||||||
|  1,4,     // Firmware_Version |     1, 5,   // Firmware_Version | ||||||
|  1,4,     // Required Flash Version |     1, 5,   // Required Flash Version | ||||||
|  GIT_REV  // Git-Hash-String |     GIT_REV // Git-Hash-String | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Initializes global variables. | ||||||
|  |  */ | ||||||
| void initGlobals(); | 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 |  | ||||||
							
								
								
									
										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 | ||||||
| @@ -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_ | #ifndef _WEBUI_H_ | ||||||
| #define _WEBUI_H_ | #define _WEBUI_H_ | ||||||
|  |  | ||||||
| @@ -11,14 +24,26 @@ | |||||||
| #include <AsyncJson.h> | #include <AsyncJson.h> | ||||||
| #include <ArduinoJson.h> | #include <ArduinoJson.h> | ||||||
|  |  | ||||||
| #include "eeprom.h" | #include "config.h" | ||||||
| #include "globals.h" | #include "globals.h" | ||||||
| #include "dtc.h" | #include "dtc.h" | ||||||
| #include "common.h" | #include "common.h" | ||||||
| #include "debugger.h" | #include "debugger.h" | ||||||
|  | #include "struct2json.h" | ||||||
|  |  | ||||||
|  | typedef enum | ||||||
|  | { | ||||||
|  |     info, | ||||||
|  |     success, | ||||||
|  |     warning, | ||||||
|  |     error | ||||||
|  | } NotificationType_t; | ||||||
|  |  | ||||||
| void initWebUI(); | void initWebUI(); | ||||||
| void Webserver_Process(); | 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_ | ||||||
|   | |||||||
| @@ -25,11 +25,10 @@ upload_speed = 921600 | |||||||
| ;   --auth=${wifi_cred.ota_password} | ;   --auth=${wifi_cred.ota_password} | ||||||
|  |  | ||||||
| build_flags= | build_flags= | ||||||
|   !python git_rev_macro.py |   !python codegen/git_rev_macro.py | ||||||
|   -DATOMIC_FS_UPDATE |   -DATOMIC_FS_UPDATE | ||||||
|   ;-DFEATURE_ENABLE_WIFI_CLIENT |   -DFEATURE_ENABLE_WIFI_CLIENT | ||||||
|   ;-DFEATURE_ENABLE_LORA |   ;-DFEATURE_ENABLE_LORA | ||||||
|   ;-DCAPTIVE |  | ||||||
|   -DFEATURE_ENABLE_UARTLORA |   -DFEATURE_ENABLE_UARTLORA | ||||||
|   -DWIFI_AP_IP_GW=10,0,0,1 |   -DWIFI_AP_IP_GW=10,0,0,1 | ||||||
|   -DADMIN_PASSWORD=${wifi_cred.ota_password} |   -DADMIN_PASSWORD=${wifi_cred.ota_password} | ||||||
| @@ -43,18 +42,22 @@ build_flags= | |||||||
|  |  | ||||||
| board_build.filesystem = littlefs | board_build.filesystem = littlefs | ||||||
| board_build.ldscript = eagle.flash.4m1m.ld | 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_filters = esp8266_exception_decoder | ||||||
| monitor_speed = 115200 | monitor_speed = 115200 | ||||||
|  |  | ||||||
|  | lib_ldf_mode = deep | ||||||
| lib_deps =  | lib_deps =  | ||||||
| 	;xreef/EByte LoRa E220 library@^1.0.6   ; made Lib local, due to changes for I2C-controller M0,M1-Pins | 	;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 | 	sstaub/Ticker@^4.4.0 | ||||||
|   robtillaart/PCF8574 @ ^0.3.7 |   robtillaart/PCF8574 @ ^0.3.7 | ||||||
| 	adafruit/Adafruit INA219 @ ^1.1.1 | 	adafruit/Adafruit INA219 @ ^1.1.1 | ||||||
|   akj7/TM1637 Driver @ ^2.1.2 |   akj7/TM1637 Driver @ ^2.1.2 | ||||||
|   me-no-dev/ESPAsyncTCP @ ^1.2.2 |   sstaub/Ticker @ ^4.4.0 | ||||||
|   robtillaart/I2C_EEPROM @ ^1.5.2 |   robtillaart/I2C_EEPROM @ ^1.8.2 | ||||||
|  |   esphome/ESPAsyncWebServer-esphome @ ^3.2.2 | ||||||
|   sandeepmistry/LoRa @ ^0.8.0 |   sandeepmistry/LoRa @ ^0.8.0 | ||||||
|   bblanchon/ArduinoJson @ ^6.19.4 |   bblanchon/ArduinoJson @ ^7.0.4 | ||||||
| @@ -1,52 +1,79 @@ | |||||||
|  | /** | ||||||
|  |  * @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" | #include "debugger.h" | ||||||
|  |  | ||||||
| DebugStatus_t DebuggerStatus[dbg_cntElements]; | DebugStatus_t DebuggerStatus[dbg_cntElements]; | ||||||
|  |  | ||||||
| String IpAddress2String(const IPAddress &ipAddress); |  | ||||||
| void processCmdDebug(String command); | void processCmdDebug(String command); | ||||||
| void Debug_formatCFG(); | void Debug_formatCFG(); | ||||||
| void Debug_formatPersistence(); | void Debug_formatPersistence(); | ||||||
| void Debug_printSystemInfo(); | void Debug_printSystemInfo(); | ||||||
| void Debug_printWifiInfo(); | void Debug_printWifiInfo(); | ||||||
| void Debug_CheckEEPOM(); | void Debug_CheckEEPOM(bool autocorrect); | ||||||
| void Debug_dumpConfig(); | void Debug_dumpConfig(); | ||||||
| void Debug_dumpPersistance(); | void Debug_dumpPersistance(); | ||||||
| void Debug_ShowDTCs(); | void Debug_ShowDTCs(); | ||||||
| void Debug_dumpGlobals(); | void Debug_dumpGlobals(); | ||||||
| void Debug_printHelp(); | void Debug_printHelp(); | ||||||
|  | const char *uint32_to_binary_string(uint32_t num); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Initializes the debugger by setting the initial status for different debug ports. | ||||||
|  |  *        Serial debug output is turned off. | ||||||
|  |  */ | ||||||
| void initDebugger() | void initDebugger() | ||||||
| { | { | ||||||
|  |     // Set the initial status of debug ports | ||||||
|     DebuggerStatus[dbg_Serial] = disabled; |     DebuggerStatus[dbg_Serial] = disabled; | ||||||
|     DebuggerStatus[dbg_Webui] = disabled; |     DebuggerStatus[dbg_Webui] = disabled; | ||||||
|  |  | ||||||
|  |     // Disable serial debug output | ||||||
|     Serial.setDebugOutput(false); |     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() | void Debug_Process() | ||||||
| { | { | ||||||
|  |     // Enumeration for tracking the state of input processing | ||||||
|     typedef enum InputProcessed_e |     typedef enum InputProcessed_e | ||||||
|     { |     { | ||||||
|         IDLE, |         IDLE,         ///< No command processing is in progress | ||||||
|         CMD_COMPLETE, |         CMD_COMPLETE, ///< Received a complete command | ||||||
|         CMD_ABORT, |         CMD_ABORT,    ///< Received an abort command (Esc) | ||||||
|         CMD_OVERFLOW |         CMD_OVERFLOW  ///< Input buffer overflow occurred | ||||||
|     } InputProcessed_t; |     } InputProcessed_t; | ||||||
|  |  | ||||||
|     static unsigned int inputCnt = 0; |     static unsigned int inputCnt = 0;       ///< Counter for characters in the input buffer | ||||||
|     static char inputBuffer[32]; |     static char inputBuffer[32];            ///< Buffer to store the received characters | ||||||
|     InputProcessed_t InputProcessed = IDLE; |     InputProcessed_t InputProcessed = IDLE; ///< State variable for input processing | ||||||
|  |  | ||||||
|  |     // Check if there are characters available in the Serial input buffer | ||||||
|     if (Serial.available()) |     if (Serial.available()) | ||||||
|     { |     { | ||||||
|         char inputChar = Serial.read(); |         char inputChar = Serial.read(); | ||||||
|  |  | ||||||
|  |         // Process the received character based on its value | ||||||
|         switch (inputChar) |         switch (inputChar) | ||||||
|         { |         { | ||||||
|         case '\n': |         case '\n': | ||||||
|             inputBuffer[inputCnt] = 0; // terminate the String |             inputBuffer[inputCnt] = 0; // terminate the String | ||||||
|             inputCnt = 0; |             inputCnt = 0; | ||||||
|             InputProcessed = CMD_COMPLETE; |             InputProcessed = CMD_COMPLETE; | ||||||
|  |             Serial.write(inputChar); | ||||||
|             break; |             break; | ||||||
|  |  | ||||||
|         case 0x1B: // Esc |         case 0x1B: // Esc | ||||||
| @@ -55,15 +82,17 @@ void Debug_Process() | |||||||
|             InputProcessed = CMD_ABORT; |             InputProcessed = CMD_ABORT; | ||||||
|             break; |             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; |             inputBuffer[inputCnt] = inputChar; | ||||||
|             inputCnt++; |             inputCnt++; | ||||||
|  |             Serial.write(inputChar); | ||||||
|             break; |             break; | ||||||
|  |  | ||||||
|         default: |         default: | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // Check for input buffer overflow | ||||||
|         if (inputCnt > sizeof(inputBuffer)) |         if (inputCnt > sizeof(inputBuffer)) | ||||||
|         { |         { | ||||||
|             inputCnt = 0; |             inputCnt = 0; | ||||||
| @@ -72,6 +101,7 @@ void Debug_Process() | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Process the command based on the detected state of input processing | ||||||
|     switch (InputProcessed) |     switch (InputProcessed) | ||||||
|     { |     { | ||||||
|     case CMD_ABORT: |     case CMD_ABORT: | ||||||
| @@ -83,40 +113,65 @@ void Debug_Process() | |||||||
|         break; |         break; | ||||||
|  |  | ||||||
|     case CMD_OVERFLOW: |     case CMD_OVERFLOW: | ||||||
|         Debug_pushMessage("input Buffer overflow\n"); |         Debug_pushMessage("Input buffer overflow\n"); | ||||||
|         break; |         break; | ||||||
|  |  | ||||||
|     default: |     default: | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (InputProcessed != IDLE) | ||||||
|  |         Serial.print(">"); | ||||||
|  |  | ||||||
|     InputProcessed = IDLE; |     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) | void SetDebugportStatus(DebugPorts_t port, DebugStatus_t status) | ||||||
| { | { | ||||||
|  |     // Display a debug message based on the provided status | ||||||
|     if (status == disabled) |     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; |     DebuggerStatus[port] = status; | ||||||
|  |  | ||||||
|  |     // Display a debug message based on the updated status | ||||||
|     if (status == enabled) |     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, ...) | 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)) |     if ((DebuggerStatus[dbg_Serial] == enabled) || (DebuggerStatus[dbg_Webui] == enabled)) | ||||||
|     { |     { | ||||||
|         char buff[64]; |         char buff[128]; // Buffer to hold the formatted message | ||||||
|         va_list arg; |         va_list arg;    // Variable argument list for vsnprintf | ||||||
|         va_start(arg, format); |         va_start(arg, format); | ||||||
|  |  | ||||||
|  |         // Format the message and store it in the buffer | ||||||
|         vsnprintf(buff, sizeof(buff), format, arg); |         vsnprintf(buff, sizeof(buff), format, arg); | ||||||
|         va_end(arg); |         va_end(arg); | ||||||
|  |  | ||||||
|  |         // Send the message to the Serial debug port if enabled | ||||||
|         if (DebuggerStatus[dbg_Serial] == enabled) |         if (DebuggerStatus[dbg_Serial] == enabled) | ||||||
|         { |         { | ||||||
|             Serial.print(buff); |             Serial.print(buff); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // Push the message to the WebUI debug port if enabled | ||||||
|         if (DebuggerStatus[dbg_Webui] == enabled) |         if (DebuggerStatus[dbg_Webui] == enabled) | ||||||
|         { |         { | ||||||
|             Websocket_PushLiveDebug(String(buff)); |             Websocket_PushLiveDebug(String(buff)); | ||||||
| @@ -124,8 +179,15 @@ void Debug_pushMessage(const char *format, ...) | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Processes a debug command and performs corresponding actions. | ||||||
|  |  * | ||||||
|  |  * @param command The debug command to be processed. | ||||||
|  |  */ | ||||||
| void processCmdDebug(String command) | void processCmdDebug(String command) | ||||||
| { | { | ||||||
|  |     // Check the received command and execute corresponding actions | ||||||
|     if (command == "help") |     if (command == "help") | ||||||
|         Debug_printHelp(); |         Debug_printHelp(); | ||||||
|     else if (command == "sysinfo") |     else if (command == "sysinfo") | ||||||
| @@ -137,7 +199,9 @@ void processCmdDebug(String command) | |||||||
|     else if (command == "formatPDS") |     else if (command == "formatPDS") | ||||||
|         Debug_formatPersistence(); |         Debug_formatPersistence(); | ||||||
|     else if (command == "checkEE") |     else if (command == "checkEE") | ||||||
|         Debug_CheckEEPOM(); |         Debug_CheckEEPOM(false); | ||||||
|  |     else if (command == "checkEEfix") | ||||||
|  |         Debug_CheckEEPOM(true); | ||||||
|     else if (command == "dumpEE1k") |     else if (command == "dumpEE1k") | ||||||
|         dumpEEPROM(0, 1024); |         dumpEEPROM(0, 1024); | ||||||
|     else if (command == "dumpEE") |     else if (command == "dumpEE") | ||||||
| @@ -150,28 +214,55 @@ void processCmdDebug(String command) | |||||||
|         Debug_dumpPersistance(); |         Debug_dumpPersistance(); | ||||||
|     else if (command == "saveEE") |     else if (command == "saveEE") | ||||||
|         globals.requestEEAction = EE_ALL_SAVE; |         globals.requestEEAction = EE_ALL_SAVE; | ||||||
|     else if (command == "showdtc") |  | ||||||
|         Debug_ShowDTCs(); |  | ||||||
|     else if (command == "dumpGlobals") |     else if (command == "dumpGlobals") | ||||||
|         Debug_dumpGlobals(); |         Debug_dumpGlobals(); | ||||||
|     else if (command == "sdbg") |     else if (command == "sdbg") | ||||||
|         SetDebugportStatus(dbg_Serial, enabled); |         SetDebugportStatus(dbg_Serial, enabled); | ||||||
|  |     else if (command == "dtc_show") | ||||||
|  |         Debug_ShowDTCs(); | ||||||
|  |     else if (command == "dtc_clear") | ||||||
|  |         ClearAllDTC(); | ||||||
|  |     else if (command == "dtc_crit") | ||||||
|  |         MaintainDTC(DTC_FAKE_DTC_CRIT, true, millis()); | ||||||
|  |     else if (command == "dtc_warn") | ||||||
|  |         MaintainDTC(DTC_FAKE_DTC_WARN, true, millis()); | ||||||
|  |     else if (command == "dtc_info") | ||||||
|  |         MaintainDTC(DTC_FAKE_DTC_INFO, true, millis()); | ||||||
|  |     else if (command == "notify_error") | ||||||
|  |         Websocket_PushNotification("Debug Error Notification", error); | ||||||
|  |     else if (command == "notify_warning") | ||||||
|  |         Websocket_PushNotification("Debug Warning Notification", warning); | ||||||
|  |     else if (command == "notify_success") | ||||||
|  |         Websocket_PushNotification("Debug Success Notification", success); | ||||||
|  |     else if (command == "notify_info") | ||||||
|  |         Websocket_PushNotification("Debug Info Notification", info); | ||||||
|     else |     else | ||||||
|         Debug_pushMessage("unknown Command\n"); |         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() | 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(); |     FormatConfig_EEPROM(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Formats the Persistence-EEPROM and resets it to default values. | ||||||
|  |  *        Prints a debug message after formatting. | ||||||
|  |  */ | ||||||
| void Debug_formatPersistence() | 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(); |     FormatPersistence_EEPROM(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Prints system information and status to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_printSystemInfo() | void Debug_printSystemInfo() | ||||||
| { | { | ||||||
|     Debug_pushMessage("Hostname: %s\n", globals.DeviceName); |     Debug_pushMessage("Hostname: %s\n", globals.DeviceName); | ||||||
| @@ -190,10 +281,13 @@ void Debug_printSystemInfo() | |||||||
|                                                                         : ideMode == FM_DOUT   ? "DOUT" |                                                                         : ideMode == FM_DOUT   ? "DOUT" | ||||||
|                                                                                                : "UNKNOWN")); |                                                                                                : "UNKNOWN")); | ||||||
|     Debug_pushMessage("OTA-Pass: %s\n", QUOTE(ADMIN_PASSWORD)); |     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("Sw-Version: %d.%02d\n", constants.FW_Version_major, constants.FW_Version_minor); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Dumps the current configuration parameters to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_dumpConfig() | void Debug_dumpConfig() | ||||||
| { | { | ||||||
|     Debug_pushMessage("batteryType: %d\n", ConfigData.batteryType); |     Debug_pushMessage("batteryType: %d\n", ConfigData.batteryType); | ||||||
| @@ -201,6 +295,9 @@ void Debug_dumpConfig() | |||||||
|     Debug_pushMessage("checksum: 0x%08X\n", ConfigData.checksum); |     Debug_pushMessage("checksum: 0x%08X\n", ConfigData.checksum); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Dumps the global variables and their values to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_dumpGlobals() | void Debug_dumpGlobals() | ||||||
| { | { | ||||||
|     Debug_pushMessage("systemStatus: %d\n", globals.systemStatus); |     Debug_pushMessage("systemStatus: %d\n", globals.systemStatus); | ||||||
| @@ -208,12 +305,14 @@ void Debug_dumpGlobals() | |||||||
|     Debug_pushMessage("loadvoltage_mV: %d\n", globals.loadvoltage_mV); |     Debug_pushMessage("loadvoltage_mV: %d\n", globals.loadvoltage_mV); | ||||||
|     Debug_pushMessage("requestEEAction: %d\n", globals.requestEEAction); |     Debug_pushMessage("requestEEAction: %d\n", globals.requestEEAction); | ||||||
|     Debug_pushMessage("DeviceName: %s\n", globals.DeviceName); |     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("FlashVersion: %s\n", globals.FlashVersion); | ||||||
|     Debug_pushMessage("eePersistanceAdress: %d\n", globals.eePersistanceAdress); |     Debug_pushMessage("eePersistanceAdress: %d\n", globals.eePersistanceAdress); | ||||||
|     Debug_pushMessage("hasDTC: %d\n", globals.hasDTC); |     Debug_pushMessage("hasDTC: %d\n", globals.hasDTC); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Dumps the persistence data variables and their values to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_dumpPersistance() | void Debug_dumpPersistance() | ||||||
| { | { | ||||||
|     Debug_pushMessage("writeCycleCounter: %d\n", PersistenceData.writeCycleCounter); |     Debug_pushMessage("writeCycleCounter: %d\n", PersistenceData.writeCycleCounter); | ||||||
| @@ -225,12 +324,21 @@ void Debug_dumpPersistance() | |||||||
|     Debug_pushMessage("PSD Adress: 0x%04X\n", globals.eePersistanceAdress); |     Debug_pushMessage("PSD Adress: 0x%04X\n", globals.eePersistanceAdress); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Prints information related to WiFi to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_printWifiInfo() | 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; |     uint32_t checksum = PersistenceData.checksum; | ||||||
|     PersistenceData.checksum = 0; |     PersistenceData.checksum = 0; | ||||||
|  |  | ||||||
| @@ -245,6 +353,7 @@ void Debug_CheckEEPOM() | |||||||
|  |  | ||||||
|     PersistenceData.checksum = checksum; |     PersistenceData.checksum = checksum; | ||||||
|  |  | ||||||
|  |     // Check ConfigData EEPROM checksum | ||||||
|     checksum = ConfigData.checksum; |     checksum = ConfigData.checksum; | ||||||
|     ConfigData.checksum = 0; |     ConfigData.checksum = 0; | ||||||
|  |  | ||||||
| @@ -257,26 +366,45 @@ void Debug_CheckEEPOM() | |||||||
|         Debug_pushMessage("ConfigData EEPROM Checksum BAD\n"); |         Debug_pushMessage("ConfigData EEPROM Checksum BAD\n"); | ||||||
|     } |     } | ||||||
|     ConfigData.checksum = checksum; |     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() | void Debug_ShowDTCs() | ||||||
| { | { | ||||||
|     char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx |     char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx | ||||||
|     char buff_active[9]; |     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 | severity\n"); | ||||||
|  |  | ||||||
|  |     // Iterate through DTCStorage and display each entry | ||||||
|     for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) |     for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|     { |     { | ||||||
|         if (DTCStorage[i].Number < DTC_LAST_DTC) |         if (DTCStorage[i].Number < DTC_LAST_DTC) | ||||||
|         { |         { | ||||||
|  |             // Format timestamp | ||||||
|             sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d", |             sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d", | ||||||
|                     DTCStorage[i].timestamp / 86400000,    // Days |                     DTCStorage[i].timestamp / 86400000,    // Days | ||||||
|                     DTCStorage[i].timestamp / 360000 % 24, // Hours |                     DTCStorage[i].timestamp / 360000 % 24, // Hours | ||||||
|                     DTCStorage[i].timestamp / 60000 % 60,  // Minutes |                     DTCStorage[i].timestamp / 60000 % 60,  // Minutes | ||||||
|                     DTCStorage[i].timestamp / 1000 % 60,   // Seconds |                     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) |             if (DTCStorage[i].active == DTC_ACTIVE) | ||||||
|                 strcpy(buff_active, "active"); |                 strcpy(buff_active, "active"); | ||||||
|             else if (DTCStorage[i].active == DTC_PREVIOUS) |             else if (DTCStorage[i].active == DTC_PREVIOUS) | ||||||
| @@ -284,19 +412,54 @@ void Debug_ShowDTCs() | |||||||
|             else |             else | ||||||
|                 strcpy(buff_active, "none"); |                 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); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Displays the help commands for debugging through Serial or WebUI. | ||||||
|  |  *        Each command is printed individually in a formatted manner. | ||||||
|  |  */ | ||||||
| void Debug_printHelp() | void Debug_printHelp() | ||||||
| { | { | ||||||
|     char buff[64]; |     char buff[64]; | ||||||
|  |  | ||||||
|     for (unsigned int i = sizeof(helpCmd) / 63; i < sizeof(helpCmd) / 63; i++) |     // Iterate through helpCmd and display each command | ||||||
|  |     for (unsigned int i = 0; i < sizeof(helpCmd) / 63; i++) | ||||||
|     { |     { | ||||||
|  |         // Copy a portion of helpCmd to buff for display | ||||||
|         memcpy_P(buff, (helpCmd + (i * 63)), 63); |         memcpy_P(buff, (helpCmd + (i * 63)), 63); | ||||||
|         buff[63] = 0; |         buff[63] = 0; | ||||||
|  |  | ||||||
|  |         // Display the help command | ||||||
|         Debug_pushMessage(buff); |         Debug_pushMessage(buff); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @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 "dtc.h" | ||||||
| #include "debugger.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++) |     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 (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) |             if (active && DTCStorage[i].active != DTC_ACTIVE) | ||||||
|             { |             { | ||||||
|                 Debug_pushMessage("DTC gone active: %d, DebugVal: %d\n", DTC_no, DebugValue); |                 Debug_pushMessage("DTC gone active: %d, DebugVal: %d\n", DTC_no, DebugValue); | ||||||
|                 DTCStorage[i].timestamp = millis(); |                 DTCStorage[i].timestamp = millis(); | ||||||
|                 DTCStorage[i].active = DTC_ACTIVE; |                 DTCStorage[i].active = DTC_ACTIVE; | ||||||
|                 DTCStorage[i].severity = DTC_severity; |  | ||||||
|                 DTCStorage[i].debugVal = DebugValue; |                 DTCStorage[i].debugVal = DebugValue; | ||||||
|             } |             } | ||||||
|  |             // If the DTC is not active anymore, update its status to previous | ||||||
|             if (!active && DTCStorage[i].active == DTC_ACTIVE) |             if (!active && DTCStorage[i].active == DTC_ACTIVE) | ||||||
|             { |             { | ||||||
|                 Debug_pushMessage("DTC gone previous: %d\n", DTC_no); |                 Debug_pushMessage("DTC gone previous: %d\n", DTC_no); | ||||||
|                 DTCStorage[i].active = DTC_PREVIOUS; |                 DTCStorage[i].active = DTC_PREVIOUS; | ||||||
|             } |             } | ||||||
|             return; |             return; // DTC found and processed, exit the function | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // DTC was not found with upper iteration, but is active |     // DTC was not found in the existing storage, but it is active, | ||||||
|     // so we need to look for free space to store DTC |     // so look for free space to store the new DTC | ||||||
|     if (active == true) |     if (active == true) | ||||||
|     { |     { | ||||||
|         for (int i = 0; i < MAX_DTC_STORAGE; i++) |         for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|         { |         { | ||||||
|  |             // Check for an empty slot in the storage | ||||||
|             if (DTCStorage[i].Number == DTC_LAST_DTC) |             if (DTCStorage[i].Number == DTC_LAST_DTC) | ||||||
|             { |             { | ||||||
|                 Debug_pushMessage("new DTC registered: %d, DebugVal: %d\n", DTC_no, DebugValue); |                 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].timestamp = millis(); | ||||||
|                 DTCStorage[i].active = DTC_ACTIVE; |                 DTCStorage[i].active = DTC_ACTIVE; | ||||||
|                 DTCStorage[i].debugVal = DebugValue; |                 DTCStorage[i].debugVal = DebugValue; | ||||||
|                 DTCStorage[i].severity = DTC_severity; |                 return; // New DTC registered, exit the function | ||||||
|                 return; |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| 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++) |     for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|     { |     { | ||||||
|         if (DTCStorage[i].Number == DTC_no) |         if (DTCStorage[i].Number == DTC_no) | ||||||
|         { |         { | ||||||
|             DTCStorage[i].Number = DTC_LAST_DTC; |             DTCStorage[i].Number = DTC_LAST_DTC; | ||||||
|             DTCStorage[i].active = DTC_NONE; |             DTCStorage[i].active = DTC_INACTIVE; | ||||||
|             DTCStorage[i].timestamp = 0; |             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() | void ClearAllDTC() | ||||||
| { | { | ||||||
|     for (int i = 0; i < MAX_DTC_STORAGE; i++) |     for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|     { |     { | ||||||
|         DTCStorage[i].Number = DTC_LAST_DTC; |         DTCStorage[i].Number = DTC_LAST_DTC; | ||||||
|         DTCStorage[i].active = DTC_NONE; |         DTCStorage[i].active = DTC_INACTIVE; | ||||||
|         DTCStorage[i].timestamp = 0; |         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; |     int8_t pointer = -1; | ||||||
|     uint32_t lasttimestamp = 0; |     uint32_t lasttimestamp = 0; | ||||||
| @@ -89,34 +135,47 @@ DTCNums_t getlastDTC(boolean only_active) | |||||||
|     return pointer >= 0 ? DTCStorage[pointer].Number : DTC_LAST_DTC; |     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; |     for (int i = 0; i < DTC_LAST_DTC; i++) | ||||||
|     uint32_t lasttimestamp = 0; |  | ||||||
|  |  | ||||||
|     for (int i = 0; i < MAX_DTC_STORAGE; 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) |             return dtc_definitions[i].severity; | ||||||
|             { |  | ||||||
|                 pointer = i; |  | ||||||
|                 lasttimestamp = DTCStorage[i].timestamp; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     return DTC_NONE; | ||||||
|     return pointer >= 0 ? DTCStorage[pointer].Number : DTC_LAST_DTC; |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @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() | void DTC_Process() | ||||||
| { | { | ||||||
|     static tSystem_Status preserverSysStatusError; |     static tSystem_Status preserverSysStatusError; | ||||||
|  |     DTCNum_t lastDTC = getlastDTC(true); | ||||||
|  |  | ||||||
|     if (getlastDTC(false) < DTC_LAST_DTC) |     if (lastDTC < DTC_LAST_DTC) | ||||||
|     { |     { | ||||||
|         globals.hasDTC = true; |         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) |             if (globals.systemStatus != sysStat_Error) | ||||||
|             { |             { | ||||||
| @@ -124,16 +183,14 @@ void DTC_Process() | |||||||
|             } |             } | ||||||
|             globals.systemStatus = sysStat_Error; |             globals.systemStatus = sysStat_Error; | ||||||
|         } |         } | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             if (globals.systemStatus == sysStat_Error) |  | ||||||
|             { |  | ||||||
|                 globals.systemStatus = preserverSysStatusError; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|     else |     else | ||||||
|     { |     { | ||||||
|         globals.hasDTC = false; |         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,103 @@ | |||||||
| #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" | ||||||
|  |  | ||||||
|  | // Instance of I2C_eeprom for EEPROM access | ||||||
| I2C_eeprom ee(I2C_EE_ADDRESS, EEPROM_SIZE_BYTES); | I2C_eeprom ee(I2C_EE_ADDRESS, EEPROM_SIZE_BYTES); | ||||||
|  |  | ||||||
|  | // Configuration and persistence data structures | ||||||
| configData_t ConfigData; | configData_t ConfigData; | ||||||
| persistenceData_t PersistenceData; | persistenceData_t PersistenceData; | ||||||
| bool eeAvailable = false; |  | ||||||
|  |  | ||||||
| bool checkEEPROMavailable(); | // EEPROM version identifier | ||||||
| bool ValidateEEPROM_Version(); | const uint16_t eeVersion = EEPROM_STRUCTURE_REVISION;  | ||||||
| bool MigrateEEPROM(uint8_t fromVersion); |  | ||||||
|  |  | ||||||
|  | // 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. | ||||||
|  |  */ | ||||||
| void InitEEPROM() | void InitEEPROM() | ||||||
| { | { | ||||||
|  |   ConfigData = ConfigData_defaults; | ||||||
|  |   PersistenceData = {0}; | ||||||
|   ee.begin(); |   ee.begin(); | ||||||
|   eeAvailable = checkEEPROMavailable(); |   checkEEPROMavailable(); | ||||||
|   eeAvailable = ValidateEEPROM_Version(); |  | ||||||
| 	Serial.printf("Initialized EEPROM at Address 0x%02X\n", I2C_EE_ADDRESS); |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @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() | void EEPROM_Process() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |  | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   switch (globals.requestEEAction) |   switch (globals.requestEEAction) | ||||||
|   { |   { | ||||||
|   case EE_CFG_SAVE: |   case EE_CFG_SAVE: | ||||||
|     StoreConfig_EEPROM(); |     StoreConfig_EEPROM(); | ||||||
|     globals.requestEEAction = EE_IDLE; |     globals.requestEEAction = EE_IDLE; | ||||||
|     Serial.println("Stored EEPROM CFG"); |     Debug_pushMessage("Stored EEPROM CFG\n"); | ||||||
|     break; |     break; | ||||||
|   case EE_CFG_LOAD: |   case EE_CFG_LOAD: | ||||||
|     GetConfig_EEPROM(); |     GetConfig_EEPROM(); | ||||||
|     globals.requestEEAction = EE_IDLE; |     globals.requestEEAction = EE_IDLE; | ||||||
|     Serial.println("Loaded EEPROM CFG"); |     Debug_pushMessage("Loaded EEPROM CFG\n"); | ||||||
|     break; |     break; | ||||||
|   case EE_CFG_FORMAT: |   case EE_CFG_FORMAT: | ||||||
|     FormatConfig_EEPROM(); |     FormatConfig_EEPROM(); | ||||||
|     globals.requestEEAction = EE_IDLE; |     globals.requestEEAction = EE_IDLE; | ||||||
|     globals.systemStatus = sysStat_Shutdown; |     GetConfig_EEPROM(); | ||||||
|     Serial.println("Formated EEPROM CFG"); |     Debug_pushMessage("Formatted EEPROM CFG\n"); | ||||||
|     break; |     break; | ||||||
|   case EE_PDS_SAVE: |   case EE_PDS_SAVE: | ||||||
|     StorePersistence_EEPROM(); |     StorePersistence_EEPROM(); | ||||||
|     globals.requestEEAction = EE_IDLE; |     globals.requestEEAction = EE_IDLE; | ||||||
|     Serial.println("Stored EEPROM PDS"); |     Debug_pushMessage("Stored EEPROM PDS\n"); | ||||||
|     break; |     break; | ||||||
|   case EE_PDS_LOAD: |   case EE_PDS_LOAD: | ||||||
|     GetPersistence_EEPROM(); |     GetPersistence_EEPROM(); | ||||||
|     globals.requestEEAction = EE_IDLE; |     globals.requestEEAction = EE_IDLE; | ||||||
|     Serial.println("Loaded EEPROM PDS"); |     Debug_pushMessage("Loaded EEPROM PDS\n"); | ||||||
|     break; |     break; | ||||||
|   case EE_PDS_FORMAT: |   case EE_PDS_FORMAT: | ||||||
|     FormatPersistence_EEPROM(); |     FormatPersistence_EEPROM(); | ||||||
|     globals.requestEEAction = EE_IDLE; |     globals.requestEEAction = EE_IDLE; | ||||||
|     Serial.println("Formated EEPROM PDS"); |     GetPersistence_EEPROM(); | ||||||
|  |     Debug_pushMessage("Formatted EEPROM PDS\n"); | ||||||
|     break; |     break; | ||||||
|   case EE_FORMAT_ALL: |   case EE_FORMAT_ALL: | ||||||
|     FormatConfig_EEPROM(); |     FormatConfig_EEPROM(); | ||||||
|     FormatPersistence_EEPROM(); |     FormatPersistence_EEPROM(); | ||||||
|  |     GetConfig_EEPROM(); | ||||||
|  |     GetPersistence_EEPROM(); | ||||||
|     globals.requestEEAction = EE_IDLE; |     globals.requestEEAction = EE_IDLE; | ||||||
|     Serial.println("Formated EEPROM ALL"); |     Debug_pushMessage("Formatted EEPROM ALL\n"); | ||||||
|     break; |     break; | ||||||
|   case EE_ALL_SAVE: |   case EE_ALL_SAVE: | ||||||
|     StorePersistence_EEPROM(); |     StorePersistence_EEPROM(); | ||||||
|     StoreConfig_EEPROM(); |     StoreConfig_EEPROM(); | ||||||
|     globals.requestEEAction = EE_IDLE; |     globals.requestEEAction = EE_IDLE; | ||||||
|     Serial.println("Stored EEPROM ALL"); |     Debug_pushMessage("Stored EEPROM ALL\n"); | ||||||
|     break; |     break; | ||||||
|   case EE_IDLE: |   case EE_IDLE: | ||||||
|   default: |   default: | ||||||
| @@ -74,20 +105,39 @@ 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() | void StoreConfig_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |  | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   ConfigData.checksum = 0; |   ConfigData.checksum = 0; | ||||||
|   ConfigData.checksum = Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)); |   ConfigData.checksum = Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)); | ||||||
|  |  | ||||||
|  |   if (!checkEEPROMavailable()) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|   ee.updateBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); |   ee.updateBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); | ||||||
|  |  | ||||||
|  |   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() | void GetConfig_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   if (!checkEEPROMavailable()) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   ee.readBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); |   ee.readBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); | ||||||
| @@ -95,26 +145,24 @@ void GetConfig_EEPROM() | |||||||
|   uint32_t checksum = ConfigData.checksum; |   uint32_t checksum = ConfigData.checksum; | ||||||
|   ConfigData.checksum = 0; |   ConfigData.checksum = 0; | ||||||
|  |  | ||||||
|   if (Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)) != checksum) |   MaintainDTC(DTC_EEPROM_CFG_BAD, (Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)) != checksum)); | ||||||
|   { |  | ||||||
|     MaintainDTC(DTC_EEPROM_CFG_BAD, DTC_CRITICAL, true); |  | ||||||
|   } |  | ||||||
|   ConfigData.checksum = checksum; |   ConfigData.checksum = checksum; | ||||||
|  |  | ||||||
|   uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); |   uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); | ||||||
|  |  | ||||||
|   if (ConfigSanityCheckResult > 0) |   MaintainDTC(DTC_EEPROM_CFG_SANITY, (ConfigSanityCheckResult > 0), ConfigSanityCheckResult); | ||||||
|   { |  | ||||||
|     MaintainDTC(DTC_EEPROM_CFG_SANITY, DTC_WARN, true, ConfigSanityCheckResult); |  | ||||||
|     globals.requestEEAction = EE_CFG_SAVE; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @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() | void StorePersistence_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |  | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   if (PersistenceData.writeCycleCounter >= 0xFFF0) |   if (PersistenceData.writeCycleCounter >= 0xFFF0) | ||||||
|     MovePersistencePage_EEPROM(false); |     MovePersistencePage_EEPROM(false); | ||||||
|   else |   else | ||||||
| @@ -123,12 +171,22 @@ void StorePersistence_EEPROM() | |||||||
|   PersistenceData.checksum = 0; |   PersistenceData.checksum = 0; | ||||||
|   PersistenceData.checksum = Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)); |   PersistenceData.checksum = Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)); | ||||||
|  |  | ||||||
|  |   if (!checkEEPROMavailable()) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|   ee.updateBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData)); |   ee.updateBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @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() | void GetPersistence_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   if (!checkEEPROMavailable()) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   ee.readBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); |   ee.readBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); | ||||||
| @@ -138,7 +196,7 @@ void GetPersistence_EEPROM() | |||||||
|   { |   { | ||||||
|     MovePersistencePage_EEPROM(true); |     MovePersistencePage_EEPROM(true); | ||||||
|     FormatPersistence_EEPROM(); |     FormatPersistence_EEPROM(); | ||||||
|     MaintainDTC(DTC_EEPROM_PDSADRESS_BAD, DTC_CRITICAL, true); |     MaintainDTC(DTC_EEPROM_PDSADRESS_BAD, true); | ||||||
|   } |   } | ||||||
|   else |   else | ||||||
|   { |   { | ||||||
| @@ -147,45 +205,53 @@ void GetPersistence_EEPROM() | |||||||
|     uint32_t checksum = PersistenceData.checksum; |     uint32_t checksum = PersistenceData.checksum; | ||||||
|     PersistenceData.checksum = 0; |     PersistenceData.checksum = 0; | ||||||
|  |  | ||||||
|     if (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum) |     MaintainDTC(DTC_EEPROM_PDS_BAD, (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum)); | ||||||
|     { |  | ||||||
|       MaintainDTC(DTC_EEPROM_PDS_BAD, DTC_CRITICAL, true); |  | ||||||
|     } |  | ||||||
|     PersistenceData.checksum = 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() | void FormatConfig_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   Debug_pushMessage("Formatting Config-Partition\n"); | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   Serial.println("Formatting Config-Partition"); |  | ||||||
|   ConfigData = ConfigData_defaults; |   ConfigData = ConfigData_defaults; | ||||||
|  |   ConfigData.EEPROM_Version = eeVersion; | ||||||
|   StoreConfig_EEPROM(); |   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() | void FormatPersistence_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   Debug_pushMessage("Formatting Persistance-Partition\n"); | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   Serial.println("Formatting Persistance-Partition"); |  | ||||||
|   PersistenceData = {0}; |   PersistenceData = {0}; | ||||||
|  |   // memset(&PersistenceData, 0, sizeof(PersistenceData)); | ||||||
|   StorePersistence_EEPROM(); |   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) | void MovePersistencePage_EEPROM(boolean reset) | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   if (!checkEEPROMavailable()) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   globals.eePersistanceAdress = +sizeof(PersistenceData); |   globals.eePersistanceAdress += sizeof(PersistenceData); | ||||||
|   PersistenceData.writeCycleCounter = 0; |   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) |   if ((globals.eePersistanceAdress + sizeof(PersistenceData)) > ee.getDeviceSize() || reset) | ||||||
|   { |   { | ||||||
|     globals.eePersistanceAdress = startofPersistence; |     globals.eePersistanceAdress = startofPersistence; | ||||||
| @@ -194,165 +260,202 @@ void MovePersistencePage_EEPROM(boolean reset) | |||||||
|   ee.updateBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); |   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) | uint32_t Checksum_EEPROM(uint8_t const *data, size_t len) | ||||||
| { | { | ||||||
|   if (data == NULL) |   if (data == NULL) | ||||||
|     return 0; |     return 0; | ||||||
|   uint32_t crc, mask; |  | ||||||
|   crc = 0xFFFFFFFF; |   uint32_t crc = 0xFFFFFFFF; | ||||||
|  |   uint32_t mask; | ||||||
|  |  | ||||||
|   while (len--) |   while (len--) | ||||||
|   { |   { | ||||||
|     crc ^= *data++; |     crc ^= *data++; | ||||||
|  |  | ||||||
|     for (uint8_t k = 0; k < 8; k++) |     for (uint8_t k = 0; k < 8; k++) | ||||||
|     { |     { | ||||||
|       mask = -(crc & 1); |       mask = -(crc & 1); | ||||||
|       crc = (crc >> 1) ^ (0xEDB88320 & mask); |       crc = (crc >> 1) ^ (0xEDB88320 & mask); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return ~crc; |   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) | void dumpEEPROM(uint16_t memoryAddress, uint16_t length) | ||||||
| { | { | ||||||
| #define BLOCK_TO_LENGTH 16 | #define BLOCK_TO_LENGTH 16 | ||||||
|  |  | ||||||
|   if (eeAvailable == false) |   if (!checkEEPROMavailable()) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   char ascii_buf[BLOCK_TO_LENGTH + 1]; |   char ascii_buf[BLOCK_TO_LENGTH + 1]; | ||||||
|   sprintf(ascii_buf, "%*s", BLOCK_TO_LENGTH, "ASCII"); |   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; |   memoryAddress = memoryAddress / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH; | ||||||
|   length = (length + BLOCK_TO_LENGTH - 1) / 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++) |   for (unsigned int i = 0; i < length; i++) | ||||||
|   { |   { | ||||||
|     int blockpoint = memoryAddress % BLOCK_TO_LENGTH; |     int blockpoint = memoryAddress % BLOCK_TO_LENGTH; | ||||||
|  |  | ||||||
|  |     // Print ASCII representation header for each block | ||||||
|     if (blockpoint == 0) |     if (blockpoint == 0) | ||||||
|     { |     { | ||||||
|       ascii_buf[BLOCK_TO_LENGTH] = 0; |       ascii_buf[BLOCK_TO_LENGTH] = 0; | ||||||
|       Serial.printf("  %s", ascii_buf); |       Debug_pushMessage("  %s", ascii_buf); | ||||||
|       Serial.printf("\n0x%05X:", memoryAddress); |       Debug_pushMessage("\n0x%05X:", memoryAddress); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Read and print each byte | ||||||
|     ascii_buf[blockpoint] = ee.readByte(memoryAddress); |     ascii_buf[blockpoint] = ee.readByte(memoryAddress); | ||||||
|     Serial.printf(" %02X", ascii_buf[blockpoint]); |     Debug_pushMessage(" %02X", ascii_buf[blockpoint]); | ||||||
|  |  | ||||||
|  |     // Replace non-printable characters with dots in ASCII representation | ||||||
|     if (ascii_buf[blockpoint] < 0x20 || ascii_buf[blockpoint] > 0x7E) |     if (ascii_buf[blockpoint] < 0x20 || ascii_buf[blockpoint] > 0x7E) | ||||||
|       ascii_buf[blockpoint] = '.'; |       ascii_buf[blockpoint] = '.'; | ||||||
|  |  | ||||||
|     memoryAddress++; |     memoryAddress++; | ||||||
|   } |   } | ||||||
|   Serial.println(); |  | ||||||
|  |   // Print a new line at the end of the dump | ||||||
|  |   Debug_pushMessage("\n"); | ||||||
| } | } | ||||||
|  |  | ||||||
| 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()) |   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; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Clear DTC for no EEPROM found since it's available now | ||||||
|  |   MaintainDTC(DTC_NO_EEPROM_FOUND, false); | ||||||
|  |  | ||||||
|  |   // EEPROM is available | ||||||
|   return true; |   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 ConfigSanityCheck(bool autocorrect) | ||||||
| { | { | ||||||
|   uint32_t setting_reset_bits = 0; |   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) |     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; |   return setting_reset_bits; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool ValidateEEPROM_Version() | /** | ||||||
|  |  * @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 (eeAvailable == false) |   if (string == NULL) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   uint8_t EEPROMVersionOnChip = ee.readByte(startofConfigData); |   for (size_t i = 0; i < size; i++) | ||||||
|  |  | ||||||
|   if (EEPROMVersionOnChip < ConfigData_defaults.EEPROM_Version) |  | ||||||
|   { |   { | ||||||
|     Serial.printf("EEPROM Image Version is %d, but %d expected - trying to migrate\n", EEPROMVersionOnChip, ConfigData_defaults.EEPROM_Version); |     char c = string[i]; | ||||||
|     if (!MigrateEEPROM(EEPROMVersionOnChip)) |     if (c == '\0') | ||||||
|     { |     { | ||||||
|       Serial.print("Error\n"); |       // Reached the end of the string, all characters were valid WiFi characters. | ||||||
|       MaintainDTC(DTC_EEPROM_MIGRATE_FAILED, DTC_CRITICAL, true, EEPROMVersionOnChip); |       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; |       return false; | ||||||
|     } |     } | ||||||
|     else |  | ||||||
|     { |  | ||||||
|       Serial.print("Success\n"); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|   return true; |   // If the loop completes without finding a null terminator, the string is invalid. | ||||||
|  |   return false; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool MigrateEEPROM(uint8_t fromVersion) |  | ||||||
| { |  | ||||||
|   uint16_t persistanceMarker_onChip; |  | ||||||
|  |  | ||||||
|   switch (fromVersion) |  | ||||||
|   { |  | ||||||
|  |  | ||||||
|     // 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; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -98,6 +98,7 @@ void LoRa_Process() | |||||||
|     static char packageInput[32]; |     static char packageInput[32]; | ||||||
|     static bool packageRecieved = false; |     static bool packageRecieved = false; | ||||||
|     static unsigned int bufferPtr = 0; |     static unsigned int bufferPtr = 0; | ||||||
|  |     int recievedSize = 0; | ||||||
|  |  | ||||||
|     while (SerialLoRa.available() && packageRecieved == false) |     while (SerialLoRa.available() && packageRecieved == false) | ||||||
|     { |     { | ||||||
| @@ -109,6 +110,7 @@ void LoRa_Process() | |||||||
|             if (packageInput[bufferPtr] == '\n') |             if (packageInput[bufferPtr] == '\n') | ||||||
|             { |             { | ||||||
|                 packageRecieved = true; |                 packageRecieved = true; | ||||||
|  |                 recievedSize = bufferPtr; | ||||||
|                 bufferPtr = 0; |                 bufferPtr = 0; | ||||||
|                 Debug_pushMessage("Got LoRa UART: %s\n", packageInput); |                 Debug_pushMessage("Got LoRa UART: %s\n", packageInput); | ||||||
|             } |             } | ||||||
| @@ -119,8 +121,10 @@ void LoRa_Process() | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (packageRecieved) |     if (packageRecieved) { | ||||||
|         Parse_LoRa_UartCommand(packageInput, bufferPtr); |         Parse_LoRa_UartCommand(packageInput, recievedSize); | ||||||
|  |         packageRecieved = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| @@ -234,6 +238,7 @@ void printParameters(struct Configuration configuration) | |||||||
| void Parse_LoRa_UartCommand(char input[], int size) | void Parse_LoRa_UartCommand(char input[], int size) | ||||||
| { | { | ||||||
|  |  | ||||||
|  |     Debug_pushMessage("Start parsing, size: %d", size); | ||||||
|     char delimiter[] = ";"; |     char delimiter[] = ";"; | ||||||
|     char *ptr; |     char *ptr; | ||||||
|     char command[8]; |     char command[8]; | ||||||
| @@ -241,11 +246,28 @@ void Parse_LoRa_UartCommand(char input[], int size) | |||||||
|  |  | ||||||
|     ptr = strtok(input, delimiter); |     ptr = strtok(input, delimiter); | ||||||
|  |  | ||||||
|  |     ptr = strtok(input, delimiter); | ||||||
|  |  | ||||||
|     while (ptr != NULL) |     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); |         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", command, value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     Debug_pushMessage("Parsed LoRa UART Command:  %s Value: %s\n", command, value); |     Debug_pushMessage("Parsed LoRa UART Command:  %s Value: %s\n", command, value); | ||||||
| @@ -253,10 +275,12 @@ void Parse_LoRa_UartCommand(char input[], int size) | |||||||
|     if (!strcmp(command, "ENABLE")) |     if (!strcmp(command, "ENABLE")) | ||||||
|     { |     { | ||||||
|         globals.timer_disabled = false; |         globals.timer_disabled = false; | ||||||
|  |         Debug_pushMessage("Enabled by LoRa"); | ||||||
|     } |     } | ||||||
|     else if (!strcmp(command, "DISABLE")) |     else if (!strcmp(command, "DISABLE")) | ||||||
|     { |     { | ||||||
|         globals.timer_disabled = false; |         globals.timer_disabled = true; | ||||||
|  |         Debug_pushMessage("Disabled by LoRa"); | ||||||
|     } |     } | ||||||
|     else if (!strcmp(command, "RESET")) |     else if (!strcmp(command, "RESET")) | ||||||
|     { |     { | ||||||
| @@ -264,6 +288,7 @@ void Parse_LoRa_UartCommand(char input[], int size) | |||||||
|         PersistenceData.faction_1_timer = 0; |         PersistenceData.faction_1_timer = 0; | ||||||
|         PersistenceData.faction_2_timer = 0; |         PersistenceData.faction_2_timer = 0; | ||||||
|         PersistenceData.faction_3_timer = 0; |         PersistenceData.faction_3_timer = 0; | ||||||
|  |         Debug_pushMessage("Reset by LoRa"); | ||||||
|     } |     } | ||||||
|     else if (!strcmp(command, "TMRSTP")) |     else if (!strcmp(command, "TMRSTP")) | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -42,12 +42,11 @@ TM1637 disp_FAC_3(GPIO_7SEG_CLK, GPIO_7SEG_EN_FAC3); | |||||||
|  |  | ||||||
| void SevenSeg_Output(); | void SevenSeg_Output(); | ||||||
| void toggleWiFiAP(boolean shutdown = false); | void toggleWiFiAP(boolean shutdown = false); | ||||||
| void SystemShutdown(); | void SystemShutdown(bool restart = false); | ||||||
| void SetBatteryType(batteryType_t type); | void SetBatteryType(batteryType_t type); | ||||||
| void ProcessKeyCombos(bool *btnState); | void ProcessKeyCombos(bool *btnState); | ||||||
| void OverrideDisplay(uint32_t time, const char *message1, const char *message2, const char *message3); | void OverrideDisplay(uint32_t time, const char *message1, const char *message2, const char *message3); | ||||||
| void initGlobals(); | void EEPROMCyclicPDS_callback(); | ||||||
| void maintainSysStat(); |  | ||||||
|  |  | ||||||
| #if defined(FEATURE_ENABLE_UARTLORA) || defined(FEATURE_ENABLE_LORA) | #if defined(FEATURE_ENABLE_UARTLORA) || defined(FEATURE_ENABLE_LORA) | ||||||
| void setMPins_Helper(int pin, int status); | void setMPins_Helper(int pin, int status); | ||||||
| @@ -61,12 +60,12 @@ void tmrCallback_FactionTicker(); | |||||||
| Ticker tmrFactionTicker(tmrCallback_FactionTicker, 1000, 0, MILLIS); | Ticker tmrFactionTicker(tmrCallback_FactionTicker, 1000, 0, MILLIS); | ||||||
| void tmrCallback_InputGetter(); | void tmrCallback_InputGetter(); | ||||||
| Ticker tmrInputGetter(tmrCallback_InputGetter, 250, 0, MILLIS); | Ticker tmrInputGetter(tmrCallback_InputGetter, 250, 0, MILLIS); | ||||||
| void tmrCallback_EEPROMCyclicPDS(); | void EEPROMCyclicPDS_callback(); | ||||||
| Ticker tmrEEPROMCyclicPDS(tmrCallback_EEPROMCyclicPDS, 60000, 0, MILLIS); | Ticker tmrEEPROMCyclicPDS(EEPROMCyclicPDS_callback, 60000, 0, MILLIS); | ||||||
|  |  | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
| void tmrCallback_WiFiMaintainConnection(); | void wifiMaintainConnectionTicker_callback(); | ||||||
| Ticker tmrWiFiMaintainConnection(tmrCallback_WiFiMaintainConnection, 1000, 0, MILLIS); | Ticker tmrWiFiMaintainConnection(wifiMaintainConnectionTicker_callback, 1000, 0, MILLIS); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| uint32_t DisplayOverrideFlag = 0; | uint32_t DisplayOverrideFlag = 0; | ||||||
| @@ -81,12 +80,30 @@ void setMPins_Helper(int pin, int status) | |||||||
|  |  | ||||||
| void setup() | void setup() | ||||||
| { | { | ||||||
|  | 	// Set CPU frequency to 80MHz | ||||||
| 	system_update_cpu_freq(SYS_CPU_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 | ||||||
|  | 	snprintf(globals.DeviceName, 32, HOST_NAME, 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.DeviceName); | ||||||
|  | 	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.begin(115200); | ||||||
| 	Serial.setDebugOutput(false); | 	Serial.setDebugOutput(false); | ||||||
|  |  | ||||||
| @@ -94,11 +111,11 @@ void setup() | |||||||
| 	Serial.print(globals.DeviceName); | 	Serial.print(globals.DeviceName); | ||||||
| 	Serial.print("\nby Hiabuto Defense\n"); | 	Serial.print("\nby Hiabuto Defense\n"); | ||||||
|  |  | ||||||
| 	ClearAllDTC(); // Init DTC-Storage | 	// Initialize EEPROM, load configuration, and persistence data from EEPROM | ||||||
|  |  | ||||||
| 	InitEEPROM(); | 	InitEEPROM(); | ||||||
| 	GetConfig_EEPROM(); | 	GetConfig_EEPROM(); | ||||||
| 	GetPersistence_EEPROM(); | 	GetPersistence_EEPROM(); | ||||||
|  | 	Serial.print("\nEE-Init done"); | ||||||
|  |  | ||||||
| 	if (i2c_io.begin()) | 	if (i2c_io.begin()) | ||||||
| 	{ | 	{ | ||||||
| @@ -131,18 +148,9 @@ void setup() | |||||||
| 	} | 	} | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | 	// Set up OTA updates | ||||||
| 	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 |  | ||||||
|  |  | ||||||
| 	ArduinoOTA.setPort(8266); | 	ArduinoOTA.setPort(8266); | ||||||
| 	ArduinoOTA.setHostname(globals.DeviceName_ID); | 	ArduinoOTA.setHostname(globals.DeviceName); | ||||||
| 	ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD)); | 	ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD)); | ||||||
|  |  | ||||||
| 	ArduinoOTA.onStart([]() | 	ArduinoOTA.onStart([]() | ||||||
| @@ -186,15 +194,21 @@ void setup() | |||||||
|                        else if (error == OTA_END_ERROR) |                        else if (error == OTA_END_ERROR) | ||||||
|                          Serial.println("End Failed"); }); |                          Serial.println("End Failed"); }); | ||||||
|  |  | ||||||
|  | 	// Begin OTA updates | ||||||
| 	ArduinoOTA.begin(); | 	ArduinoOTA.begin(); | ||||||
| 	Serial.print("\nOTA-Init done"); | 	Serial.print("\nOTA-Init done"); | ||||||
|  |  | ||||||
|  | 	// Initialize the web user interface | ||||||
| 	initWebUI(); | 	initWebUI(); | ||||||
| 	Serial.print("\nWebUI-Init done"); | 	Serial.print("\nWebUI-Init done"); | ||||||
|  |  | ||||||
|  | 	// Initialize global variables | ||||||
| 	initGlobals(); | 	initGlobals(); | ||||||
| 	Serial.print("\nglobals-Init done"); | 	Serial.print("\nglobals-Init done"); | ||||||
| #ifdef CAPTIVE |  | ||||||
| 	dnsServer.start(53, "*", WiFi.softAPIP()); | 	// Start cyclic EEPROM updates for Persistence Data Structure (PDS) | ||||||
| #endif | 	tmrEEPROMCyclicPDS.start(); | ||||||
|  | 	Serial.print("\nSetup Done\n"); | ||||||
|  |  | ||||||
| 	disp_FAC_1.init(); | 	disp_FAC_1.init(); | ||||||
| 	disp_FAC_1.setBrightness(5); | 	disp_FAC_1.setBrightness(5); | ||||||
| @@ -215,8 +229,6 @@ void setup() | |||||||
|  |  | ||||||
| void loop() | void loop() | ||||||
| { | { | ||||||
| 	maintainSysStat(); |  | ||||||
|  |  | ||||||
| 	tmrEEPROMCyclicPDS.update(); | 	tmrEEPROMCyclicPDS.update(); | ||||||
| 	tmrFactionTicker.update(); | 	tmrFactionTicker.update(); | ||||||
| 	tmrInputGetter.update(); | 	tmrInputGetter.update(); | ||||||
| @@ -234,13 +246,59 @@ void loop() | |||||||
| 	tmrStatusSender.update(); | 	tmrStatusSender.update(); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef CAPTIVE |  | ||||||
| 	dnsServer.processNextRequest(); |  | ||||||
| #endif |  | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
|  | 	// Update WiFi connection maintenance ticker if WiFi client feature is enabled | ||||||
| 	tmrWiFiMaintainConnection.update(); | 	tmrWiFiMaintainConnection.update(); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | 	static tSystem_Status lastStatus = sysStat_Error; | ||||||
|  |  | ||||||
|  | 	// 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; | ||||||
|  |  | ||||||
|  | 	default: | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Yield to allow other tasks to run | ||||||
| 	yield(); | 	yield(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -254,7 +312,7 @@ String macToString(const unsigned char *mac) | |||||||
|  |  | ||||||
| void SevenSeg_Output() | void SevenSeg_Output() | ||||||
| { | { | ||||||
| 	char sevenSegBuff[5] = ""; | 	char sevenSegBuff[9] = ""; | ||||||
|  |  | ||||||
| 	if (DisplayOverrideFlag > millis()) | 	if (DisplayOverrideFlag > millis()) | ||||||
| 	{ | 	{ | ||||||
| @@ -270,9 +328,11 @@ void SevenSeg_Output() | |||||||
| 		if (globals.battery_level < BAT_LOW_PERCENT && millis() % 10000 > 7000) | 		if (globals.battery_level < BAT_LOW_PERCENT && millis() % 10000 > 7000) | ||||||
| 		{ | 		{ | ||||||
| 			if (millis() % 3000 < 1500) | 			if (millis() % 3000 < 1500) | ||||||
| 				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", globals.battery_level); | 				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4u", globals.battery_level); | ||||||
| 			else | 			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.setBrightness(1); | ||||||
| 			disp_FAC_1.display(" Bat"); | 			disp_FAC_1.display(" Bat"); | ||||||
| @@ -288,18 +348,21 @@ void SevenSeg_Output() | |||||||
| 			disp_FAC_1.setBrightness(PersistenceData.activeFaction == FACTION_1 ? 5 : 1); | 			disp_FAC_1.setBrightness(PersistenceData.activeFaction == FACTION_1 ? 5 : 1); | ||||||
| 			disp_FAC_1.refresh(); | 			disp_FAC_1.refresh(); | ||||||
| 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_1_timer / 60); | 			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.display(String(sevenSegBuff), false, false); | ||||||
| 			disp_FAC_1.setDp((PersistenceData.activeFaction == FACTION_1) && (millis() % 1000 > 500) ? 0x08 : 0x00); | 			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.setBrightness(PersistenceData.activeFaction == FACTION_2 ? 5 : 1); | ||||||
| 			disp_FAC_2.refresh(); | 			disp_FAC_2.refresh(); | ||||||
| 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_2_timer / 60); | 			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.display(String(sevenSegBuff), false, false); | ||||||
| 			disp_FAC_2.setDp((PersistenceData.activeFaction == FACTION_2) && (millis() % 1000 > 500) ? 0x08 : 0x00); | 			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.setBrightness(PersistenceData.activeFaction == FACTION_3 ? 5 : 1); | ||||||
| 			disp_FAC_3.refresh(); | 			disp_FAC_3.refresh(); | ||||||
| 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_3_timer / 60); | 			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.display(String(sevenSegBuff), false, false); | ||||||
| 			disp_FAC_3.setDp((PersistenceData.activeFaction == FACTION_3) && (millis() % 1000 > 500) ? 0x08 : 0x00); | 			disp_FAC_3.setDp((PersistenceData.activeFaction == FACTION_3) && (millis() % 1000 > 500) ? 0x08 : 0x00); | ||||||
| 		} | 		} | ||||||
| @@ -352,7 +415,7 @@ void tmrCallback_InputGetter() | |||||||
| 		return; | 		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) | 		if (PersistenceData.activeFaction != FACTION_1) | ||||||
| 		{ | 		{ | ||||||
| @@ -362,7 +425,7 @@ void tmrCallback_InputGetter() | |||||||
| 		PersistenceData.activeFaction = FACTION_1; | 		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) | 		if (PersistenceData.activeFaction != FACTION_2) | ||||||
| 		{ | 		{ | ||||||
| @@ -372,7 +435,7 @@ void tmrCallback_InputGetter() | |||||||
| 		PersistenceData.activeFaction = FACTION_2; | 		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) | 		if (PersistenceData.activeFaction != FACTION_3) | ||||||
| 		{ | 		{ | ||||||
| @@ -425,8 +488,8 @@ void tmrCallback_PowerMonitor() | |||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	MaintainDTC(DTC_BAT_LOW, DTC_WARN, (battery_level < 15 ? true : false), battery_level); | 	MaintainDTC(DTC_BAT_LOW, (battery_level < 15 ? true : false), battery_level); | ||||||
| 	MaintainDTC(DTC_BAT_CRITICAL, DTC_CRITICAL, (battery_level < 5 ? 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("Battery Level: %d %%\n", globals.battery_level); | ||||||
| 	// Serial.printf("Bus Voltage: %f V\n", busvoltage); | 	// Serial.printf("Bus Voltage: %f V\n", busvoltage); | ||||||
| @@ -436,74 +499,131 @@ void tmrCallback_PowerMonitor() | |||||||
| 	// Serial.printf("Power: %f mW\n", power_mW); | 	// 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(); | 	StorePersistence_EEPROM(); | ||||||
| } | } | ||||||
|  |  | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #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; | 	static uint32_t WiFiFailCount = 0; | ||||||
| 	const uint32_t WiFiFailMax = 20; | 	const uint32_t WiFiFailMax = 20; | ||||||
|  |  | ||||||
|  | 	// Check if the device is connected to WiFi | ||||||
| 	if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED) | 	if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED) | ||||||
| 	{ | 	{ | ||||||
| 		return; | 		return; // Exit if connected | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
|  | 		// Increment WiFi connection failure count | ||||||
| 		if (WiFiFailCount < WiFiFailMax) | 		if (WiFiFailCount < WiFiFailMax) | ||||||
| 		{ | 		{ | ||||||
| 			WiFiFailCount++; | 			WiFiFailCount++; | ||||||
| 		} | 		} | ||||||
| 		else | 		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(); | 			toggleWiFiAP(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif | #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) | ||||||
| { | { | ||||||
|  | 	// Check if WiFi is currently active | ||||||
| 	if (WiFi.getMode() != WIFI_OFF) | 	if (WiFi.getMode() != WIFI_OFF) | ||||||
| 	{ | 	{ | ||||||
|  | 		// Turn off WiFi | ||||||
| 		WiFi.mode(WIFI_OFF); | 		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 | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
| 		tmrWiFiMaintainConnection.stop(); | 		tmrWiFiMaintainConnection.stop(); | ||||||
| #endif | #endif | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
|  | 		// Start WiFi in Access Point (AP) mode | ||||||
| 		WiFi.mode(WIFI_AP); | 		WiFi.mode(WIFI_AP); | ||||||
| 		WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0)); | 		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)); | 		WiFi.softAP(globals.DeviceName, QUOTE(WIFI_AP_PASSWORD)); | ||||||
|  |  | ||||||
|  | 		// Stop WiFi maintenance connection ticker if enabled and display debug messages | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
| 		tmrWiFiMaintainConnection.stop(); | 		tmrWiFiMaintainConnection.stop(); | ||||||
| 		Serial.println("WiFi AP started, stopped Maintain-Timer"); | 		Debug_pushMessage("WiFi AP started, stopped Maintain-Timer\n"); | ||||||
| #else | #else | ||||||
| 		Serial.println("WiFi AP started"); | 		Debug_pushMessage("WiFi AP started\n"); | ||||||
| #endif | #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; | 	static uint32_t shutdown_delay = 0; | ||||||
|  |  | ||||||
|  | 	// Initialize shutdown delay on the first call | ||||||
| 	if (shutdown_delay == 0) | 	if (shutdown_delay == 0) | ||||||
| 	{ | 	{ | ||||||
| 		shutdown_delay = millis() + SHUTDOWN_DELAY_MS; | 		shutdown_delay = millis() + SHUTDOWN_DELAY_MS; | ||||||
| 		Serial.printf("Shutdown requested - Restarting in %d seconds\n", SHUTDOWN_DELAY_MS / 1000); | 		Serial.printf("Shutdown requested - Restarting in %d seconds\n", SHUTDOWN_DELAY_MS / 1000); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Check if the shutdown delay has elapsed | ||||||
| 	if (shutdown_delay < millis()) | 	if (shutdown_delay < millis()) | ||||||
| 	{ | 	{ | ||||||
| 		StoreConfig_EEPROM(); | 		Webserver_Shutdown(); | ||||||
|  |  | ||||||
|  | 		// Store persistence data to EEPROM | ||||||
| 		StorePersistence_EEPROM(); | 		StorePersistence_EEPROM(); | ||||||
| 		ESP.restart(); |  | ||||||
|  | 		// Perform restart if requested, otherwise enter an indefinite loop | ||||||
|  | 		if (restart) | ||||||
|  | 			ESP.restart(); | ||||||
|  | 		else | ||||||
|  | 			while (1) | ||||||
|  | 				; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -605,7 +725,7 @@ void ProcessKeyCombos(bool *btnState) | |||||||
| void maintainSysStat() | void maintainSysStat() | ||||||
| { | { | ||||||
|  |  | ||||||
| 	static tSystem_Status lastStat = sysStat_null; | 	static tSystem_Status lastStat = sysStat_Startup; | ||||||
|  |  | ||||||
| 	// system Status Transistions | 	// system Status Transistions | ||||||
| 	switch (globals.systemStatus) | 	switch (globals.systemStatus) | ||||||
| @@ -621,7 +741,6 @@ void maintainSysStat() | |||||||
|  |  | ||||||
| 	case sysStat_Error: | 	case sysStat_Error: | ||||||
| 	case sysStat_Normal: | 	case sysStat_Normal: | ||||||
| 	case sysStat_null: |  | ||||||
| 	default: | 	default: | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
| @@ -641,7 +760,6 @@ void maintainSysStat() | |||||||
|  |  | ||||||
| 		case sysStat_Error: | 		case sysStat_Error: | ||||||
| 		case sysStat_Normal: | 		case sysStat_Normal: | ||||||
| 		case sysStat_null: |  | ||||||
| 		default: | 		default: | ||||||
| 			break; | 			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 | ||||||
| @@ -1,3 +1,16 @@ | |||||||
|  | /** | ||||||
|  |  * @file webui.cpp | ||||||
|  |  * | ||||||
|  |  * @brief Implementation file for web-based user interface (WebUI) functions in the DE-Timer application. | ||||||
|  |  * | ||||||
|  |  * This file contains the implementation of functions related to the initialization and processing of the | ||||||
|  |  * web-based user interface (WebUI). It includes the setup of LittleFS, handling of firmware version checks, | ||||||
|  |  * initialization of mDNS, setup of web server routes, and handling of various HTTP events. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   09.01.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #include "webui.h" | #include "webui.h" | ||||||
|  |  | ||||||
| AsyncWebServer webServer(80); | AsyncWebServer webServer(80); | ||||||
| @@ -5,9 +18,7 @@ AsyncWebServer webServer(80); | |||||||
| const char *PARAM_MESSAGE = "message"; | const char *PARAM_MESSAGE = "message"; | ||||||
|  |  | ||||||
| String processor(const String &var); | String processor(const String &var); | ||||||
| void WebserverPOST_Callback(AsyncWebServerRequest *request); |  | ||||||
| void WebserverNotFound_Callback(AsyncWebServerRequest *request); | void WebserverNotFound_Callback(AsyncWebServerRequest *request); | ||||||
| void Webserver_Callback(AsyncWebServerRequest *request); |  | ||||||
| void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); | void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); | ||||||
| void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); | void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); | ||||||
| void WebServerEEJSON_Callback(AsyncWebServerRequest *request); | void WebServerEEJSON_Callback(AsyncWebServerRequest *request); | ||||||
| @@ -17,295 +28,137 @@ AsyncWebSocket webSocket("/ws"); | |||||||
|  |  | ||||||
| void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); | void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); | ||||||
| void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len); | void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len); | ||||||
|  | void Websocket_RefreshClientData_DTCs(uint32_t client_id); | ||||||
|  | void Websocket_RefreshClientData_Status(uint32_t client_id, bool send_mapping = false); | ||||||
|  | void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping = false); | ||||||
|  | void Websocket_HandleButtons(uint8_t *data); | ||||||
|  | void Websocket_HandleSettings(uint8_t *data); | ||||||
|  | void parseWebsocketString(char *data, char *identifierBuffer, size_t identifierBufferSize, char *valueBuffer, size_t valueBufferSize); | ||||||
|  | int findIndexByString(const char *searchString, const char *const *array, int arraySize); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Initializes the web-based user interface (WebUI) for the DE-Timer application. | ||||||
|  |  * | ||||||
|  |  * This function sets up the necessary components for the WebUI, including mounting LittleFS, | ||||||
|  |  * performing flash version checks, initializing mDNS, and configuring the web server with | ||||||
|  |  * routes and event handlers. If any errors occur during setup, appropriate diagnostic messages | ||||||
|  |  * are pushed to the debugging system, and potential error conditions are recorded as Diagnostic | ||||||
|  |  * Trouble Codes (DTCs). | ||||||
|  |  * | ||||||
|  |  * @note This function should be called during the initialization phase of the application. | ||||||
|  |  */ | ||||||
| void initWebUI() | void initWebUI() | ||||||
| { | { | ||||||
|  |   // Attempt to mount LittleFS | ||||||
|   if (!LittleFS.begin()) |   if (!LittleFS.begin()) | ||||||
|   { |   { | ||||||
|     Debug_pushMessage("An Error has occurred while mounting LittleFS\n"); |     Debug_pushMessage("An Error has occurred while mounting LittleFS\n"); | ||||||
|     MaintainDTC(DTC_FLASHFS_ERROR, DTC_CRITICAL, true); |     MaintainDTC(DTC_FLASHFS_ERROR, true); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Retrieve the flash version | ||||||
|   GetFlashVersion(globals.FlashVersion, sizeof(globals.FlashVersion)); |   GetFlashVersion(globals.FlashVersion, sizeof(globals.FlashVersion)); | ||||||
|  |  | ||||||
|  |   // Compare the flash version with the required version | ||||||
|   char buffer[6]; |   char buffer[6]; | ||||||
|   snprintf(buffer, sizeof(buffer), "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); |   snprintf(buffer, sizeof(buffer), "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); | ||||||
|   if (strcmp(globals.FlashVersion, buffer)) |   if (strcmp(globals.FlashVersion, buffer)) | ||||||
|   { |   { | ||||||
|     MaintainDTC(DTC_FLASHFS_VERSION_ERROR, DTC_WARN, true); |     MaintainDTC(DTC_FLASHFS_VERSION_ERROR, true); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Initialize mDNS and add service | ||||||
|   MDNS.begin(globals.DeviceName); |   MDNS.begin(globals.DeviceName); | ||||||
|   MDNS.addService("http", "tcp", 80); |   MDNS.addService("http", "tcp", 80); | ||||||
|  |  | ||||||
|  |   // Set up WebSocket event handler and attach to web server | ||||||
|   webSocket.onEvent(WebsocketEvent_Callback); |   webSocket.onEvent(WebsocketEvent_Callback); | ||||||
|   webServer.addHandler(&webSocket); |   webServer.addHandler(&webSocket); | ||||||
|  |  | ||||||
|  |   // Serve static files and define routes | ||||||
|   webServer.serveStatic("/static/", LittleFS, "/static/").setCacheControl("max-age=360000"); |   webServer.serveStatic("/static/", LittleFS, "/static/").setCacheControl("max-age=360000"); | ||||||
|  |   webServer.serveStatic("/index.htm", LittleFS, "/index.htm").setCacheControl("max-age=360000"); | ||||||
|   webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) |   webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) | ||||||
|                { request->redirect("/index.htm"); }); |                { request->redirect("/index.htm"); }); | ||||||
|   webServer.onNotFound(WebserverNotFound_Callback); |   webServer.onNotFound(WebserverNotFound_Callback); | ||||||
|   webServer.on("/index.htm", HTTP_GET, Webserver_Callback); |  | ||||||
|   webServer.on("/post.htm", HTTP_POST, WebserverPOST_Callback); |  | ||||||
|   webServer.on("/eejson", HTTP_GET, WebServerEEJSON_Callback); |   webServer.on("/eejson", HTTP_GET, WebServerEEJSON_Callback); | ||||||
|   webServer.on( |   webServer.on( | ||||||
|       "/doUpdate", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverFirmwareUpdate_Callback); |       "/doUpdate", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverFirmwareUpdate_Callback); | ||||||
|   webServer.on( |   webServer.on( | ||||||
|       "/eeRestore", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverEERestore_Callback); |       "/eeRestore", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverEERestore_Callback); | ||||||
|  |  | ||||||
|  |   // Start the web server | ||||||
|   webServer.begin(); |   webServer.begin(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Processes the web server functionality for the DE-Timer application. | ||||||
|  |  * | ||||||
|  |  * This function performs periodic processing tasks for the web server, including cleaning up | ||||||
|  |  * WebSocket clients and refreshing client data when WebSocket connections are active. It ensures | ||||||
|  |  * that WebSocket client data related to Diagnostic Trouble Codes (DTCs) and system status is | ||||||
|  |  * updated at regular intervals. | ||||||
|  |  * | ||||||
|  |  * @note This function should be called in the main loop of the application. | ||||||
|  |  */ | ||||||
| void Webserver_Process() | void Webserver_Process() | ||||||
| { | { | ||||||
|  |   static uint32_t previousMillis = 0; | ||||||
|  |  | ||||||
|   webSocket.cleanupClients(); |   webSocket.cleanupClients(); | ||||||
|  |  | ||||||
|  |   if ((webSocket.count() > 0) && (millis() - previousMillis >= 10000)) | ||||||
|  |   { | ||||||
|  |     Websocket_RefreshClientData_DTCs(0); | ||||||
|  |     Websocket_RefreshClientData_Status(0); | ||||||
|  |     previousMillis = millis(); | ||||||
|  |   } | ||||||
| } | } | ||||||
| String processor(const String &var) |  | ||||||
|  | /** | ||||||
|  |  * @brief Shuts down the web server functionality for the DE-Timer application. | ||||||
|  |  * | ||||||
|  |  * This function closes all WebSocket connections and terminates the web server. It is intended | ||||||
|  |  * to be called when the application is being shut down or when there is a need to deactivate the | ||||||
|  |  * web server. | ||||||
|  |  * | ||||||
|  |  * @details This function ensures a graceful shutdown of the web server by closing all active | ||||||
|  |  *          WebSocket connections and ending the web server instance. | ||||||
|  |  * | ||||||
|  |  * @note This function should be called before shutting down the application to properly | ||||||
|  |  *       deactivate the web server. | ||||||
|  |  */ | ||||||
|  | void Webserver_Shutdown() | ||||||
| { | { | ||||||
|   if (var == "HOSTNAME") |   if (webSocket.count() > 0) | ||||||
|     return String(globals.DeviceName); |     webSocket.closeAll(); | ||||||
|   if (var == "SYSTEM_STATUS") |   webServer.end(); | ||||||
|     return String(sSystem_Status_txt[globals.systemStatus]); |  | ||||||
|   if (var == "SW_VERSION") |  | ||||||
|   { |  | ||||||
|     char buffer[6]; |  | ||||||
|     snprintf(buffer, sizeof(buffer), "%d.%02d", constants.FW_Version_major, constants.FW_Version_minor); |  | ||||||
|     return String(buffer); |  | ||||||
|   } |  | ||||||
|   if (var == "FS_VERSION") |  | ||||||
|     return String(globals.FlashVersion); |  | ||||||
|   if (var == "GIT_REV") |  | ||||||
|     return String(constants.GitHash); |  | ||||||
|  |  | ||||||
|   if (var == "SHOW_DTC_TABLE") |  | ||||||
|     return globals.systemStatus == sysStat_Error ? "" : "hidden"; |  | ||||||
|   if (var == "BAT_REMAIN_CAPACITY") |  | ||||||
|     return String(globals.battery_level); |  | ||||||
|   if (var == "DEVICENAME") |  | ||||||
|     return String(globals.DeviceName); |  | ||||||
|   if (var == "DEVICENAME_ID") |  | ||||||
|     return String(globals.DeviceName_ID); |  | ||||||
|   if (var == "BATTERY_TYPE") |  | ||||||
|     return String(ConfigData.batteryType); |  | ||||||
|   if (var == "BAT_VOLTAGE") |  | ||||||
|     return String((float)globals.loadvoltage_mV / 1000.0); |  | ||||||
|   if (var == "PERSISTANCE_CHECKSUM") |  | ||||||
|   { |  | ||||||
|     char buffer[7]; |  | ||||||
|     sprintf(buffer, "0x%04X", PersistenceData.checksum); |  | ||||||
|     return String(buffer); |  | ||||||
|   } |  | ||||||
|   if (var == "WRITE_CYCLE_COUNT") |  | ||||||
|     return String(PersistenceData.writeCycleCounter); |  | ||||||
|   if (var == "PERSISTENCE_MARKER") |  | ||||||
|     return String(globals.eePersistanceAdress); |  | ||||||
|   if (var == "EEPROM_VERSION") |  | ||||||
|     return String(ConfigData.EEPROM_Version); |  | ||||||
|   if (var == "CONFIG_CHECKSUM") |  | ||||||
|   { |  | ||||||
|     char buffer[7]; |  | ||||||
|     sprintf(buffer, "0x%04X", ConfigData.checksum); |  | ||||||
|     return String(buffer); |  | ||||||
|   } |  | ||||||
|   if (var == "DTC_TABLE") |  | ||||||
|   { |  | ||||||
|     String temp = ""; |  | ||||||
|     char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx |  | ||||||
|  |  | ||||||
|     for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) |  | ||||||
|     { |  | ||||||
|       if (DTCStorage[i].Number < DTC_LAST_DTC) |  | ||||||
|       { |  | ||||||
|         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 |  | ||||||
|  |  | ||||||
|         temp = temp + "<tr data-dtc=" + String(DTCStorage[i].Number); |  | ||||||
|         temp = temp + " data-debugval=" + String(DTCStorage[i].debugVal) + "><td>" + String(buff_timestamp); |  | ||||||
|         temp = temp + "</td><td>" + String(DTCStorage[i].Number) + "</td><td>"; |  | ||||||
|         temp = temp + "<img src=static/img/"; |  | ||||||
|         switch (DTCStorage[i].severity) |  | ||||||
|         { |  | ||||||
|         case DTC_CRITICAL: |  | ||||||
|           temp = temp + "critical"; |  | ||||||
|           break; |  | ||||||
|         case DTC_WARN: |  | ||||||
|           temp = temp + "warn"; |  | ||||||
|           break; |  | ||||||
|         case DTC_INFO: |  | ||||||
|           temp = temp + "info"; |  | ||||||
|           break; |  | ||||||
|         } |  | ||||||
|         temp = temp + ".png></td><td>"; |  | ||||||
|  |  | ||||||
|         if (DTCStorage[i].active == DTC_ACTIVE) |  | ||||||
|           temp = temp + "active"; |  | ||||||
|         else if (DTCStorage[i].active == DTC_PREVIOUS) |  | ||||||
|           temp = temp + "previous"; |  | ||||||
|         else |  | ||||||
|           temp = temp + "none"; |  | ||||||
|  |  | ||||||
|         temp = temp + "</td></tr>"; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return temp; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (var == "PLACEHOLDER") |  | ||||||
|     return "placeholder"; |  | ||||||
|  |  | ||||||
|   if (var == "POINTS_FAC_1") |  | ||||||
|   { |  | ||||||
|     char buff[12]; |  | ||||||
|     snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_1_timer / 3600, (PersistenceData.faction_1_timer / 60) % 60, PersistenceData.faction_1_timer % 60); |  | ||||||
|     return String(buff); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (var == "POINTS_FAC_2") |  | ||||||
|   { |  | ||||||
|     char buff[12]; |  | ||||||
|     snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_2_timer / 3600, (PersistenceData.faction_2_timer / 60) % 60, PersistenceData.faction_2_timer % 60); |  | ||||||
|     return String(buff); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (var == "POINTS_FAC_3") |  | ||||||
|   { |  | ||||||
|     char buff[12]; |  | ||||||
|     snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_3_timer / 3600, (PersistenceData.faction_3_timer / 60) % 60, PersistenceData.faction_3_timer % 60); |  | ||||||
|     return String(buff); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (var == "ACTIVE_FACTION") |  | ||||||
|     return String(PersistenceData.activeFaction); |  | ||||||
|  |  | ||||||
|   if (var == "FACTION_1_ACTIVE") |  | ||||||
|     return String(PersistenceData.activeFaction == FACTION_1 ? "bg-primary" : "bg-secondary"); |  | ||||||
|   if (var == "FACTION_2_ACTIVE") |  | ||||||
|     return String(PersistenceData.activeFaction == FACTION_2 ? "bg-primary" : "bg-secondary"); |  | ||||||
|   if (var == "FACTION_3_ACTIVE") |  | ||||||
|     return String(PersistenceData.activeFaction == FACTION_3 ? "bg-primary" : "bg-secondary"); |  | ||||||
|  |  | ||||||
|   if (var == "NAME_FAC_1") |  | ||||||
|     return String(ConfigData.Faction_1_Name); |  | ||||||
|  |  | ||||||
|   if (var == "NAME_FAC_2") |  | ||||||
|     return String(ConfigData.Faction_2_Name); |  | ||||||
|  |  | ||||||
|   if (var == "NAME_FAC_3") |  | ||||||
|     return String(ConfigData.Faction_3_Name); |  | ||||||
|  |  | ||||||
|   if (var == "BATTERY_SELECT_OPTIONS") |  | ||||||
|   { |  | ||||||
|     String temp; |  | ||||||
|     for (uint32_t i = 0; i < BatteryString_Elements; i++) |  | ||||||
|     { |  | ||||||
|       String selected = ConfigData.batteryType == i ? " selected " : ""; |  | ||||||
|       temp = temp + "<option value=\"" + i + "\"" + selected + ">" + BatteryString[i] + "</option>"; |  | ||||||
|     } |  | ||||||
|     return temp; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (var == "FACTIONREBOOT_CHECKED") |  | ||||||
|     return String(ConfigData.active_faction_on_reboot == true ? "checked" : ""); |  | ||||||
|  |  | ||||||
|   if (var == "FACTION_RECOVERY") |  | ||||||
|     return String(ConfigData.active_faction_on_reboot); |  | ||||||
|  |  | ||||||
|   return String(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Webserver_Callback(AsyncWebServerRequest *request) |  | ||||||
| { |  | ||||||
|   request->send(LittleFS, "/index.htm", "text/html", false, processor); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void WebserverPOST_Callback(AsyncWebServerRequest *request) |  | ||||||
| { |  | ||||||
|   request->send(LittleFS, "/post.htm", "text/html", false, processor); |  | ||||||
|  |  | ||||||
|   Debug_pushMessage("POST:\n"); |  | ||||||
|   int paramsNr = request->params(); |  | ||||||
|   for (int i = 0; i < paramsNr; i++) |  | ||||||
|   { |  | ||||||
|     AsyncWebParameter *p = request->getParam(i); |  | ||||||
|     Debug_pushMessage("%s : %s\n", p->name().c_str(), p->value().c_str()); |  | ||||||
|  |  | ||||||
|     // begin: POST Form Maintenance |  | ||||||
|     if (p->name() == "reset_ee_btn") |  | ||||||
|     { |  | ||||||
|       if (request->hasParam("reset_ee_pds", true)) |  | ||||||
|       { |  | ||||||
|         AsyncWebParameter *param = request->getParam("reset_ee_pds", true); |  | ||||||
|         if (param->value() == "on") |  | ||||||
|           globals.requestEEAction = globals.requestEEAction == EE_CFG_FORMAT ? EE_FORMAT_ALL : EE_PDS_FORMAT; |  | ||||||
|       } |  | ||||||
|       if (request->hasParam("reset_ee_cfg", true)) |  | ||||||
|       { |  | ||||||
|         AsyncWebParameter *param = request->getParam("reset_ee_cfg", true); |  | ||||||
|         if (param->value() == "on") |  | ||||||
|           globals.requestEEAction = globals.requestEEAction == EE_PDS_FORMAT ? EE_FORMAT_ALL : EE_CFG_FORMAT; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     if (p->name() == "reboot") |  | ||||||
|     { |  | ||||||
|       globals.systemStatus = sysStat_Shutdown; |  | ||||||
|     } |  | ||||||
|     if (p->name() == "resetpoints") |  | ||||||
|     { |  | ||||||
|       PersistenceData.faction_1_timer = 0; |  | ||||||
|       PersistenceData.faction_2_timer = 0; |  | ||||||
|       PersistenceData.faction_3_timer = 0; |  | ||||||
|       PersistenceData.activeFaction = NONE; |  | ||||||
|       globals.requestEEAction == EE_PDS_SAVE; |  | ||||||
|     } |  | ||||||
|     // end: POST Form Maintenance |  | ||||||
|  |  | ||||||
|     // begin: POST Form Settings |  | ||||||
|     if (p->name() == "battery_select") |  | ||||||
|     { |  | ||||||
|       batteryType_t temp = (batteryType_t)p->value().toInt(); |  | ||||||
|       ConfigData.batteryType = temp; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (request->hasParam("factionreboot_cont", true)) |  | ||||||
|     { |  | ||||||
|       AsyncWebParameter *param = request->getParam("factionreboot_cont", true); |  | ||||||
|       if (param->value() == "on") |  | ||||||
|         ConfigData.active_faction_on_reboot = true; |  | ||||||
|     } |  | ||||||
|     else |  | ||||||
|     { |  | ||||||
|       ConfigData.active_faction_on_reboot = false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (p->name() == "faction_1_name") |  | ||||||
|     { |  | ||||||
|       strncpy(ConfigData.Faction_1_Name, p->value().c_str(), sizeof(ConfigData.Faction_1_Name)); |  | ||||||
|     } |  | ||||||
|     if (p->name() == "faction_2_name") |  | ||||||
|     { |  | ||||||
|       strncpy(ConfigData.Faction_2_Name, p->value().c_str(), sizeof(ConfigData.Faction_2_Name)); |  | ||||||
|     } |  | ||||||
|     if (p->name() == "faction_3_name") |  | ||||||
|     { |  | ||||||
|       strncpy(ConfigData.Faction_3_Name, p->value().c_str(), sizeof(ConfigData.Faction_3_Name)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (p->name() == "settingssave") |  | ||||||
|       globals.requestEEAction = EE_CFG_SAVE; |  | ||||||
|     // end: POST Form Settings |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback function for handling HTTP 404 (Not Found) errors on the web server. | ||||||
|  |  * | ||||||
|  |  * This function is invoked when an HTTP request results in a 404 error (Not Found). It sends | ||||||
|  |  * a simple "Not found" text response with an HTTP status code of 404. | ||||||
|  |  * | ||||||
|  |  * @param request Pointer to the AsyncWebServerRequest object representing the HTTP request. | ||||||
|  |  */ | ||||||
| void WebserverNotFound_Callback(AsyncWebServerRequest *request) | void WebserverNotFound_Callback(AsyncWebServerRequest *request) | ||||||
| { | { | ||||||
|   request->send(404, "text/html", "Not found"); |   request->send(404, "text/html", "Not found"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Reads the flash version information from a file in LittleFS. | ||||||
|  |  * | ||||||
|  |  * This function reads the flash version information stored in a file named "version" in the | ||||||
|  |  * LittleFS filesystem. It opens the file, reads the content until a carriage return ('\r') is | ||||||
|  |  * encountered, and stores the result in the provided buffer. The buffer is null-terminated. | ||||||
|  |  * | ||||||
|  |  * @param buff Pointer to the buffer where the flash version information will be stored. | ||||||
|  |  * @param buff_size Size of the buffer. | ||||||
|  |  */ | ||||||
| void GetFlashVersion(char *buff, size_t buff_size) | void GetFlashVersion(char *buff, size_t buff_size) | ||||||
| { | { | ||||||
|   File this_file = LittleFS.open("version", "r"); |   File this_file = LittleFS.open("version", "r"); | ||||||
| @@ -323,12 +176,27 @@ void GetFlashVersion(char *buff, size_t buff_size) | |||||||
|   this_file.close(); |   this_file.close(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback function for handling firmware updates via the web server. | ||||||
|  |  * | ||||||
|  |  * This function is invoked during the firmware update process when a new firmware file | ||||||
|  |  * is received. It handles the update process using the ESPAsyncHTTPUpdate library. The update | ||||||
|  |  * process involves checking the firmware type, initializing the update, writing data, and finalizing | ||||||
|  |  * the update. If the update is successful, it triggers a system shutdown. | ||||||
|  |  * | ||||||
|  |  * @param request   Pointer to the AsyncWebServerRequest object. | ||||||
|  |  * @param filename  The name of the file being updated. | ||||||
|  |  * @param index     The index of the file being updated. | ||||||
|  |  * @param data      Pointer to the data buffer. | ||||||
|  |  * @param len       The length of the data buffer. | ||||||
|  |  * @param final     Boolean indicating if this is the final chunk of data. | ||||||
|  |  */ | ||||||
| void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) | void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) | ||||||
| { | { | ||||||
|  |  | ||||||
|   if (!index) |   if (!index) | ||||||
|   { |   { | ||||||
|     Debug_pushMessage("Update"); |     Debug_pushMessage("Update\n"); | ||||||
|     size_t content_len = request->contentLength(); |     size_t content_len = request->contentLength(); | ||||||
|     int cmd = (filename.indexOf(".fs") > -1) ? U_FS : U_FLASH; |     int cmd = (filename.indexOf(".fs") > -1) ? U_FS : U_FLASH; | ||||||
|     Update.runAsync(true); |     Update.runAsync(true); | ||||||
| @@ -365,6 +233,21 @@ void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const Stri | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback function for handling EEPROM restore via the web server. | ||||||
|  |  * | ||||||
|  |  * This function is invoked during the EEPROM restore process when a new EEPROM file | ||||||
|  |  * is received. It handles the restore process by reading the data from the received file, | ||||||
|  |  * deserializing the JSON data, and updating the configuration and persistence data accordingly. | ||||||
|  |  * If the restore is successful, it triggers a system shutdown. | ||||||
|  |  * | ||||||
|  |  * @param request   Pointer to the AsyncWebServerRequest object. | ||||||
|  |  * @param filename  The name of the file being restored. | ||||||
|  |  * @param index     The index of the file being restored. | ||||||
|  |  * @param data      Pointer to the data buffer. | ||||||
|  |  * @param len       The length of the data buffer. | ||||||
|  |  * @param final     Boolean indicating if this is the final chunk of data. | ||||||
|  |  */ | ||||||
| void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) | void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) | ||||||
| { | { | ||||||
|   bool ee_done = false; |   bool ee_done = false; | ||||||
| @@ -381,7 +264,7 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f | |||||||
|       buffer = (char *)malloc(1536); |       buffer = (char *)malloc(1536); | ||||||
|       read_ptr = 0; |       read_ptr = 0; | ||||||
|       if (buffer == NULL) |       if (buffer == NULL) | ||||||
|         Debug_pushMessage("malloc() failed for EEPROM-Restore"); |         Debug_pushMessage("malloc() failed for EEPROM-Restore\n"); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -396,8 +279,8 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f | |||||||
|     if (buffer != NULL) |     if (buffer != NULL) | ||||||
|     { |     { | ||||||
|       Serial.print(buffer); |       Serial.print(buffer); | ||||||
|       StaticJsonDocument<1536> doc; |       JsonDocument json; | ||||||
|       error = deserializeJson(doc, buffer); |       error = deserializeJson(json, buffer); | ||||||
|       if (error) |       if (error) | ||||||
|       { |       { | ||||||
|         Debug_pushMessage("deserializeJson() failed: %s\n", error.f_str()); |         Debug_pushMessage("deserializeJson() failed: %s\n", error.f_str()); | ||||||
| @@ -405,18 +288,22 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f | |||||||
|       else |       else | ||||||
|       { |       { | ||||||
|  |  | ||||||
|         ConfigData.batteryType = (batteryType_t)doc["config"]["batteryType"].as<uint32_t>(); |         ConfigData.batteryType = (batteryType_t)json["config"]["batteryType"].as<int>(); | ||||||
|         ConfigData.EEPROM_Version = doc["config"]["EEPROM_Version"].as<uint32_t>(); |         ConfigData.active_faction_on_reboot = json["config"]["active_faction_on_reboot"].as<bool>(); | ||||||
|         strncpy(ConfigData.Faction_1_Name, doc["config"]["Faction_1_Name"].as<String>().c_str(), sizeof(ConfigData.Faction_1_Name)); |         strncpy(ConfigData.Faction_1_Name, json["config"]["Faction_1_Name"].as<const char *>(), sizeof(ConfigData.Faction_1_Name)); | ||||||
|         strncpy(ConfigData.Faction_2_Name, doc["config"]["Faction_2_Name"].as<String>().c_str(), sizeof(ConfigData.Faction_2_Name)); |         strncpy(ConfigData.Faction_2_Name, json["config"]["Faction_2_Name"].as<const char *>(), sizeof(ConfigData.Faction_2_Name)); | ||||||
|         strncpy(ConfigData.Faction_3_Name, doc["config"]["Faction_3_Name"].as<String>().c_str(), sizeof(ConfigData.Faction_3_Name)); |         strncpy(ConfigData.Faction_3_Name, json["config"]["Faction_3_Name"].as<const char *>(), sizeof(ConfigData.Faction_3_Name)); | ||||||
|  |         strncpy(ConfigData.wifi_ap_ssid, json["config"]["wifi_ap_ssid"].as<const char *>(), sizeof(ConfigData.wifi_ap_ssid)); | ||||||
|  |         strncpy(ConfigData.wifi_ap_password, json["config"]["wifi_ap_password"].as<const char *>(), sizeof(ConfigData.wifi_ap_password)); | ||||||
|  |         strncpy(ConfigData.wifi_client_ssid, json["config"]["wifi_client_ssid"].as<const char *>(), sizeof(ConfigData.wifi_client_ssid)); | ||||||
|  |         strncpy(ConfigData.wifi_client_password, json["config"]["wifi_client_password"].as<const char *>(), sizeof(ConfigData.wifi_client_password)); | ||||||
|  |  | ||||||
|         PersistenceData.writeCycleCounter = doc["persis"]["writeCycleCounter"].as<uint16_t>(); |         PersistenceData.writeCycleCounter = json["persis"]["writeCycleCounter"].as<uint16_t>(); | ||||||
|         PersistenceData.activeFaction = (factions_t)doc["persis"]["activeFaction"].as<uint32_t>(); |         PersistenceData.activeFaction = (Factions_t)json["persis"]["activeFaction"].as<int>(); | ||||||
|         PersistenceData.faction_1_timer = doc["persis"]["faction_1_timer"].as<uint32_t>(); |         PersistenceData.faction_1_timer = json["persis"]["faction_1_timer"].as<uint32_t>(); | ||||||
|         PersistenceData.faction_2_timer = doc["persis"]["faction_2_timer"].as<uint32_t>(); |         PersistenceData.faction_2_timer = json["persis"]["faction_2_timer"].as<uint32_t>(); | ||||||
|         PersistenceData.faction_3_timer = doc["persis"]["faction_3_timer"].as<uint32_t>(); |         PersistenceData.faction_3_timer = json["persis"]["faction_3_timer"].as<uint32_t>(); | ||||||
|         PersistenceData.checksum = doc["persis"]["checksum"].as<uint32_t>(); |         PersistenceData.checksum = json["persis"]["checksum"].as<uint32_t>(); | ||||||
|  |  | ||||||
|         ee_done = true; |         ee_done = true; | ||||||
|       } |       } | ||||||
| @@ -431,55 +318,44 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f | |||||||
|  |  | ||||||
|     if (ee_done) |     if (ee_done) | ||||||
|     { |     { | ||||||
|       Debug_pushMessage("Update complete"); |       Debug_pushMessage("Update complete\n"); | ||||||
|       globals.systemStatus = sysStat_Shutdown; |       globals.systemStatus = sysStat_Shutdown; | ||||||
|     } |     } | ||||||
|     else |  | ||||||
|     { |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback function for handling EEPROM JSON request via the web server. | ||||||
|  |  * | ||||||
|  |  * This function is invoked when a request for EEPROM JSON data is received. It constructs a JSON | ||||||
|  |  * response containing information about the firmware, configuration, and persistence data. | ||||||
|  |  * | ||||||
|  |  * @param request Pointer to the AsyncWebServerRequest object. | ||||||
|  |  */ | ||||||
| void WebServerEEJSON_Callback(AsyncWebServerRequest *request) | void WebServerEEJSON_Callback(AsyncWebServerRequest *request) | ||||||
| { | { | ||||||
|   AsyncResponseStream *response = request->beginResponseStream("application/json"); |   AsyncResponseStream *response = request->beginResponseStream("application/json"); | ||||||
|   DynamicJsonDocument json(1024); |   JsonDocument json; | ||||||
|   JsonObject fwinfo = json.createNestedObject("info"); |   JsonObject info = json["info"].to<JsonObject>(); | ||||||
|  |  | ||||||
|   char buffer[16]; |   char buffer[16]; | ||||||
|  |  | ||||||
|   fwinfo["DeviceName"] = globals.DeviceName; |   info["DeviceName"] = globals.DeviceName; | ||||||
|   sprintf(buffer, "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); |   sprintf(buffer, "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); | ||||||
|   fwinfo["FW-Version"] = buffer; |   info["FW-Version"] = buffer; | ||||||
|   fwinfo["FS-Version"] = globals.FlashVersion; |   info["FS-Version"] = globals.FlashVersion; | ||||||
|   snprintf_P(buffer, sizeof(buffer), "%s", constants.GitHash); |   snprintf_P(buffer, sizeof(buffer), "%s", constants.GitHash); | ||||||
|   fwinfo["Git-Hash"] = buffer; |   info["Git-Hash"] = buffer; | ||||||
|  |  | ||||||
|   JsonObject config = json.createNestedObject("config"); |   JsonObject config = json["config"].to<JsonObject>(); | ||||||
|  |   generateJsonObject_ConfigData(config); | ||||||
|   config["EEPROM_Version"] = ConfigData.EEPROM_Version; |   JsonObject persis = json["persis"].to<JsonObject>(); | ||||||
|   config["batteryType"] = ConfigData.batteryType; |   generateJsonObject_PersistenceData(persis); | ||||||
|   config["Faction_1_Name"] = ConfigData.Faction_1_Name; |  | ||||||
|   config["Faction_2_Name"] = ConfigData.Faction_2_Name; |  | ||||||
|   config["Faction_3_Name"] = ConfigData.Faction_3_Name; |  | ||||||
|   sprintf(buffer, "0x%08X", ConfigData.checksum); |  | ||||||
|   config["checksum"] = buffer; |  | ||||||
|  |  | ||||||
|   JsonObject eepart = json.createNestedObject("eepart"); |  | ||||||
|  |  | ||||||
|  |   JsonObject eepart = json["eepart"].to<JsonObject>(); | ||||||
|   sprintf(buffer, "0x%04X", globals.eePersistanceAdress); |   sprintf(buffer, "0x%04X", globals.eePersistanceAdress); | ||||||
|   eepart["PersistanceAddress"] = buffer; |   eepart["PersistanceAddress"] = buffer; | ||||||
|  |  | ||||||
|   JsonObject persis = json.createNestedObject("persis"); |  | ||||||
|  |  | ||||||
|   persis["writeCycleCounter"] = PersistenceData.writeCycleCounter; |  | ||||||
|   persis["activeFaction"] = PersistenceData.activeFaction; |  | ||||||
|   persis["faction_1_timer"] = PersistenceData.faction_1_timer; |  | ||||||
|   persis["faction_2_timer"] = PersistenceData.faction_2_timer; |  | ||||||
|   persis["faction_3_timer"] = PersistenceData.faction_3_timer; |  | ||||||
|   sprintf(buffer, "0x%08X", PersistenceData.checksum); |  | ||||||
|   persis["checksum"] = buffer; |  | ||||||
|  |  | ||||||
|   serializeJsonPretty(json, *response); |   serializeJsonPretty(json, *response); | ||||||
|  |  | ||||||
|   response->addHeader("Content-disposition", "attachment; filename=backup.ee.json"); |   response->addHeader("Content-disposition", "attachment; filename=backup.ee.json"); | ||||||
| @@ -487,12 +363,28 @@ void WebServerEEJSON_Callback(AsyncWebServerRequest *request) | |||||||
|   request->send(response); |   request->send(response); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback function for handling WebSocket events. | ||||||
|  |  * | ||||||
|  |  * This function is invoked when events occur in the WebSocket communication, such as client connection, | ||||||
|  |  * disconnection, reception of data, and others. It dispatches the events to the appropriate handlers. | ||||||
|  |  * | ||||||
|  |  * @param server Pointer to the AsyncWebSocket object. | ||||||
|  |  * @param client Pointer to the AsyncWebSocketClient object representing the WebSocket client. | ||||||
|  |  * @param type Type of WebSocket event. | ||||||
|  |  * @param arg Event-specific argument. | ||||||
|  |  * @param data Pointer to the received data (if applicable). | ||||||
|  |  * @param len Length of the received data. | ||||||
|  |  */ | ||||||
| void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) | void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) | ||||||
| { | { | ||||||
|   switch (type) |   switch (type) | ||||||
|   { |   { | ||||||
|   case WS_EVT_CONNECT: |   case WS_EVT_CONNECT: | ||||||
|     Debug_pushMessage("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); |     Debug_pushMessage("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); | ||||||
|  |     Websocket_RefreshClientData_Status(client->id(), true); | ||||||
|  |     Websocket_RefreshClientData_Static(client->id(), true); | ||||||
|  |     Websocket_RefreshClientData_DTCs(client->id()); | ||||||
|     break; |     break; | ||||||
|   case WS_EVT_DISCONNECT: |   case WS_EVT_DISCONNECT: | ||||||
|     Debug_pushMessage("WebSocket client #%u disconnected\n", client->id()); |     Debug_pushMessage("WebSocket client #%u disconnected\n", client->id()); | ||||||
| @@ -506,31 +398,368 @@ void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *clien | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Handles WebSocket messages received from clients. | ||||||
|  |  * | ||||||
|  |  * This function processes WebSocket messages, such as starting or stopping debugging, | ||||||
|  |  * and provides appropriate responses. | ||||||
|  |  * | ||||||
|  |  * @param arg Pointer to the WebSocket frame information. | ||||||
|  |  * @param data Pointer to the received data. | ||||||
|  |  * @param len Length of the received data. | ||||||
|  |  */ | ||||||
| void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len) | void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len) | ||||||
| { | { | ||||||
|   AwsFrameInfo *info = (AwsFrameInfo *)arg; |   AwsFrameInfo *info = (AwsFrameInfo *)arg; | ||||||
|   if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) |   if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) | ||||||
|   { |   { | ||||||
|     data[len] = 0; |     data[len] = 0; | ||||||
|  |     Debug_pushMessage("Websocket-Message (len: %d): %s\n", len, (char *)data); | ||||||
|  |  | ||||||
|     Debug_pushMessage("Got WebSocket Message: %s \n", (char *)data); |     if (strncmp((char *)data, "btn-", strlen("btn-")) == 0) | ||||||
|  |  | ||||||
|     if (strcmp((char *)data, "start") == 0) |  | ||||||
|     { |     { | ||||||
|       SetDebugportStatus(dbg_Webui, enabled); |       Websocket_HandleButtons(data + strlen("btn-")); | ||||||
|     } |     } | ||||||
|     else if (strcmp((char *)data, "stop") == 0) |     else if (strncmp((char *)data, "set-", strlen("set-")) == 0) | ||||||
|     { |     { | ||||||
|       SetDebugportStatus(dbg_Webui, disabled); |       Websocket_HandleSettings(data + strlen("set-")); | ||||||
|     } |     } | ||||||
|     else if (strcmp((char *)data, "foo") == 0) |     else | ||||||
|     { |     { | ||||||
|       Debug_pushMessage("Got WebSocket Message 'foo' from client\n"); |       Debug_pushMessage("Got unknown Websocket-Message '%s' from client\n", (char *)data); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Handle button commands received via WebSocket. | ||||||
|  |  * | ||||||
|  |  * This function parses a WebSocket string representing button commands, extracts | ||||||
|  |  * the identifier and value components, and performs corresponding actions based on | ||||||
|  |  * the received commands. | ||||||
|  |  * | ||||||
|  |  * @param data The WebSocket data containing button commands. | ||||||
|  |  */ | ||||||
|  | void Websocket_HandleButtons(uint8_t *data) | ||||||
|  | { | ||||||
|  |   char identifier[32]; | ||||||
|  |   char value[32]; | ||||||
|  |  | ||||||
|  |   parseWebsocketString((char *)data, identifier, sizeof(identifier), value, sizeof(value)); | ||||||
|  |  | ||||||
|  |   if (strcmp(identifier, "debugstart") == 0) | ||||||
|  |   { | ||||||
|  |     SetDebugportStatus(dbg_Webui, enabled); | ||||||
|  |   } | ||||||
|  |   else if (strcmp(identifier, "debugstop") == 0) | ||||||
|  |   { | ||||||
|  |     SetDebugportStatus(dbg_Webui, disabled); | ||||||
|  |   } | ||||||
|  |   else if (strcmp(identifier, "settingssave") == 0) | ||||||
|  |   { | ||||||
|  |     globals.requestEEAction = EE_CFG_SAVE; | ||||||
|  |   } | ||||||
|  |   else if (strcmp(identifier, "reboot") == 0) | ||||||
|  |   { | ||||||
|  |     globals.systemStatus = sysStat_Shutdown; | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     Debug_pushMessage("Got unknown Button-id '%s' from ws-client\n", identifier); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Handle settings commands received via WebSocket. | ||||||
|  |  * | ||||||
|  |  * This function parses a WebSocket string representing settings commands, extracts | ||||||
|  |  * the identifier and value components, and updates the system settings accordingly. | ||||||
|  |  * | ||||||
|  |  * @param data The WebSocket data containing settings commands. | ||||||
|  |  */ | ||||||
|  | void Websocket_HandleSettings(uint8_t *data) | ||||||
|  | { | ||||||
|  |   char identifier[32]; | ||||||
|  |   char value[63]; | ||||||
|  |  | ||||||
|  |   parseWebsocketString((char *)data, identifier, sizeof(identifier), value, sizeof(value)); | ||||||
|  |  | ||||||
|  |   if (strcmp(identifier, "wifi-ssid") == 0) | ||||||
|  |   { | ||||||
|  |     strncpy(ConfigData.wifi_client_ssid, value, sizeof(ConfigData.wifi_client_ssid)); | ||||||
|  |   } | ||||||
|  |   else if (strcmp(identifier, "wifi-password") == 0) | ||||||
|  |   { | ||||||
|  |     strncpy(ConfigData.wifi_client_password, value, sizeof(ConfigData.wifi_client_password)); | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     Debug_pushMessage("Got unknown Settings-id and value '%s' from ws-client\n", identifier); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Pushes live debug messages to all WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * This function sends a live debug message to all connected WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * @param Message The debug message to be sent. | ||||||
|  |  */ | ||||||
| void Websocket_PushLiveDebug(String Message) | void Websocket_PushLiveDebug(String Message) | ||||||
| { | { | ||||||
|   webSocket.textAll(Message + "\n"); |   webSocket.textAll("DEBUG:" + Message); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Refreshes client data related to Diagnostic Trouble Codes (DTCs) on WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * This function constructs a DTC-related string and sends it to a specific WebSocket client or | ||||||
|  |  * broadcasts it to all connected WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * @param client_id The ID of the WebSocket client to which the data should be sent. If 0, the data | ||||||
|  |  * will be broadcasted to all connected clients. | ||||||
|  |  */ | ||||||
|  | void Websocket_RefreshClientData_DTCs(uint32_t client_id) | ||||||
|  | { | ||||||
|  |   String temp = "DTC:"; | ||||||
|  |  | ||||||
|  |   // Build DTC-String | ||||||
|  |   if (globals.hasDTC != true) | ||||||
|  |   { | ||||||
|  |     temp.concat(String(DTC_NO_DTC) + ";"); | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|  |     { | ||||||
|  |       if (DTCStorage[i].Number < DTC_LAST_DTC) | ||||||
|  |       { | ||||||
|  |         temp.concat(String(DTCStorage[i].timestamp) + ","); | ||||||
|  |         temp.concat(String(DTCStorage[i].Number) + ","); | ||||||
|  |         temp.concat(String(getSeverityForDTC(DTCStorage[i].Number)) + ","); | ||||||
|  |         temp.concat(String(DTCStorage[i].active) + ","); | ||||||
|  |         temp.concat(String(DTCStorage[i].debugVal) + ";"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (client_id > 0) | ||||||
|  |   { | ||||||
|  |     webSocket.text(client_id, temp); | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     webSocket.textAll(temp); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Refreshes client data related to system status and relevant parameters on WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * This function constructs a status-related string and sends it to a specific WebSocket client or | ||||||
|  |  * broadcasts it to all connected WebSocket clients. It also sends a mapping of the status parameters. | ||||||
|  |  * | ||||||
|  |  * @param client_id The ID of the WebSocket client to which the data should be sent. If 0, the data | ||||||
|  |  * will be broadcasted to all connected clients. | ||||||
|  |  * @param send_mapping Flag indicating whether to send the parameter mapping to the client(s). | ||||||
|  |  */ | ||||||
|  | void Websocket_RefreshClientData_Status(uint32_t client_id, bool send_mapping) | ||||||
|  | { | ||||||
|  |  | ||||||
|  |   if (send_mapping) | ||||||
|  |   { | ||||||
|  |     const char mapping[] = "MAPPING_STATUS:" | ||||||
|  |                            "systemstatus;" | ||||||
|  |                            "activefaction;" | ||||||
|  |                            "time_faction1;" | ||||||
|  |                            "time_faction2;" | ||||||
|  |                            "time_faction3;"; | ||||||
|  |  | ||||||
|  |     if (client_id > 0) | ||||||
|  |       webSocket.text(client_id, mapping); | ||||||
|  |     else | ||||||
|  |       webSocket.textAll(mapping); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String temp = "STATUS:"; | ||||||
|  |  | ||||||
|  |   temp.concat(String(globals.systemStatustxt) + ";"); | ||||||
|  |   temp.concat(String(PersistenceData.activeFaction) + ";"); | ||||||
|  |   temp.concat(String(PersistenceData.faction_1_timer) + ";"); | ||||||
|  |   temp.concat(String(PersistenceData.faction_2_timer) + ";"); | ||||||
|  |   temp.concat(String(PersistenceData.faction_3_timer) + ";"); | ||||||
|  |  | ||||||
|  |   if (client_id > 0) | ||||||
|  |   { | ||||||
|  |     webSocket.text(client_id, temp); | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     webSocket.textAll(temp); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Refreshes client data related to static configuration parameters on WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * This function constructs a static configuration-related string and sends it to a specific WebSocket client or | ||||||
|  |  * broadcasts it to all connected WebSocket clients. It also sends a mapping of the static configuration parameters. | ||||||
|  |  * | ||||||
|  |  * @param client_id The ID of the WebSocket client to which the data should be sent. If 0, the data | ||||||
|  |  * will be broadcasted to all connected clients. | ||||||
|  |  * @param send_mapping Flag indicating whether to send the parameter mapping to the client(s). | ||||||
|  |  */ | ||||||
|  | void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping) | ||||||
|  | { | ||||||
|  |  | ||||||
|  |   if (send_mapping) | ||||||
|  |   { | ||||||
|  |     const char mapping[] = "MAPPING_STATIC:" | ||||||
|  |                            "active_faction_on_reboot;" | ||||||
|  |                            "batteryType;" | ||||||
|  |                            "name_faction1;" | ||||||
|  |                            "name_faction2;" | ||||||
|  |                            "name_faction3;" | ||||||
|  |                            "wifi-ssid;" | ||||||
|  |                            "wifi-pass;"; | ||||||
|  |  | ||||||
|  |     if (client_id > 0) | ||||||
|  |       webSocket.text(client_id, mapping); | ||||||
|  |     else | ||||||
|  |       webSocket.textAll(mapping); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String temp = "STATIC:"; | ||||||
|  |  | ||||||
|  |   temp.concat(String(ConfigData.active_faction_on_reboot) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.batteryType) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.Faction_1_Name) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.Faction_2_Name) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.Faction_3_Name) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.wifi_client_ssid) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.wifi_client_password) + ";"); | ||||||
|  |  | ||||||
|  |   if (client_id > 0) | ||||||
|  |   { | ||||||
|  |     webSocket.text(client_id, temp); | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     webSocket.textAll(temp); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Parse a WebSocket string into identifier and value components. | ||||||
|  |  * | ||||||
|  |  * This function takes a WebSocket string, separates it into identifier and value | ||||||
|  |  * components using the ":" delimiter, and stores them in the specified buffers. | ||||||
|  |  * If no ":" is found, the entire string is considered as the value, and the | ||||||
|  |  * identifier buffer is set to an empty string. | ||||||
|  |  * | ||||||
|  |  * @param data The WebSocket string to parse. | ||||||
|  |  * @param identifierBuffer The buffer to store the identifier component. | ||||||
|  |  * @param identifierBufferSize The size of the identifier buffer. | ||||||
|  |  * @param valueBuffer The buffer to store the value component. | ||||||
|  |  * @param valueBufferSize The size of the value buffer. | ||||||
|  |  */ | ||||||
|  | void parseWebsocketString(char *data, char *identifierBuffer, size_t identifierBufferSize, | ||||||
|  |                           char *valueBuffer, size_t valueBufferSize) | ||||||
|  | { | ||||||
|  |   // Zerlegen des Strings anhand des Trennzeichens ":" | ||||||
|  |   char *token = strtok(data, ":"); | ||||||
|  |  | ||||||
|  |   // Falls der erste Teil des Strings vorhanden ist | ||||||
|  |   if (token != NULL) | ||||||
|  |   { | ||||||
|  |     // Kopieren des ersten Teils in den Buffer für Identifier | ||||||
|  |     strncpy(identifierBuffer, token, identifierBufferSize - 1); | ||||||
|  |     identifierBuffer[identifierBufferSize - 1] = '\0'; // Null-Terminierung sicherstellen | ||||||
|  |  | ||||||
|  |     // Weitere Aufrufe von strtok, um den nächsten Teil zu erhalten | ||||||
|  |     token = strtok(NULL, ":"); | ||||||
|  |  | ||||||
|  |     // Falls der zweite Teil des Strings vorhanden ist | ||||||
|  |     if (token != NULL) | ||||||
|  |     { | ||||||
|  |       // Kopieren des zweiten Teils in den Buffer für Value | ||||||
|  |       strncpy(valueBuffer, token, valueBufferSize - 1); | ||||||
|  |       valueBuffer[valueBufferSize - 1] = '\0'; // Null-Terminierung sicherstellen | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       // Kein zweiter Teil vorhanden, setzen Sie den Buffer für Value auf leer | ||||||
|  |       valueBuffer[0] = '\0'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     // Der erste Teil des Strings fehlt, setzen Sie den Buffer für Identifier auf leer | ||||||
|  |     identifierBuffer[0] = '\0'; | ||||||
|  |  | ||||||
|  |     // Der gesamte String wird als Value betrachtet | ||||||
|  |     strncpy(valueBuffer, data, valueBufferSize - 1); | ||||||
|  |     valueBuffer[valueBufferSize - 1] = '\0'; // Null-Terminierung sicherstellen | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Find the index of a string in an array. | ||||||
|  |  * | ||||||
|  |  * This function searches for the given string in the provided array and returns | ||||||
|  |  * the index of the first occurrence. If the string is not found, it returns -1. | ||||||
|  |  * | ||||||
|  |  * @param searchString The string to search for in the array. | ||||||
|  |  * @param array The array of strings to search within. | ||||||
|  |  * @param arraySize The size of the array. | ||||||
|  |  * | ||||||
|  |  * @return The index of the first occurrence of the string in the array, | ||||||
|  |  *         or -1 if the string is not found. | ||||||
|  |  */ | ||||||
|  | int findIndexByString(const char *searchString, const char *const *array, int arraySize) | ||||||
|  | { | ||||||
|  |   // Durchlaufe das Array und vergleiche jeden String | ||||||
|  |   for (int i = 0; i < arraySize; ++i) | ||||||
|  |   { | ||||||
|  |     if (strcmp(array[i], searchString) == 0) | ||||||
|  |     { | ||||||
|  |       // String gefunden, gib den Index zurück | ||||||
|  |       return i; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // String nicht gefunden, gib -1 zurück | ||||||
|  |   return -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Pushes a notification to all WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * This function sends a live debug message to all connected WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * @param Message The debug message to be sent. | ||||||
|  |  * @param type The type of notification (info, success, warning, error). | ||||||
|  |  *             - Use NotificationType_t::info for informational messages. | ||||||
|  |  *             - Use NotificationType_t::success for successful operation messages. | ||||||
|  |  *             - Use NotificationType_t::warning for warning messages. | ||||||
|  |  *             - Use NotificationType_t::error for error messages. | ||||||
|  |  */ | ||||||
|  | void Websocket_PushNotification(String Message, NotificationType_t type) | ||||||
|  | { | ||||||
|  |   String typeString = ""; | ||||||
|  |   switch (type) | ||||||
|  |   { | ||||||
|  |   case info: | ||||||
|  |     typeString = "info"; | ||||||
|  |     break; | ||||||
|  |   case success: | ||||||
|  |     typeString = "success"; | ||||||
|  |     break; | ||||||
|  |   case warning: | ||||||
|  |     typeString = "warning"; | ||||||
|  |     break; | ||||||
|  |   case error: | ||||||
|  |     typeString = "danger"; | ||||||
|  |     break; | ||||||
|  |   } | ||||||
|  |   webSocket.textAll("NOTIFY:" + typeString + ";" + Message); | ||||||
|  |   Debug_pushMessage("Sending Notification to WebUI: %s\n", typeString); | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user