Compare commits
	
		
			34 Commits
		
	
	
		
			1.03
			...
			cbcdc34e6c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cbcdc34e6c | |||
| 48774a42f4 | |||
| 73c486be73 | |||
| 6574947a80 | |||
| 956cc49e6f | |||
| b1da9449ad | |||
| e54cadcc7c | |||
| 0b2245d7f6 | |||
| 3ceab44a96 | |||
| 91de9f0785 | |||
| 837fd7f558 | |||
| 6cacd8451f | |||
| 5ae054f314 | |||
| 3e571a515d | |||
| a2aa302121 | |||
| d04489819d | |||
| 52026296f2 | |||
| a22f71649a | |||
| ae8eef52ef | |||
| d9ee193e26 | |||
| 170c66ff49 | |||
| 2ad835966c | |||
| a20a351002 | |||
| c9fba23e70 | |||
| 9301607468 | |||
| fc592c4342 | |||
| ab2ab0e0c1 | |||
| 5ee0a23a6d | |||
| cf76ea7cc7 | |||
| 7a2e95c126 | |||
| 1cf0560957 | |||
| b66d175948 | |||
| 0967b6aa65 | |||
| 498d813624 | 
							
								
								
									
										2
									
								
								Software/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								Software/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,9 @@ | |||||||
| data/ | data/ | ||||||
|  | data_src/version | ||||||
| .pio | .pio | ||||||
| .vscode/.browse.c_cpp.db* | .vscode/.browse.c_cpp.db* | ||||||
| .vscode/c_cpp_properties.json | .vscode/c_cpp_properties.json | ||||||
| .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 | ||||||
| @@ -1,14 +1,73 @@ | |||||||
| # SCRIPT TO GZIP CRITICAL FILES FOR ACCELERATED WEBSERVING |  | ||||||
| # see also https://community.platformio.org/t/question-esp32-compress-files-in-data-to-gzip-before-upload-possible-to-spiffs/6274/10 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| import glob | import glob | ||||||
| import shutil | import shutil | ||||||
| import gzip | import gzip | ||||||
| import os | import os | ||||||
|  | import subprocess | ||||||
|  | import platform | ||||||
|  | from os import popen | ||||||
| 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 +106,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 +128,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,12 +161,18 @@ 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 | ||||||
| 
 | 
 | ||||||
| def gzip_binffiles(source, target, env): | def gzip_binffiles(source, target, env): | ||||||
|  |     git_revision = popen('git rev-parse --short HEAD').read().strip() | ||||||
|  |     custom_flash_version = env.GetProjectOption("custom_flash_version", "0.99") | ||||||
|  | 
 | ||||||
|  |     # Format the target file name | ||||||
|  |     target_filename = f"filesystem_{custom_flash_version}_{git_revision}.fs" | ||||||
|     littlefsbin = target[0].get_abspath() |     littlefsbin = target[0].get_abspath() | ||||||
|     targetbin = os.path.join(os.path.dirname(littlefsbin), 'filesystem.fs') |     targetbin = os.path.join(os.path.dirname(littlefsbin), target_filename) | ||||||
|     shutil.copyfile(littlefsbin, targetbin) |     shutil.copyfile(littlefsbin, targetbin) | ||||||
|     gzip_file(targetbin, os.path.join(str(targetbin) + '.gz')) |     gzip_file(targetbin, os.path.join(str(targetbin) + '.gz')) | ||||||
|     os.remove(targetbin) |     os.remove(targetbin) | ||||||
							
								
								
									
										33
									
								
								Software/codegen/run_pre.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Software/codegen/run_pre.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | Import("env")  # pylint: disable=undefined-variable | ||||||
|  | env.Execute("\"$PYTHONEXE\" -m pip install jinja2") | ||||||
|  |  | ||||||
|  | import struct2json | ||||||
|  | import dtcs | ||||||
|  | from os import popen | ||||||
|  |  | ||||||
|  | git_revision = popen('git rev-parse --short HEAD').read().strip() | ||||||
|  |  | ||||||
|  | # Versionsnummern aus platformio.ini holen | ||||||
|  | custom_firmware_version = env.GetProjectOption("custom_firmware_version", "0.99") | ||||||
|  | custom_flash_version = env.GetProjectOption("custom_flash_version", "0.99") | ||||||
|  |  | ||||||
|  | # Versionsnummern aufteilen in Major und Minor | ||||||
|  | fw_major, fw_minor = custom_firmware_version.split('.') | ||||||
|  | fl_major, fl_minor = custom_flash_version.split('.') | ||||||
|  |  | ||||||
|  | # Version in Datei "version" im Ordner "data_src" überschreiben | ||||||
|  | with open('data_src/version', 'w') as version_file: | ||||||
|  |     version_file.write(custom_flash_version) | ||||||
|  |      | ||||||
|  | # Build-Flags setzen | ||||||
|  | env.Replace(PROGNAME="firmware_%s_%s.fw" % (custom_firmware_version, git_revision)) | ||||||
|  | env.Append(CPPDEFINES=[ | ||||||
|  |     ('GIT_REV', '\\"{}\\"'.format(git_revision)), | ||||||
|  |     ('FW_MAJOR', fw_major), | ||||||
|  |     ('FW_MINOR', fw_minor), | ||||||
|  |     ('FL_MAJOR', fl_major), | ||||||
|  |     ('FL_MINOR', fl_minor) | ||||||
|  | ]) | ||||||
|  |  | ||||||
|  | struct2json.struct2json() | ||||||
|  | dtcs.build_dtcs() | ||||||
							
								
								
									
										113
									
								
								Software/codegen/struct2json.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								Software/codegen/struct2json.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | |||||||
|  | import os | ||||||
|  | import time | ||||||
|  | from jinja2 import Environment, FileSystemLoader | ||||||
|  | import re | ||||||
|  |  | ||||||
|  | import filechecksum as fcs | ||||||
|  |  | ||||||
|  | # Pfad zur Eingabedatei und Ausgabedatei | ||||||
|  | input_file = "include/eeprom.h" | ||||||
|  | output_sourcefile = "src/struct2json.cpp" | ||||||
|  | output_headerfile = "include/struct2json.h" | ||||||
|  | # Liste der zu suchenden Variablen/Structs | ||||||
|  | variable_names = ['ConfigData', 'PersistenceData'] | ||||||
|  |  | ||||||
|  | def get_types(file_content, variable_names): | ||||||
|  |     result = {} | ||||||
|  |      | ||||||
|  |     # Entferne Kommentare, um unerwünschte Störungen zu vermeiden | ||||||
|  |     file_content = re.sub(r'\/\*.*?\*\/', '', file_content, flags=re.DOTALL) | ||||||
|  |     file_content = re.sub(r'\/\/.*', '', file_content) | ||||||
|  |      | ||||||
|  |     for var_name in variable_names: | ||||||
|  |         # Erstelle ein reguläres Ausdrucksmuster, um den Typ der Variable zu extrahieren | ||||||
|  |         pattern = re.compile(r'\b(?:extern\s+)?(\w+)\s+' + re.escape(var_name) + r'\s*;') | ||||||
|  |         match = pattern.search(file_content) | ||||||
|  |          | ||||||
|  |         if match: | ||||||
|  |             # Extrahiere den Typ aus dem Treffer | ||||||
|  |             type_match = match.group(1) | ||||||
|  |             result[var_name] = type_match | ||||||
|  |  | ||||||
|  |     return result | ||||||
|  |  | ||||||
|  | def extract_struct_fields(file_content, variable_types): | ||||||
|  |     result = {} | ||||||
|  |  | ||||||
|  |     # Entferne Kommentare, um unerwünschte Störungen zu vermeiden | ||||||
|  |     file_content = re.sub(r'\/\*.*?\*\/', '', file_content, flags=re.DOTALL) | ||||||
|  |     file_content = re.sub(r'\/\/.*', '', file_content) | ||||||
|  |  | ||||||
|  |     for var_name, var_type in variable_types.items(): | ||||||
|  |         # Erstelle ein reguläres Ausdrucksmuster, um das Strukturfeld zu extrahieren | ||||||
|  |         pattern = re.compile(r'typedef\s+struct\s*{([^}]*)}\s*' + re.escape(var_type) + r'\s*;') | ||||||
|  |         match = pattern.search(file_content) | ||||||
|  |  | ||||||
|  |         if match: | ||||||
|  |             # Extrahiere die Felder aus dem Treffer | ||||||
|  |             fields_match = re.findall(r'\b(\w+)\s+(\w+)(?:\[(\d+)\])?\s*;', match.group(1)) | ||||||
|  |             if fields_match: | ||||||
|  |                 result[var_name] = {'type': var_type, 'fields': {}} | ||||||
|  |                 for field_type, field_name, array_size in fields_match: | ||||||
|  |                     if array_size: | ||||||
|  |                         result[var_name]['fields'][field_name] = {'type': field_type, 'size': int(array_size)} | ||||||
|  |                     else: | ||||||
|  |                         result[var_name]['fields'][field_name] = {'type': field_type} | ||||||
|  |  | ||||||
|  |     return result | ||||||
|  |  | ||||||
|  | def struct2json(): | ||||||
|  |     # Überprüfen, ob die Verzeichnisse existieren, andernfalls erstellen | ||||||
|  |     output_dir_source = os.path.dirname(output_sourcefile) | ||||||
|  |     if not os.path.exists(output_dir_source): | ||||||
|  |         os.makedirs(output_dir_source) | ||||||
|  |     output_dir_header = os.path.dirname(output_headerfile) | ||||||
|  |     if not os.path.exists(output_dir_header): | ||||||
|  |         os.makedirs(output_dir_header) | ||||||
|  |  | ||||||
|  |     # Unix-Zeitstempel hinzufügen | ||||||
|  |     timestamp = int(time.time()) | ||||||
|  |  | ||||||
|  |     # Parse structs | ||||||
|  |     with open(input_file, 'r') as file: | ||||||
|  |         content = file.read() | ||||||
|  |  | ||||||
|  |     variable_types = get_types(content, variable_names) | ||||||
|  |     structs = extract_struct_fields(content, variable_types) | ||||||
|  |     checksum = fcs.calculate_checksum(structs) | ||||||
|  |  | ||||||
|  |     env = Environment(loader=FileSystemLoader('codegen/templates', encoding='utf-8')) | ||||||
|  |     # Lade das Jinja2-Template aus der Datei | ||||||
|  |     template_c = env.get_template('struct2json.cpp.j2') | ||||||
|  |     template_h = env.get_template('struct2json.h.j2') | ||||||
|  |  | ||||||
|  |     # Erstelle ein Context-Dictionary mit den erforderlichen Daten | ||||||
|  |     context = { | ||||||
|  |         'timestamp_unix': timestamp, | ||||||
|  |         'timestamp' : time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp)), | ||||||
|  |         'date' : time.strftime('%d.%m.%Y', time.localtime(timestamp)), | ||||||
|  |         'structs': structs, | ||||||
|  |         'checksum': checksum | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     # Überprüfe, ob die Checksummen übereinstimmen | ||||||
|  |     if fcs.read_and_compare_checksum(output_sourcefile, checksum): | ||||||
|  |         print("Keine Änderungen in der Source-Datei erforderlich.") | ||||||
|  |     else: | ||||||
|  |         # Rendere das Template mit den Werten und erhalte den Source-Text | ||||||
|  |         source_text = template_c.render(context) | ||||||
|  |         # Schreibe den generierten Source-Text in die Source-Datei | ||||||
|  |         with open(output_sourcefile, "w", encoding='utf-8') as f: | ||||||
|  |             f.write(source_text) | ||||||
|  |         print(f"Source-Datei wurde erstellt: {output_sourcefile}") | ||||||
|  |  | ||||||
|  |     # Überprüfe, ob die Checksummen übereinstimmen | ||||||
|  |     if fcs.read_and_compare_checksum(output_headerfile, checksum): | ||||||
|  |         print("Keine Änderungen in der Header-Datei erforderlich.") | ||||||
|  |     else: | ||||||
|  |         # Rendere das Template mit den Werten und erhalte den Header-Text | ||||||
|  |         header_text = template_h.render(context) | ||||||
|  |         # Schreibe den generierten Header-Text in die Header-Datei | ||||||
|  |         with open(output_headerfile, "w", encoding='utf-8') as f: | ||||||
|  |             f.write(header_text) | ||||||
|  |         print(f"Header-Datei wurde erstellt: {output_headerfile}") | ||||||
							
								
								
									
										54
									
								
								Software/codegen/templates/dtc_defs.h.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								Software/codegen/templates/dtc_defs.h.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | /** | ||||||
|  |  * @file dtc_defs.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for Diagnostic Trouble Code (DTC) definitions in the DE-Timer application. | ||||||
|  |  * | ||||||
|  |  * This file contains definitions for Diagnostic Trouble Codes (DTC) in the DE-Timer project. | ||||||
|  |  * It includes enums for DTC active status, severity levels, and specific DTC codes. | ||||||
|  |  * The file also defines an array of DTC definitions and a timestamp indicating the generation time. | ||||||
|  |  * | ||||||
|  |  * @note This file is auto-generated by a script on {{ timestamp }}. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   {{ date }} | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef DTC_DEFS_H | ||||||
|  | #define DTC_DEFS_H | ||||||
|  |  | ||||||
|  | #include <stdint.h> | ||||||
|  |  | ||||||
|  | typedef uint32_t DTCNum_t; | ||||||
|  |  | ||||||
|  | typedef enum | ||||||
|  | { | ||||||
|  |   DTC_INACTIVE, | ||||||
|  |   DTC_ACTIVE, | ||||||
|  |   DTC_PREVIOUS | ||||||
|  | } DTCActive_t; | ||||||
|  |  | ||||||
|  | typedef enum | ||||||
|  | { | ||||||
|  |   DTC_NONE, | ||||||
|  |   DTC_INFO, | ||||||
|  |   DTC_WARN, | ||||||
|  |   DTC_CRITICAL | ||||||
|  | } DTCSeverity_t; | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |   DTCNum_t code; | ||||||
|  |   DTCSeverity_t severity; | ||||||
|  | } DTC_t; | ||||||
|  |  | ||||||
|  | {% for dtc in dtc_macros -%} | ||||||
|  | {{ dtc }} | ||||||
|  | {% endfor %} | ||||||
|  | const DTC_t dtc_definitions[] = { | ||||||
|  | {% for struct in dtc_structs -%} | ||||||
|  | {{ struct }} | ||||||
|  | {% endfor -%} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // DTC_DEFS_H | ||||||
|  |  | ||||||
|  | // CODEGENERATOR_CHECKSUM: {{ checksum }} | ||||||
							
								
								
									
										25
									
								
								Software/codegen/templates/struct2json.cpp.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Software/codegen/templates/struct2json.cpp.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | /** | ||||||
|  |  * @file struct2json.cpp | ||||||
|  |  * | ||||||
|  |  * @brief Implementation file for converting structs to JSON objects. | ||||||
|  |  * | ||||||
|  |  * @note This file is auto-generated by a script on {{ timestamp }}. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   {{ date }} | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #include "struct2json.h" | ||||||
|  |  | ||||||
|  | {% for var_name, var_info in structs.items() -%} | ||||||
|  | void generateJsonObject_{{ var_name }}(JsonObject data) | ||||||
|  | { | ||||||
|  |     {% for field_name, field_type in var_info['fields'].items() -%} | ||||||
|  |     data["{{ field_name }}"] = {{ var_name }}.{{ field_name }}; | ||||||
|  |     {% endfor -%} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | {% endfor %} | ||||||
|  |  | ||||||
|  | // CODEGENERATOR_CHECKSUM: {{ checksum }} | ||||||
							
								
								
									
										26
									
								
								Software/codegen/templates/struct2json.h.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Software/codegen/templates/struct2json.h.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | /** | ||||||
|  |  * @file struct2json.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for converting structs to JSON objects. | ||||||
|  |  * | ||||||
|  |  * @note This file is auto-generated by a script on {{ timestamp }}. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   {{ date }} | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _STRUCT2JSON_H_ | ||||||
|  | #define _STRUCT2JSON_H_ | ||||||
|  |  | ||||||
|  | #include <Arduino.h> | ||||||
|  | #include <ArduinoJson.h> | ||||||
|  |  | ||||||
|  | #include "eeprom.h" | ||||||
|  |  | ||||||
|  | {% for var_name, var_info in structs.items() -%} | ||||||
|  | void generateJsonObject_{{ var_name }}(JsonObject data); | ||||||
|  | {% endfor %} | ||||||
|  |  | ||||||
|  | #endif /* _STRUCT2JSON_H_ */ | ||||||
|  |  | ||||||
|  | // CODEGENERATOR_CHECKSUM: {{ checksum }} | ||||||
| @@ -3,7 +3,7 @@ | |||||||
|  |  | ||||||
| <head> | <head> | ||||||
|   <meta charset="utf-8" /> |   <meta charset="utf-8" /> | ||||||
|   <title>%DEVICENAME%</title> |   <title>Dark Emergency Timer</title> | ||||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> |   <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/bootstrap.min.css"> | ||||||
|   <link rel="stylesheet" href="static/css/custom.css"> |   <link rel="stylesheet" href="static/css/custom.css"> | ||||||
| @@ -11,6 +11,8 @@ | |||||||
|   <script src="static/js/jquery.min.js"></script> |   <script src="static/js/jquery.min.js"></script> | ||||||
|   <script src="static/js/bootstrap.min.js"></script> |   <script src="static/js/bootstrap.min.js"></script> | ||||||
|   <script src="static/js/websocket.js"></script> |   <script src="static/js/websocket.js"></script> | ||||||
|  |   <script src="static/js/dtc_table.js"></script> | ||||||
|  |   <script src="static/js/script.js"></script> | ||||||
|   <link rel="apple-touch-icon" sizes="180x180" href="static/img/apple-touch-icon.png"> |   <link rel="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="32x32" href="static/img/favicon-32x32.png"> | ||||||
|   <link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon-16x16.png"> |   <link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon-16x16.png"> | ||||||
| @@ -18,11 +20,22 @@ | |||||||
| </head> | </head> | ||||||
|  |  | ||||||
| <body> | <body> | ||||||
|  |   <!-- Connection-Overlay --> | ||||||
|  |   <div id="overlay"> | ||||||
|  |     <div class="overlay-content"> | ||||||
|  |       <p>Verbinde...</p> | ||||||
|  |       <span class="loader"></span> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <!-- Connection-Overlay --> | ||||||
|  |   <!-- Notification-Container --> | ||||||
|  |   <div id="notification-container" class="notification-container"></div> | ||||||
|  |   <!-- Notification-Container --> | ||||||
|  |  | ||||||
|   <nav class="navbar fixed-top navbar-dark bg-primary" id="navbar1"> |   <nav class="navbar fixed-top navbar-dark bg-primary" id="navbar1"> | ||||||
|     <a class="navbar-brand" href="#"> |     <a class="navbar-brand" href="#"> | ||||||
|       <img src="static/img/logo.png" width="30" height="30" class="d-inline-block align-top mr-1" alt=""> |       <img src="static/img/logo.png" width="30" height="30" class="d-inline-block align-top mr-1" alt=""> | ||||||
|       %DEVICENAME% |       <span class="data-devicename">DE Airsoft Timer</span> | ||||||
|     </a> |     </a> | ||||||
|     <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsingNavbar" |     <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsingNavbar" | ||||||
|       aria-controls="collapsingNavbar" aria-expanded="false" aria-label="Toggle navigation"> |       aria-controls="collapsingNavbar" aria-expanded="false" aria-label="Toggle navigation"> | ||||||
| @@ -35,7 +48,6 @@ | |||||||
|         <li class="nav-item"><a class="nav-link active" role="tab" data-toggle="tab" href="#tab_home">Home</a></li> |         <li class="nav-item"><a class="nav-link active" role="tab" data-toggle="tab" href="#tab_home">Home</a></li> | ||||||
|         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_maintenance">Wartung</a></li> |         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_maintenance">Wartung</a></li> | ||||||
|         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_source">Einstellungen</a></li> |         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_source">Einstellungen</a></li> | ||||||
|         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_sysinfo">Systeminfo</a></li> |  | ||||||
|         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_fwupdate">Update</a></li> |         <li class="nav-item"><a class="nav-link" role="tab" data-toggle="tab" href="#tab_fwupdate">Update</a></li> | ||||||
|  |  | ||||||
|       </ul> |       </ul> | ||||||
| @@ -51,17 +63,17 @@ | |||||||
|         <div class="col text-center"> |         <div class="col text-center"> | ||||||
|           <div class="jumbotron"> |           <div class="jumbotron"> | ||||||
|             <img src="static/img/logo.png" width="120" height="120" class="img-fluid" alt=""> |             <img src="static/img/logo.png" width="120" height="120" class="img-fluid" alt=""> | ||||||
|             <h3 class="pt-3">%DEVICENAME%</h3> |             <h3 class="pt-3"><span class="data-devicename">DE Airsoft Timer</span></h3> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         <!-- Div Group Battery remain --> |         <!-- Div Group Battery remain --> | ||||||
|         <hr /> |         <hr /> | ||||||
|         <p> |         <p> | ||||||
|         <h4>Akku Ladestand</h4> |         <h4>Akkuladestand</h4> | ||||||
|         <div class="progress"> |         <div class="progress"> | ||||||
|           <div class="progress-bar text-light" role="progressbar" aria-valuenow="%BAT_REMAIN_CAPACITY%" |           <div id="battery_level" class="data-battery_level progress-bar text-light" role="progressbar" aria-valuenow="0" | ||||||
|             aria-valuemin="0" aria-valuemax="100" style="width: %BAT_REMAIN_CAPACITY%%"> |             aria-valuemin="0" aria-valuemax="100" style="width: 0%"> | ||||||
|             %BAT_REMAIN_CAPACITY%% |             0 | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         </p> |         </p> | ||||||
| @@ -70,7 +82,7 @@ | |||||||
|         <hr /> |         <hr /> | ||||||
|         <p> |         <p> | ||||||
|         <h4>aktueller Modus</h4> |         <h4>aktueller Modus</h4> | ||||||
|         <input class="form-control" type="text" placeholder="%SYSTEM_STATUS%" readonly> |         <input class="data-systemstatus form-control" type="text" id="sysstatus" readonly> | ||||||
|         </p> |         </p> | ||||||
|         <!-- Div Group current Mode --> |         <!-- Div Group current Mode --> | ||||||
|         <!-- Div Group Faction Points --> |         <!-- Div Group Faction Points --> | ||||||
| @@ -79,35 +91,35 @@ | |||||||
|         <h4>aktueller Punktestand</h4> |         <h4>aktueller Punktestand</h4> | ||||||
|         <div class="container-fluid"> |         <div class="container-fluid"> | ||||||
|           <div class="row"> |           <div class="row"> | ||||||
|             <div class="col text-center %FACTION_1_ACTIVE% text-white p-3">%NAME_FAC_1%</div> |             <div id="header_faction1" class="col text-center data-name_faction1 text-white p-3">%NAME_FAC_1%</div> | ||||||
|             <div class="col text-center %FACTION_2_ACTIVE% text-white p-3">%NAME_FAC_2%</div> |             <div id="header_faction2" class="col text-center data-name_faction2 text-white p-3">%NAME_FAC_2%</div> | ||||||
|             <div class="col text-center %FACTION_3_ACTIVE% text-white p-3">%NAME_FAC_3%</div> |             <div id="header_faction3" class="col text-center data-name_faction3 text-white p-3">%NAME_FAC_3%</div> | ||||||
|           </div> |           </div> | ||||||
|           <div class="row"> |           <div class="row"> | ||||||
|             <div class="col bg-dark text-white p-3"><img src="static/img/logo_fac1.png" |             <div class="col bg-dark text-white p-3 data-activefaction faction-logo faction1"> | ||||||
|                 class="rounded mx-auto img-fluid d-block" alt="..."> |               <img src="static/img/logo_fac1.png" class="rounded mx-auto img-fluid d-block" alt="..."> | ||||||
|             </div> |             </div> | ||||||
|             <div class="col bg-dark text-white p-3"><img src="static/img/logo_fac2.png" |             <div class="col bg-dark text-white p-3 data-activefaction faction-logo faction2"> | ||||||
|                 class="rounded mx-auto img-fluid d-block" alt="..."> |               <img src="static/img/logo_fac2.png" class="rounded mx-auto img-fluid d-block" alt="..."> | ||||||
|             </div> |             </div> | ||||||
|             <div class="col bg-dark text-white p-3"><img src="static/img/logo_fac3.png" |             <div class="col bg-dark text-white p-3 data-activefaction faction-logo faction3"> | ||||||
|                 class="rounded mx-auto img-fluid d-block" alt="..."> |               <img src="static/img/logo_fac3.png" class="rounded mx-auto img-fluid d-block" alt="..."> | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
|           <div class="row"> |           <div class="row"> | ||||||
|             <div class="col text-center bg-secondary text-white p-3">%POINTS_FAC_1%</div> |             <div id="time_faction1" class="data-time_faction1 col text-center bg-secondary text-white p-3 format-time">0</div> | ||||||
|             <div class="col text-center bg-secondary text-white p-3">%POINTS_FAC_2%</div> |             <div id="time_faction2" class="data-time_faction2 col text-center bg-secondary text-white p-3 format-time">0</div> | ||||||
|             <div class="col text-center bg-secondary text-white p-3">%POINTS_FAC_3%</div> |             <div id="time_faction3" class="data-time_faction3 col text-center bg-secondary text-white p-3 format-time">0</div> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         </p> |         </p> | ||||||
|         <!-- Div GroupFaction Points --> |         <!-- Div GroupFaction Points --> | ||||||
|         <!-- Div Group DTC Table --> |         <!-- Div Group DTC Table --> | ||||||
|         <div %SHOW_DTC_TABLE%> |         <div id="dtc_container" hidden> | ||||||
|           <hr /> |           <hr /> | ||||||
|           <p> |           <p> | ||||||
|           <h4>Fehlercodes</h4> |           <h4>Fehlercodes</h4> | ||||||
|           <table class="table"> |           <table class="table" id="dtc_table"> | ||||||
|             <tbody> |             <tbody> | ||||||
|               <tr> |               <tr> | ||||||
|                 <th class="col-6" scope="col">Zeitstempel</th> |                 <th class="col-6" scope="col">Zeitstempel</th> | ||||||
| @@ -115,7 +127,6 @@ | |||||||
|                 <th class="col-2" scope="col">Schwere</th> |                 <th class="col-2" scope="col">Schwere</th> | ||||||
|                 <th class="col-2" scope="col">Aktiv</th> |                 <th class="col-2" scope="col">Aktiv</th> | ||||||
|               </tr> |               </tr> | ||||||
|               %DTC_TABLE% |  | ||||||
|             </tbody> |             </tbody> | ||||||
|           </table> |           </table> | ||||||
|           </p> |           </p> | ||||||
| @@ -131,435 +142,245 @@ | |||||||
|         <hr /> |         <hr /> | ||||||
|         <p> |         <p> | ||||||
|         <h4>Punkte zurücksetzen</h4> |         <h4>Punkte zurücksetzen</h4> | ||||||
|         <form action="post.htm" method="POST" class="form-horizontal"> |         <div class="form-group row"> | ||||||
|           <div class="form-group row"> |           <div class="col text-center"> | ||||||
|             <div class="col text-center"> |             <button id="reset-timer" class="btn-wsevent btn btn-outline-primary ml-2">Timer zurücksetzen</button> | ||||||
|               <button name="resetpoints" type="submit" class="btn btn-outline-primary">Reset</button> |  | ||||||
|             </div> |  | ||||||
|           </div> |           </div> | ||||||
|         </form> |         </div> | ||||||
|         </p> |         </p> | ||||||
|         <!-- Div Group Reset Timers  --> |         <!-- Div Group Reset Timers  --> | ||||||
|         <!-- Div Group EEPROM formatting --> |      <!-- Div Group LiveDebug --> | ||||||
|         <hr /> |      <hr /> | ||||||
|         <p> |      <p> | ||||||
|         <h4>EEPROM formatieren</h4> |      <h4>Live Debug</h4> | ||||||
|         <div class="alert alert-primary alert-dismissable show fade" role="alert"> |      <div class="form-group row"> | ||||||
|           <button type="button" class="close" data-dismiss="alert" aria-label="Close"> |        <textarea class="form-control" spellcheck="false" id="livedebug-out" rows="3" readonly></textarea> | ||||||
|             <span aria-hidden="true">×</span> |      </div> | ||||||
|           </button> |      <div class="form-group row"> | ||||||
|           <strong>Achtung!</strong><br> |        <div class="col text-center"> | ||||||
|           Das Formatieren der EEPROM-Bereiche sollte nur ausgeführt werden wenn es unbedingt erforderlich ist! |          <button id="debugstart" class="btn-wsevent btn btn-outline-primary ml-2">Start</button> | ||||||
|           Hierdurch werden alle Einstellungen zurück gesetzt bzw. alle Betriebsdaten gehen verloren. |          <button id="debugstop" class="btn-wsevent btn btn-outline-primary ml-2">Stop</button> | ||||||
|           Folgende Situationen erfordern unter anderem eine Formatierung: |        </div> | ||||||
|           - Erstinitialisierung (bei neu aufgebautem Gerät) |      </div> | ||||||
|           - Firmware-Update (nur wenn es die Release-Notes fordern) |      </p> | ||||||
|         </div> |      <!-- Div Group LiveDebug --> | ||||||
|         <form action="post.htm" method="POST" class="form-horizontal"> |      <!-- Div Group Device Reboot --> | ||||||
|           <div class="form-group row"> |      <hr /> | ||||||
|             <div class="offset-4 col-8"> |      <p> | ||||||
|               <div class="form-check"> |      <h4>Gerät neustarten</h4> | ||||||
|                 <input class="form-check-input" type="checkbox" name="reset_ee_cfg" id="reset_ee_cfg"> |        <div class="form-group row"> | ||||||
|                 <label class="form-check-label" for="reset_ee_cfg"> |          <div class="col text-center"> | ||||||
|                   Bereich "CFG" |            <button id="reboot" class="btn-wsevent confirm btn btn-outline-primary">Reboot</button> | ||||||
|                 </label> |          </div> | ||||||
|               </div> |        </div> | ||||||
|               <div class="form-check"> |      </p> | ||||||
|                 <input class="form-check-input" type="checkbox" name="reset_ee_pds" id="reset_ee_pds"> |      <!-- Div Group Device Reboot --> | ||||||
|                 <label class="form-check-label" for="reset_ee_pds"> |    </div> | ||||||
|                   Bereich "PDS" |    <!-- Div Tab Maintenance --> | ||||||
|                 </label> |  | ||||||
|  |          <!-- Div Tab Settings--> | ||||||
|  |          <div id="tab_source" class="tab-pane fade" role="tabpanel"> | ||||||
|  |           <h3>Einstellungen</h3> | ||||||
|  |           <!-- Div Group Battery Type --> | ||||||
|  |           <hr /> | ||||||
|  |           <p> | ||||||
|  |             <h4>Akku</h4> | ||||||
|  |             <div class="form-group row"> | ||||||
|  |               <label for="batterytype" class="control-label col-4">Akku-Variante</label> | ||||||
|  |               <div class="col-8"> | ||||||
|  |                 <select id="batterytype" class="set-wsevent data-batterytype select form-control">  | ||||||
|  |                   <option value="Undefined">Undefined</option> | ||||||
|  |                   <option value="LiPo 3S">LiPo 3S</option> | ||||||
|  |                   <option value="LiPo 2S">LiPo 2S</option>             | ||||||
|  |                 </select> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </p> | ||||||
|           <div class="form-group row"> |           <!-- Div Group Battery Type --> | ||||||
|             <div class="col text-center"> |           <!-- Div Group Timer Settings --> | ||||||
|               <button name="reset_ee_btn" type="submit" class="btn btn-outline-primary">EEPROM formatieren</button> |           <hr /> | ||||||
|             </div> |           <p> | ||||||
|           </div> |           <h4>Timer Einstellungen</h4> | ||||||
|         </form> |             <div class="form-group row"> | ||||||
|         </p> |               <label for="active_faction_on_reboot" class="control-label col-4">Aktive Fraktion wiederherstellen</label> | ||||||
|         <!-- Div Group EEPROM formatting --> |               <div class="col-8"> | ||||||
|         <!-- Div Group Device Reboot --> |                 <div class="form-check"> | ||||||
|         <hr /> |                   <input class="set-wsevent data-active_faction_on_reboot form-check-input" type="checkbox" id="active_faction_on_reboot"> | ||||||
|         <p> |                   <label class="form-check-label" for="active_faction_on_reboot"> | ||||||
|         <h4>Gerät neustarten</h4> |                     aktivieren | ||||||
|         <form action="post.htm" method="POST" class="form-horizontal"> |                   </label> | ||||||
|           <div class="form-group row"> |  | ||||||
|             <div class="col text-center"> |  | ||||||
|               <button name="reboot" type="submit" class="btn btn-outline-primary">Reboot</button> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|         </form> |  | ||||||
|         </p> |  | ||||||
|         <!-- Div Group Device Reboot --> |  | ||||||
|       </div> |  | ||||||
|       <!-- Div Tab Maintenance --> |  | ||||||
|       <!-- Div Tab Settings--> |  | ||||||
|       <div id="tab_source" class="tab-pane fade" role="tabpanel"> |  | ||||||
|         <h3>Einstellungen</h3> |  | ||||||
|         <!-- Div Group Battery Type --> |  | ||||||
|         <hr /> |  | ||||||
|         <p> |  | ||||||
|         <form action="post.htm" method="POST" class="form-horizontal"> |  | ||||||
|           <h4>Akku-Variante</h4> |  | ||||||
|           <div class="form-group row"> |  | ||||||
|             <label for="battery_select" class="control-label col-4">Akku</label> |  | ||||||
|             <div class="col-8"> |  | ||||||
|               <select id="battery_select" name="battery_select" class="select form-control"> |  | ||||||
|                 %BATTERY_SELECT_OPTIONS% |  | ||||||
|               </select> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|           <h4>Timer-Einstellungen</h4> |  | ||||||
|           <div class="form-group row"> |  | ||||||
|             <label for="factionreboot_cont" class="control-label col-4">active Faction Recovery</label> |  | ||||||
|             <div class="col-8"> |  | ||||||
|               <div class="form-check"> |  | ||||||
|                 <input class="form-check-input" type="checkbox" name="factionreboot_cont" id="factionreboot_cont" |  | ||||||
|                   %FACTIONREBOOT_CHECKED%> |  | ||||||
|                 <label class="form-check-label" for="factionreboot_cont"> |  | ||||||
|                   aktive Faktion beim booten wiederherstellen ? |  | ||||||
|                 </label> |  | ||||||
|               </div> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|           <h4>Faktionsbezeichnungen</h4> |  | ||||||
|           <div class="alert alert-primary alert-dismissable show fade" role="alert"> |  | ||||||
|             <button type="button" class="close" data-dismiss="alert" aria-label="Close"> |  | ||||||
|               <span aria-hidden="true">×</span> |  | ||||||
|             </button> |  | ||||||
|             <strong>Achtung!</strong><br> |  | ||||||
|             Faktionsbezeichnungen können nur aus ASCII-Zeichen bestehen, also A-Z, a-z und 0-9 |  | ||||||
|           </div> |  | ||||||
|           <div class="form-group row"> |  | ||||||
|             <label for="faction_1_name" class="control-label col-4">Faktion 1</label> |  | ||||||
|             <div class="col-8"> |  | ||||||
|               <div class="input-group"> |  | ||||||
|                 <input id="faction_1_name" name="faction_1_name" value="%NAME_FAC_1%" type="text" class="form-control" pattern="[A-Za-z0-9 _-]{1,32}"> |  | ||||||
|                 <div class="input-group-append"> |  | ||||||
|                   <span class="input-group-text">max 32 Zeichen</span> |  | ||||||
|                 </div> |                 </div> | ||||||
|               </div> |               </div> | ||||||
|             </div>       |             </div>       | ||||||
|           </div> |             <div class="form-group row"> | ||||||
|           <div class="form-group row"> |               <label for="name_faction1" class="control-label col-4">Faktion 1</label> | ||||||
|             <label for="faction_2_name" class="control-label col-4">Faktion 2</label> |               <div class="col-8"> | ||||||
|             <div class="col-8"> |                 <div class="input-group"> | ||||||
|               <div class="input-group"> |                   <input id="name_faction1" type="text" class="set-wsevent data-name_faction1 form-control" required="required"> | ||||||
|                 <input id="faction_2_name" name="faction_2_name" value="%NAME_FAC_2%" type="text" class="form-control" pattern="[A-Za-z0-9 _-]{1,32}"> |                   <div class="input-group-append"> | ||||||
|                 <div class="input-group-append"> |                     <span class="input-group-text">max. 32 Zeichen</span> | ||||||
|                   <span class="input-group-text">max 32 Zeichen</span> |                   </div> | ||||||
|                 </div> |                 </div> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|           </div> |             <div class="form-group row"> | ||||||
|           <div class="form-group row"> |               <label for="name_faction2" class="control-label col-4">Faktion 2</label> | ||||||
|             <label for="faction_3_name" class="control-label col-4">Faktion 3</label> |               <div class="col-8"> | ||||||
|             <div class="col-8"> |                 <div class="input-group"> | ||||||
|               <div class="input-group"> |                   <input id="name_faction2" type="text" class="set-wsevent data-name_faction2 form-control" required="required"> | ||||||
|                 <input id="faction_3_name" name="faction_3_name" value="%NAME_FAC_3%" type="text" class="form-control" pattern="[A-Za-z0-9 _-]{1,32}"> |                   <div class="input-group-append"> | ||||||
|                 <div class="input-group-append"> |                     <span class="input-group-text">max. 32 Zeichen</span> | ||||||
|                   <span class="input-group-text">max 32 Zeichen</span> |                   </div> | ||||||
|                 </div> |                 </div> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|           </div> |             <div class="form-group row"> | ||||||
|  |               <label for="name_faction3" class="control-label col-4">Faktion 3</label> | ||||||
|  |               <div class="col-8"> | ||||||
|  |                 <div class="input-group"> | ||||||
|  |                   <input id="name_faction3" type="text" class="set-wsevent data-name_faction3 form-control" required="required"> | ||||||
|  |                   <div class="input-group-append"> | ||||||
|  |                     <span class="input-group-text">max. 32 Zeichen</span> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           <!-- Div Group Timer Settings --> | ||||||
|  |         <!-- Div Group Save Button--> | ||||||
|  |         <hr /> | ||||||
|  |         <p> | ||||||
|           <div class="form-group row"> |           <div class="form-group row"> | ||||||
|             <div class="col text-center"> |             <div class="col text-center"> | ||||||
|               <button name="settingssave" type="submit" class="btn btn-outline-primary">Übernehmen</button> |               <button id="settingssave" class="btn-wsevent btn btn-outline-primary">Speichern</button> | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
|         </form> |  | ||||||
|         </p> |         </p> | ||||||
|         <!-- Div Group Battery Type --> |  | ||||||
|       </div> |       </div> | ||||||
|       <!-- Div Tab Settings --> |       <!-- Div Tab Settings --> | ||||||
|  |  | ||||||
|       <!-- Div Tab SystemInfo --> |     <!-- Div Tab Firmware Update--> | ||||||
|       <div id="tab_sysinfo" class="tab-pane fade" role="tabpanel"> |     <div id="tab_fwupdate" class="tab-pane fade" role="tabpanel"> | ||||||
|         <h3>Systeminfo</h3> |       <h3>Firmware</h3> | ||||||
|         <!-- Div Group Sysinfo:Geraeteinfo --> |       <!-- Div Group VersionInfo --> | ||||||
|         <hr /> |       <hr /> | ||||||
|         <p> |       <p> | ||||||
|         <h4>Gerät</h4> |       <h4>Version-Info</h4> | ||||||
|         <table class="table"> |       <table class="table"> | ||||||
|           <tbody> |         <tbody> | ||||||
|             <tr> |           <tr> | ||||||
|               <th class="col-7" scope="col">Parameter</td> |             <th class="col-7" scope="col">Parameter</td> | ||||||
|               <th class="col-5" scope="col">Value</td> |             <th class="col-5" scope="col">Value</td> | ||||||
|             </tr> |           </tr> | ||||||
|             <tr> |           <tr> | ||||||
|               <td>Hostname</td> |             <td>Firmware Version</td> | ||||||
|               <td>%HOSTNAME%</td> |             <td><span class="data-fw-version"></span></td> | ||||||
|             </tr> |           </tr> | ||||||
|             <tr> |           <tr> | ||||||
|               <td>Device-ID</td> |             <td>Flash Version</td> | ||||||
|               <td>%DEVICENAME_ID%</td> |             <td><span class="data-flash-version"></span></td> | ||||||
|             </tr> |           </tr> | ||||||
|             <tr> |           <tr> | ||||||
|               <td>Battery Voltage</td> |             <td>Git Revision</td> | ||||||
|               <td>%BAT_VOLTAGE%V</td> |             <td><span class="data-git-revision"></span></td> | ||||||
|             </tr> |           </tr> | ||||||
|             <tr> |       </table> | ||||||
|               <td>Battery Remain</td> |       </p> | ||||||
|               <td>%BAT_REMAIN_CAPACITY%%</td> |       <!-- Div Group VersionInfo --> | ||||||
|             </tr> |       <!-- Div Group EEPROM Backup --> | ||||||
|         </table> |       <hr /> | ||||||
|         </p> |       <p> | ||||||
|         <!-- Div Group Sysinfo:Geraeteinfo --> |       <h4>EEPROM-Backup</h4> | ||||||
|         <!-- Div Group Sysinfo:Settings --> |       <div class="form-group row"> | ||||||
|         <hr /> |         <div class="col text-center"> | ||||||
|         <p> |           <a class="btn btn-outline-primary" href="eejson" role="button" id="ee-backup-download">Download</a> | ||||||
|         <h4>Einstellungen</h4> |         </div> | ||||||
|         <table class="table"> |       </div> | ||||||
|           <tbody> |       </p> | ||||||
|             <tr> |       <!-- Div Group EEPROM Backup --> | ||||||
|               <th class="col-7" scope="col">Parameter</td> |       <!-- Div Group EEPROM Restore --> | ||||||
|               <th class="col-5" scope="col">Value</td> |       <hr /> | ||||||
|             </tr> |       <p> | ||||||
|             <tr> |       <h4>EEPROM-Restore</h4> | ||||||
|               <td>Battery_type</td> |       <form method='POST' action='eeRestore' enctype='multipart/form-data'> | ||||||
|               <td>%BATTERY_TYPE%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>Faction_recovery</td> |  | ||||||
|               <td>%FACTION_RECOVERY%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>EEPROM Version</td> |  | ||||||
|               <td>%EEPROM_VERSION%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>Checksum</td> |  | ||||||
|               <td>%CONFIG_CHECKSUM%</td> |  | ||||||
|             </tr> |  | ||||||
|           </tbody> |  | ||||||
|         </table> |  | ||||||
|         </p> |  | ||||||
|         <!-- Div Group Sysinfo:Settings --> |  | ||||||
|         <!-- Div Group Sysinfo:Persistance --> |  | ||||||
|         <hr /> |  | ||||||
|         <p> |  | ||||||
|         <h4>Betriebsdaten</h4> |  | ||||||
|         <table class="table"> |  | ||||||
|           <tbody> |  | ||||||
|             <tr> |  | ||||||
|               <th class="col-7" scope="col">Parameter</td> |  | ||||||
|               <th class="col-5" scope="col">Value</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>writeCycleCounter</td> |  | ||||||
|               <td>%WRITE_CYCLE_COUNT%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>PersistenceMarker</td> |  | ||||||
|               <td>%PERSISTENCE_MARKER%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>activeFaction</td> |  | ||||||
|               <td>%ACTIVE_FACTION%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>faction_1_timer</td> |  | ||||||
|               <td>%POINTS_FAC_1%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>faction_2_timer</td> |  | ||||||
|               <td>%POINTS_FAC_2%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>faction_3_timer</td> |  | ||||||
|               <td>%POINTS_FAC_3%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>checksum</td> |  | ||||||
|               <td>%PERSISTANCE_CHECKSUM%</td> |  | ||||||
|             </tr> |  | ||||||
|         </table> |  | ||||||
|         </p> |  | ||||||
|         <!-- Div Group Sysinfo:Persistance --> |  | ||||||
|         <!-- Div Group LiveDebug --> |  | ||||||
|         <hr /> |  | ||||||
|         <p> |  | ||||||
|         <h4>Live Debug</h4> |  | ||||||
|         <div class="form-group row"> |         <div class="form-group row"> | ||||||
|           <textarea class="form-control" spellcheck="false" id="livedebug-out" rows="3" readonly></textarea> |           <div class="custom-file"> | ||||||
|  |             <input type="file" name="ee-restore-file" class="custom-file-input" id="ee-restore-file" accept=".ee.json" | ||||||
|  |               required /> | ||||||
|  |             <label class="custom-file-label" for="ee-restore-file">EEPROM-Backup auswählen</label> | ||||||
|  |           </div> | ||||||
|         </div> |         </div> | ||||||
|         <div class="form-group row"> |         <div class="form-group row"> | ||||||
|           <div class="col text-center"> |           <div class="col text-center"> | ||||||
|             <button id="btn-ws-start" class="btn btn-outline-primary">Start</button> |             <button name="submit" type="submit" class="btn btn-outline-primary">Restore starten</button> | ||||||
|             <button id="btn-ws-stop" class="btn btn-outline-primary ml-2">Stop</button> |           </div> | ||||||
|  |         </div> | ||||||
|  |       </form> | ||||||
|  |       </p> | ||||||
|  |       <!-- Div Group EEPROM Restore --> | ||||||
|  |       <!-- Div Group Firmware Update --> | ||||||
|  |       <hr /> | ||||||
|  |       <p> | ||||||
|  |       <h4>Firmware-Update</h4> | ||||||
|  |       <form method='POST' action='doUpdate' enctype='multipart/form-data'> | ||||||
|  |         <div class="form-group row"> | ||||||
|  |           <div class="custom-file"> | ||||||
|  |             <input type="file" name="fw-update-file" class="custom-file-input" id="fw-update-file" | ||||||
|  |               accept=".fw.bin,.fs.gz" required /> | ||||||
|  |             <label class="custom-file-label" for="fw-update-file">Firmware-Update auswählen</label> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         </p> |  | ||||||
|         <!-- Div Group LiveDebug --> |  | ||||||
|       </div> |  | ||||||
|       <!-- Div Tab SystemInfo --> |  | ||||||
|  |  | ||||||
|       <!-- Div Tab Firmware Update--> |  | ||||||
|       <div id="tab_fwupdate" class="tab-pane fade" role="tabpanel"> |  | ||||||
|         <h3>Firmware</h3> |  | ||||||
|         <!-- Div Group VersionInfo --> |  | ||||||
|         <hr /> |  | ||||||
|         <p> |  | ||||||
|         <h4>Version-Info</h4> |  | ||||||
|         <table class="table"> |  | ||||||
|           <tbody> |  | ||||||
|             <tr> |  | ||||||
|               <th class="col-7" scope="col">Parameter</td> |  | ||||||
|               <th class="col-5" scope="col">Value</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>Firmware Version</td> |  | ||||||
|               <td>%SW_VERSION%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>Flash Version</td> |  | ||||||
|               <td>%FS_VERSION%</td> |  | ||||||
|             </tr> |  | ||||||
|             <tr> |  | ||||||
|               <td>Git Revision</td> |  | ||||||
|               <td>%GIT_REV%</td> |  | ||||||
|             </tr> |  | ||||||
|         </table> |  | ||||||
|         </p> |  | ||||||
|         <!-- Div Group VersionInfo --> |  | ||||||
|         <!-- Div Group EEPROM Backup --> |  | ||||||
|         <hr /> |  | ||||||
|         <p> |  | ||||||
|         <h4>EEPROM-Backup</h4> |  | ||||||
|         <div class="form-group row"> |         <div class="form-group row"> | ||||||
|           <div class="col text-center"> |           <div class="col text-center"> | ||||||
|             <a class="btn btn-outline-primary" href="eejson" role="button" id="ee-backup-download">Download</a> |             <button name="submit" type="submit" class="btn btn-outline-primary">Update starten</button> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|         </p> |       </form> | ||||||
|         <!-- Div Group EEPROM Backup --> |       </p> | ||||||
|         <!-- Div Group EEPROM Restore --> |       <!-- Div Group Firmware Update --> | ||||||
|         <hr /> |  | ||||||
|         <p> |  | ||||||
|         <h4>EEPROM-Restore</h4> |  | ||||||
|         <form method='POST' action='eeRestore' enctype='multipart/form-data'> |  | ||||||
|           <div class="form-group row"> |  | ||||||
|             <div class="custom-file"> |  | ||||||
|               <input type="file" name="ee-restore-file" class="custom-file-input" id="ee-restore-file" accept=".ee.json" |  | ||||||
|                 required /> |  | ||||||
|               <label class="custom-file-label" for="ee-restore-file">EEPROM-Backup auswählen</label> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|           <div class="form-group row"> |  | ||||||
|             <div class="col text-center"> |  | ||||||
|               <button name="submit" type="submit" class="btn btn-outline-primary">Restore starten</button> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|         </form> |  | ||||||
|         </p> |  | ||||||
|         <!-- Div Group EEPROM Restore --> |  | ||||||
|         <!-- Div Group Firmware Update --> |  | ||||||
|         <hr /> |  | ||||||
|         <p> |  | ||||||
|         <h4>Firmware-Update</h4> |  | ||||||
|         <form method='POST' action='doUpdate' enctype='multipart/form-data'> |  | ||||||
|           <div class="form-group row"> |  | ||||||
|             <div class="custom-file"> |  | ||||||
|               <input type="file" name="fw-update-file" class="custom-file-input" id="fw-update-file" |  | ||||||
|                 accept=".fw.bin,.fs.gz" required /> |  | ||||||
|               <label class="custom-file-label" for="fw-update-file">Firmware-Update auswählen</label> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|           <div class="form-group row"> |  | ||||||
|             <div class="col text-center"> |  | ||||||
|               <button name="submit" type="submit" class="btn btn-outline-primary">Update starten</button> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|         </form> |  | ||||||
|         </p> |  | ||||||
|         <!-- Div Group Firmware Update --> |  | ||||||
|       </div> |  | ||||||
|       <!-- Div Tab Firmware Update--> |  | ||||||
|     </div> |     </div> | ||||||
|     <!-- Tabs Content --> |     <!-- Div Tab Firmware Update--> | ||||||
|   </main> |   </div> | ||||||
|  |   <!-- Tabs Content --> | ||||||
|  | </main> | ||||||
|  |  | ||||||
|  | <!-- Footer --> | ||||||
|  |  | ||||||
|   <!-- Footer --> | <footer class="page-footer navbar-dark bg-primary font-small fixed-bottom"> | ||||||
|  |   <div class="container-fluid text-center"> | ||||||
|   <footer class="page-footer navbar-dark bg-primary font-small fixed-bottom"> |     <div class="footer-copyright text-center py-3"> | ||||||
|     <div class="container-fluid text-center"> |       <span class="text-muted">© 2023 - | ||||||
|       <div class="footer-copyright text-center py-3"> |         <a class="text-reset fw-bold" href="https://eventronics.de/">Marcel Peterkau</a></span> | ||||||
|         <span class="text-muted">© 2023 - |  | ||||||
|           <a class="text-reset fw-bold" href="https://hiabuto.de/">Hiabuto Defense Systems</a></span> |  | ||||||
|       </div> |  | ||||||
|     </div> |     </div> | ||||||
|   </footer> |   </div> | ||||||
|  | </footer> | ||||||
|  |  | ||||||
|   <!-- Footer --> | <!-- Footer --> | ||||||
|  |  | ||||||
|   <!-- Modal Dialog --> | <!-- Modal Dialog --> | ||||||
|  |  | ||||||
|   <div class="modal fade" id="dtcModal" tabindex="-1" role="dialog" aria-labelledby="dtcModalLabel" aria-hidden="true"> | <div class="modal fade" id="dtcModal" tabindex="-1" role="dialog" aria-labelledby="dtcModalLabel" aria-hidden="true"> | ||||||
|     <div class="modal-dialog modal-dialog-centered" role="document"> |   <div class="modal-dialog modal-dialog-centered" role="document"> | ||||||
|       <div class="modal-content"> |     <div class="modal-content"> | ||||||
|         <div class="modal-header"> |       <div class="modal-header"> | ||||||
|           <h5 class="modal-title" id="dtcModalLabel">DTC-Description</h5> |         <h5 class="modal-title" id="dtcModalLabel">DTC-Description</h5> | ||||||
|           <button type="button" class="close" data-dismiss="modal" aria-label="Close"> |         <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||||||
|             <span aria-hidden="true">×</span> |           <span aria-hidden="true">×</span> | ||||||
|           </button> |         </button> | ||||||
|         </div> |       </div> | ||||||
|         <div class="modal-body"> |       <div class="modal-body"> | ||||||
|           <p class="dtc-desc">DTC Description</p> |         <p class="dtc-desc">DTC Description</p> | ||||||
|           <p class="dtc-debugval">DTC DebugVal</p> |         <p class="dtc-debugval">DTC DebugVal</p> | ||||||
|         </div> |       </div> | ||||||
|         <div class="modal-footer"> |       <div class="modal-footer"> | ||||||
|           <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Close</button> |         <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Close</button> | ||||||
|         </div> |  | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|   <!-- Modal Dialog --> | <!-- Modal Dialog --> | ||||||
|  |  | ||||||
|   <script> |  | ||||||
|     $('.navbar-nav>li>a').on('click', function () { |  | ||||||
|       $('.navbar-collapse').collapse('hide'); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     document.querySelector('.custom-file-input').addEventListener('change', function (e) { |  | ||||||
|       var fileName = document.getElementById("fw-update-file").files[0].name; |  | ||||||
|       var nextSibling = e.target.nextElementSibling |  | ||||||
|       nextSibling.innerText = fileName |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     $(document).ready(function () { |  | ||||||
|       $("tr[data-dtc]").each(function (i) { |  | ||||||
|         $(this).attr('data-toggle', "modal"); |  | ||||||
|         $(this).attr('data-target', "#dtcModal"); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     $('#dtcModal').on('show.bs.modal', function (event) { |  | ||||||
|       var dtctr = $(event.relatedTarget) |  | ||||||
|       var dtc = dtctr.data('dtc') |  | ||||||
|       var debugval = dtctr.data('debugval') |  | ||||||
|       var modal = $(this) |  | ||||||
|       $.getJSON('static/tt_dtc/dtc_' + dtc + '.json', function (data) { |  | ||||||
|         modal.find('.modal-title').text(data.title) |  | ||||||
|         modal.find('.dtc-desc').text(data.description) |  | ||||||
|         if (debugval > 0) { |  | ||||||
|           modal.find('.dtc-debugval').text("Debugvalue: " + debugval) |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|           modal.find('.dtc-debugval').remove() |  | ||||||
|         } |  | ||||||
|       }).fail(function () { |  | ||||||
|         console.log("An error has occurred."); |  | ||||||
|         modal.find('.modal-title').text("Fehler") |  | ||||||
|         modal.find('.dtc-desc').text("DTC-Beschreibung konnte nicht geladen werden") |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|   </script> |  | ||||||
|  |  | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,28 +0,0 @@ | |||||||
| <!DOCTYPE html> |  | ||||||
| <html> |  | ||||||
|  |  | ||||||
| <head> |  | ||||||
|   <meta charset="utf-8" /> |  | ||||||
|   <title>KTM CAN Chain Oiler</title> |  | ||||||
|   <meta http-equiv="content-type" content="text/html;charset=UTF-8"> |  | ||||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> |  | ||||||
|   <link rel="stylesheet" href="static/css/bootstrap.min.css"> |  | ||||||
|   <link rel="stylesheet" href="static/css/custom.css"> |  | ||||||
|   <script src="static/js/jquery.min.js"></script> |  | ||||||
|   <script src="static/js/bootstrap.min.js"></script> |  | ||||||
|   <link rel="apple-touch-icon" sizes="180x180" href="static/img/apple-touch-icon.png"> |  | ||||||
|   <link rel="icon" type="image/png" sizes="32x32" href="static/img/favicon-32x32.png"> |  | ||||||
|   <link rel="icon" type="image/png" sizes="16x16" href="static/img/favicon-16x16.png"> |  | ||||||
|   <link rel="manifest" href="static/img/site.webmanifest"> |  | ||||||
|   <meta http-equiv="refresh" content="3; url='index.htm'" /> |  | ||||||
| </head> |  | ||||||
|  |  | ||||||
| <body> |  | ||||||
|   <div class="container" style="display: flex; justify-content: center; align-items: center; height: 100vh"> |  | ||||||
|     <div class="alert alert-success"> |  | ||||||
|       <strong>Bitte warten!</strong> Änderungen werden übernommen. |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| </body> |  | ||||||
|  |  | ||||||
| </html> |  | ||||||
| @@ -4,7 +4,7 @@ | |||||||
|  * Copyright 2011-2018 Twitter, Inc. |  * 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; | ||||||
|   | |||||||
| @@ -8439,3 +8439,22 @@ a.text-dark:hover { | |||||||
| .navbar-dark.bg-primary { | .navbar-dark.bg-primary { | ||||||
|     background-color: #111 !important |     background-color: #111 !important | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .glow-active-faction { | ||||||
|  |     border: 3px solid #FFD700; /* Goldene Umrandung */ | ||||||
|  |     box-shadow: 0 0 20px #FFD700; /* Leuchtender Glüheffekt */ | ||||||
|  |     animation: glow 1.5s infinite alternate; | ||||||
|  |     border-radius: 10px; /* Abgerundete Ecken */ | ||||||
|  |     margin-bottom: 10px; /* Abstand nach unten */ | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   @keyframes glow { | ||||||
|  |     from { | ||||||
|  |       box-shadow: 0 0 10px #FFD700; | ||||||
|  |     } | ||||||
|  |     to { | ||||||
|  |       box-shadow: 0 0 20px #FFD700; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |    | ||||||
| @@ -25,3 +25,66 @@ hr { | |||||||
|     padding: 10px; |     padding: 10px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #overlay { | ||||||
|  |     display: none; | ||||||
|  |     position: fixed; | ||||||
|  |     top: 0; | ||||||
|  |     left: 0; | ||||||
|  |     width: 100%; | ||||||
|  |     height: 100%; | ||||||
|  |     background: rgba(0, 0, 0, 0.8); /* Dunkler Hintergrund mit Transparenz */ | ||||||
|  |     color: white; /* Textfarbe */ | ||||||
|  |     justify-content: center; | ||||||
|  |     align-items: center; | ||||||
|  |     z-index: 9999; /* Stellen Sie sicher, dass es über anderen Elementen liegt */ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .overlay-content { | ||||||
|  |     text-align: center; | ||||||
|  |     font-size: 4rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .loader { | ||||||
|  |     width: 96px; | ||||||
|  |     height: 96px; | ||||||
|  |     border: 12px solid #FFF; | ||||||
|  |     border-radius: 50%; | ||||||
|  |     display: inline-block; | ||||||
|  |     position: relative; | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     animation: rotation 1s linear infinite; | ||||||
|  |   }  | ||||||
|  |   .loader::after { | ||||||
|  |     content: '';   | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     position: absolute; | ||||||
|  |     left: 50%; | ||||||
|  |     top: 50%; | ||||||
|  |     transform: translate(-50%, -50%); | ||||||
|  |     width: 120px; | ||||||
|  |     height: 120px; | ||||||
|  |     border-radius: 50%; | ||||||
|  |     border: 12px solid transparent; | ||||||
|  |     border-bottom-color: #FF3D00; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   @keyframes rotation { | ||||||
|  |     0% { | ||||||
|  |       transform: rotate(0deg); | ||||||
|  |     } | ||||||
|  |     100% { | ||||||
|  |       transform: rotate(360deg); | ||||||
|  |     } | ||||||
|  |   }  | ||||||
|  |  | ||||||
|  |   .notification-container { | ||||||
|  |     position: fixed; | ||||||
|  |     top: 30%; | ||||||
|  |     left: 50%; | ||||||
|  |     transform: translateX(-50%); | ||||||
|  |     z-index: 1000; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   .notification { | ||||||
|  |     margin-bottom: 20px; /* Fügen Sie bei Bedarf weitere Stile hinzu */ | ||||||
|  |   } | ||||||
							
								
								
									
										96
									
								
								Software/data_src/static/dtc_table.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								Software/data_src/static/dtc_table.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | { | ||||||
|  |     "codegenerator_checksum": "313d59949b074024df3c5d796f65e3bd518e34f0bb171185c30f008f21c19d30", | ||||||
|  |     "timestamp": "2024-05-30 21:56:51", | ||||||
|  |     "dtc_table_data": [ | ||||||
|  |         { | ||||||
|  |             "num": 0, | ||||||
|  |             "title": "No Error", | ||||||
|  |             "description": "No Error" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 1, | ||||||
|  |             "title": "Akku leer", | ||||||
|  |             "description": "Akku ist komplett leer. Den Akku aufladen!" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 2, | ||||||
|  |             "title": "Akku niedrig", | ||||||
|  |             "description": "Akku ist unter der Warnschwelle. Den Akku demnächst aufladen" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 3, | ||||||
|  |             "title": "kein EEPROM erkannt", | ||||||
|  |             "description": "Es wurde kein EEPROM gefunden. Dies lässt einen Hardware-Defekt vermuten." | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 4, | ||||||
|  |             "title": "EEPROM CFG Checksumme", | ||||||
|  |             "description": "Die Checksumme der Config-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 5, | ||||||
|  |             "title": "EEPROM PDS Checksumme", | ||||||
|  |             "description": "Die Checksumme der Betriebsdaten-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 6, | ||||||
|  |             "title": "EEPROM PDS Adresse", | ||||||
|  |             "description": "Die Adresse der Betriebsdaten-Partition im EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 7, | ||||||
|  |             "title": "EEPROM Version falsch", | ||||||
|  |             "description": "Die Layout-Version des EEPROM stimmt nicht mit der Firmware-Version überein. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 8, | ||||||
|  |             "title": "Flashspeicher Fehler", | ||||||
|  |             "description": "Der Flashspeicher konnte nicht initialisiert werden. Aktualisieren sie Flash & Firmware" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 9, | ||||||
|  |             "title": "Flashversion falsch", | ||||||
|  |             "description": "Die Version des Flashspeicher stimmt nicht mit der Firmware-Version überein. Aktualisieren sie den Flash mit der passenden Update-Datei" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 10, | ||||||
|  |             "title": "Keine Akkuüberwachung", | ||||||
|  |             "description": "Es wurde keine Akkuüberwachung über I2C gefunden, Prüfen sie die Hardware!" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 11, | ||||||
|  |             "title": "LoRa-Transceiver Error", | ||||||
|  |             "description": "Es konnte keine Verbindung zum LoRa-Transceiver hergestellt werden. Prüfen Sie die Hardware auf Defekte" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 12, | ||||||
|  |             "title": "Config-Validierung", | ||||||
|  |             "description": "Ein oder mehrer Einstellungswerte sind ausserhalb plausibler Werte. Prüfen Sie Ihre Einstellungen" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 13, | ||||||
|  |             "title": "EEPROM-Migration", | ||||||
|  |             "description": "Es wurde ein altes EEPROm Image erkannt, konnte aber nicht migriert werden. EEPROM manuell zurück setzen und neue Einstellunge speichern." | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 14, | ||||||
|  |             "title": "Dummy-DTC Info", | ||||||
|  |             "description": "Ein Dummy-DTC der Schwere \"Info\" für Debugging-Zwecke" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 15, | ||||||
|  |             "title": "Dummy-DTC Warnung", | ||||||
|  |             "description": "Ein Dummy-DTC der Schwere \"Warnung\" für Debugging-Zwecke" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 16, | ||||||
|  |             "title": "Dummy-DTC Kritisch", | ||||||
|  |             "description": "Ein Dummy-DTC der Schwere \"Kritisch\" für Debugging-Zwecke" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 17, | ||||||
|  |             "title": "Last Error", | ||||||
|  |             "description": "Last Error" | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										221
									
								
								Software/data_src/static/js/dtc_table.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								Software/data_src/static/js/dtc_table.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | |||||||
|  | const jsonFilePath = "static/dtc_table.json"; | ||||||
|  |  | ||||||
|  | var dtcState = {}; | ||||||
|  |  | ||||||
|  | async function processDTCNotifications(dtcArray) { | ||||||
|  |   // Entferne DTCs aus dtcState, die nicht im dtcArray enthalten sind | ||||||
|  |   for (var key in dtcState) { | ||||||
|  |     if (!dtcArray.some((dtc) => parseInt(dtc.split(",")[1]) == key)) { | ||||||
|  |       delete dtcState[key]; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (dtcArray.length === 0 || dtcArray[0] == "0") { | ||||||
|  |     dtcState = {}; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (var i = 0; i < dtcArray.length; i++) { | ||||||
|  |     var dtcInfo = dtcArray[i].split(","); | ||||||
|  |     var errorCode = parseInt(dtcInfo[1]); | ||||||
|  |     var activity = parseInt(dtcInfo[3]); | ||||||
|  |     var severity = parseInt(dtcInfo[2]); | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       var { title, description } = await getDescriptionForDTCNumber(errorCode); | ||||||
|  |  | ||||||
|  |       switch (severity) { | ||||||
|  |         case 1: | ||||||
|  |           severity = "info"; | ||||||
|  |           break; | ||||||
|  |         case 2: | ||||||
|  |           severity = "warning"; | ||||||
|  |           break; | ||||||
|  |         case 3: | ||||||
|  |           severity = "danger"; | ||||||
|  |           break; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (dtcState[errorCode]) { | ||||||
|  |         // Überprüfen, ob sich der Zustand von "previous" auf "active" geändert hat | ||||||
|  |         if (activity !== dtcState[errorCode].activity) { | ||||||
|  |           dtcState[errorCode].activity = activity; | ||||||
|  |           if (activity === 1) showNotification(description, severity); | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         // DTC ist neu, Zustand speichern und wenn active, Benachrichtigung anzeigen | ||||||
|  |         dtcState[errorCode] = { activity: activity }; | ||||||
|  |         if (activity === 1) showNotification(description, severity); | ||||||
|  |       } | ||||||
|  |     } catch (error) { | ||||||
|  |       console.error("Error processing DTC:", error); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getDescriptionForDTCNumber(number) { | ||||||
|  |   return new Promise((resolve, reject) => { | ||||||
|  |     fetch(jsonFilePath) | ||||||
|  |       .then((response) => response.json()) | ||||||
|  |       .then((data) => { | ||||||
|  |         const dtcList = data.dtc_table_data; | ||||||
|  |         const foundEntry = dtcList.find((entry) => entry.num === number); | ||||||
|  |  | ||||||
|  |         if (foundEntry) { | ||||||
|  |           const description = foundEntry.description; | ||||||
|  |           const title = foundEntry.title; | ||||||
|  |           resolve({ title, description }); | ||||||
|  |         } else { | ||||||
|  |           // Wenn die Nummer nicht gefunden wurde, geben Sie einen Fehler zurück | ||||||
|  |           reject(`Beschreibung für Nummer ${number} nicht gefunden.`); | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |       .catch((error) => { | ||||||
|  |         // Im Fehlerfall geben Sie den Fehler zurück | ||||||
|  |         reject(error); | ||||||
|  |       }); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function showDTCModal(event) { | ||||||
|  |   var dtc = parseInt(event.currentTarget.getAttribute("data-dtc")); | ||||||
|  |   var debugval = event.currentTarget.getAttribute("data-debugval"); | ||||||
|  |   var modal = $("#dtcModal"); | ||||||
|  |  | ||||||
|  |   try { | ||||||
|  |     var { title, description } = await getDescriptionForDTCNumber(dtc); | ||||||
|  |  | ||||||
|  |     modal.find(".modal-title").text(title); | ||||||
|  |     modal.find(".dtc-desc").text(description); | ||||||
|  |  | ||||||
|  |     if (debugval > 0) { | ||||||
|  |       modal.find(".dtc-debugval").text("Debugvalue: " + debugval); | ||||||
|  |     } else { | ||||||
|  |       modal.find(".dtc-debugval").remove(); | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error("Fehler beim Abrufen der Beschreibung:", error); | ||||||
|  |     modal.find(".modal-title").text("Fehler"); | ||||||
|  |     modal | ||||||
|  |       .find(".dtc-desc") | ||||||
|  |       .text("DTC-Beschreibung konnte nicht geladen werden"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Modal anzeigen | ||||||
|  |   modal.modal("show"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function fillDTCTable(dtcArray) { | ||||||
|  |   // Referenz auf das Tabellen-Element | ||||||
|  |   var table = document.getElementById("dtc_table"); | ||||||
|  |   var tablediv = document.getElementById("dtc_container"); | ||||||
|  |  | ||||||
|  |   // Prüfen, ob DTC vorhanden sind | ||||||
|  |   if (dtcArray.length === 0 || dtcArray[0] == "0") { | ||||||
|  |     // Verstecke das Tabellen-Div, wenn keine DTC vorhanden sind | ||||||
|  |     tablediv.hidden = true; | ||||||
|  |     table.innerHTML = ""; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Prüfen, ob sich der Inhalt der Tabelle geändert hat | ||||||
|  |   var tableContentChanged = false; | ||||||
|  |   for (var i = 0; i < dtcArray.length; i++) { | ||||||
|  |     var dtcInfo = dtcArray[i].split(","); | ||||||
|  |     var errorCode = parseInt(dtcInfo[1]); | ||||||
|  |     var activity = parseInt(dtcInfo[3]); | ||||||
|  |  | ||||||
|  |     if (!dtcState[errorCode] || dtcState[errorCode].activity !== activity) { | ||||||
|  |       tableContentChanged = true; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!tableContentChanged) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Zeige das Tabellen-Div, wenn DTC vorhanden sind | ||||||
|  |   tablediv.hidden = false; | ||||||
|  |  | ||||||
|  |   // Tabelle leeren, bevor sie neu gefüllt wird | ||||||
|  |   table.innerHTML = ""; | ||||||
|  |  | ||||||
|  |   // Überschriften für die Tabelle erstellen | ||||||
|  |   var headerRow = table.insertRow(0); | ||||||
|  |  | ||||||
|  |   // Definition der Klassen und Scopes für die Spalten | ||||||
|  |   var columnDefinitions = [ | ||||||
|  |     { class: "col-6", scope: "Zeitstempel" }, | ||||||
|  |     { class: "col-2", scope: "Fehlercode" }, | ||||||
|  |     { class: "col-2", scope: "Schwere" }, | ||||||
|  |     { class: "col-2", scope: "Aktiv" }, | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|  |   for (var i = 0; i < columnDefinitions.length; i++) { | ||||||
|  |     var headerCell = headerRow.insertCell(i); | ||||||
|  |     headerCell.className = `th ${columnDefinitions[i].class}`; | ||||||
|  |     headerCell.scope = columnDefinitions[i].scope; | ||||||
|  |     headerCell.innerHTML = columnDefinitions[i].scope; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // DTC-Daten in die Tabelle einfügen | ||||||
|  |   for (var i = 0; i < dtcArray.length; i++) { | ||||||
|  |     var dtcInfo = dtcArray[i].split(","); | ||||||
|  |  | ||||||
|  |     var row = table.insertRow(i + 1); // +1 wegen der Überschriftenzeile | ||||||
|  |  | ||||||
|  |     // Zeitstempel | ||||||
|  |     var timestampCell = row.insertCell(0); | ||||||
|  |     timestampCell.innerHTML = formatTimestamp(parseInt(dtcInfo[0])); | ||||||
|  |  | ||||||
|  |     // Fehlercode | ||||||
|  |     var errorCodeCell = row.insertCell(1); | ||||||
|  |     errorCodeCell.innerHTML = dtcInfo[1]; | ||||||
|  |  | ||||||
|  |     // Schwere | ||||||
|  |     var severityCell = row.insertCell(2); | ||||||
|  |     var severity = parseInt(dtcInfo[2]); | ||||||
|  |  | ||||||
|  |     // Schwere | ||||||
|  |     switch (severity) { | ||||||
|  |       case 1: | ||||||
|  |         severityCell.innerHTML = '<img src="static/img/info.png" alt="Info" />'; | ||||||
|  |         break; | ||||||
|  |       case 2: | ||||||
|  |         severityCell.innerHTML = | ||||||
|  |           '<img src="static/img/warn.png" alt="Warnung" />'; | ||||||
|  |         break; | ||||||
|  |       case 3: | ||||||
|  |         severityCell.innerHTML = | ||||||
|  |           '<img src="static/img/critical.png" alt="Kritisch" />'; | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         severityCell.innerHTML = | ||||||
|  |           '<img src="static/img/none.png" alt="Unbekannt" />'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     row.setAttribute("data-dtc", dtcInfo[1]); | ||||||
|  |     row.setAttribute("data-debugval", dtcInfo[4]); | ||||||
|  |     row.addEventListener("click", showDTCModal); | ||||||
|  |  | ||||||
|  |     // Aktivität | ||||||
|  |     var activityCell = row.insertCell(3); | ||||||
|  |     activityCell.innerHTML = parseInt(dtcInfo[3]) === 1 ? "active" : "previous"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function formatTimestamp(milliseconds) { | ||||||
|  |   const date = new Date(milliseconds); | ||||||
|  |  | ||||||
|  |   const days = String(date.getUTCDate() - 1).padStart(2, "0"); | ||||||
|  |   const hours = String(date.getUTCHours()).padStart(2, "0"); | ||||||
|  |   const minutes = String(date.getUTCMinutes()).padStart(2, "0"); | ||||||
|  |   const seconds = String(date.getUTCSeconds()).padStart(2, "0"); | ||||||
|  |   const millisecondsFormatted = String(date.getUTCMilliseconds()).padStart( | ||||||
|  |     3, | ||||||
|  |     "0" | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   return `${days}-${hours}:${minutes}:${seconds}:${millisecondsFormatted}`; | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								Software/data_src/static/js/script.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Software/data_src/static/js/script.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | $(document).ready(function () { | ||||||
|  |     $(".navbar-nav a").on("click", function () { | ||||||
|  |       $(".navbar-collapse").collapse("hide"); | ||||||
|  |     }); | ||||||
|  |    | ||||||
|  |     $("#show_hide_password a").on("click", function (event) { | ||||||
|  |       event.preventDefault(); | ||||||
|  |       if ($("#show_hide_password input").attr("type") == "text") { | ||||||
|  |         $("#show_hide_password input").attr("type", "password"); | ||||||
|  |         $("#show_hide_password i").addClass("fa-eye-slash"); | ||||||
|  |         $("#show_hide_password i").removeClass("fa-eye"); | ||||||
|  |       } else if ($("#show_hide_password input").attr("type") == "password") { | ||||||
|  |         $("#show_hide_password input").attr("type", "text"); | ||||||
|  |         $("#show_hide_password i").removeClass("fa-eye-slash"); | ||||||
|  |         $("#show_hide_password i").addClass("fa-eye"); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |    | ||||||
|  |   document | ||||||
|  |     .querySelector(".custom-file-input") | ||||||
|  |     .addEventListener("change", function (e) { | ||||||
|  |       var fileName = document.getElementById("fw-update-file").files[0].name; | ||||||
|  |       var nextSibling = e.target.nextElementSibling; | ||||||
|  |       nextSibling.innerText = fileName; | ||||||
|  |     }); | ||||||
|  |    | ||||||
| @@ -1,7 +1,15 @@ | |||||||
| var gateway = `ws://${window.location.hostname}/ws`; | var 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,94 @@ 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 +156,120 @@ 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 if (element.tagName === "DIV" || element.tagName === "SPAN") { | ||||||
|  |           if (element.classList.contains("format-time")) { | ||||||
|  |             element.innerText = formatTime(dataset[key]); | ||||||
|  |           } else if (element.classList.contains("faction-logo")) { | ||||||
|  |             // Faction-Logo-Logik | ||||||
|  |             updateFactionLogo(element, dataset[key]); | ||||||
|  |           } else { | ||||||
|  |             element.innerText = dataset[key]; | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           // Standardmäßig für Textfelder und andere Elemente | ||||||
|  |           element.value = dataset[key]; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }); |     } | ||||||
|  |     // Aktualisiere den <title>-Tag, wenn der Schlüssel 'devicename' im dataset vorhanden ist | ||||||
|  |     if (key === "devicename") { | ||||||
|  |       document.title = dataset[key]; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function formatTime(seconds) { | ||||||
|  |   var hrs = Math.floor(seconds / 3600); | ||||||
|  |   var mins = Math.floor((seconds % 3600) / 60); | ||||||
|  |   var secs = seconds % 60; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     String(hrs).padStart(2, "0") + | ||||||
|  |     ":" + | ||||||
|  |     String(mins).padStart(2, "0") + | ||||||
|  |     ":" + | ||||||
|  |     String(secs).padStart(2, "0") | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function updateFactionLogo(element, faction) { | ||||||
|  |   const glowClass = "glow-active-faction"; | ||||||
|  |   const factionClasses = ["faction1", "faction2", "faction3"]; | ||||||
|  |  | ||||||
|  |   factionClasses.forEach((factionClass) => { | ||||||
|  |     if ( | ||||||
|  |       factionClass === "faction" + faction && | ||||||
|  |       element.classList.contains(factionClass) | ||||||
|  |     ) { | ||||||
|  |       element.classList.add(glowClass); | ||||||
|  |     } else { | ||||||
|  |       element.classList.remove(glowClass); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Funktion zum Setzen des ausgewählten Werts für Dropdowns | ||||||
|  | function setDropdownValue(selectElement, value) { | ||||||
|  |   for (var i = 0; i < selectElement.options.length; i++) { | ||||||
|  |     if (selectElement.options[i].value === value) { | ||||||
|  |       selectElement.selectedIndex = i; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Funktion zum Aktualisieren der Fortschrittsleiste | ||||||
|  | function updateProgressBar(progressBar, value) { | ||||||
|  |   // Wert in das aria-valuenow-Attribut einfügen | ||||||
|  |   progressBar.setAttribute("aria-valuenow", value); | ||||||
|  |  | ||||||
|  |   // Breite des Fortschrittsbalkens und inneren Text aktualisieren | ||||||
|  |   progressBar.style.width = value + "%"; | ||||||
|  |   progressBar.textContent = value + "%"; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function showNotification(message, type) { | ||||||
|  |   // Erstellen Sie ein Bootstrap-Alert-Element | ||||||
|  |   var alertElement = $( | ||||||
|  |     '<div class="alert alert-' + | ||||||
|  |       type + | ||||||
|  |       ' alert-dismissible fade show notification" role="alert">' + | ||||||
|  |       "<strong>" + | ||||||
|  |       message + | ||||||
|  |       "</strong>" + | ||||||
|  |       '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' + | ||||||
|  |       '<span aria-hidden="true">×</span>' + | ||||||
|  |       "</button>" + | ||||||
|  |       "</div>" | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   // Fügen Sie das Alert-Element dem Container hinzu | ||||||
|  |   $("#notification-container").append(alertElement); | ||||||
|  |  | ||||||
|  |   // Nach 5 Sekunden das Alert-Element ausblenden | ||||||
|  |   setTimeout(function () { | ||||||
|  |     alertElement.alert("close"); | ||||||
|  |   }, 5000); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "kein EEPROM gefunden", |  | ||||||
|     "description": "Es wurde kein EEPROM gefunden. Dies lässt einen Hardware-Defekt vermuten." |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "Power-Monitor Fehler", |  | ||||||
|     "description": "Es gibt ein Problem mit dem Power-Monitoring. Die Akku-Überwachung ist ohne Funktion! Bitte Hardware prüfen" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "Akku-Spannung niedrig", |  | ||||||
|     "description": "Die Akkuspannung ist niedrig. Bitte Akku bald aufladen!" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "Akku-Spannung kritisch", |  | ||||||
|     "description": "Die Akkuspannung ist sehr niedrig. Bitte Akku umgehend ersetzen um eine schädliche Tiefentladung zu vermeiden!" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "EEPROM-Migration failed", |  | ||||||
|     "description": "Migration of EEPROM-Image from an other FW-Version failed - you need to reset everything manually!" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "EEPROM CFG Checksumme", |  | ||||||
|     "description": "Die Checksumme der Config-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "EEPROM PDS Checksumme", |  | ||||||
|     "description": "Die Checksumme der Betriebsdaten-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "EEPROM PDS Adresse", |  | ||||||
|     "description": "Die Adresse der Betriebsdaten-Partition im EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "EEPROM Version falsch", |  | ||||||
|     "description": "Die Layout-Version des EEPROM stimmt nicht mit der Firmware-Version überein. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "Flashstorage Fehler", |  | ||||||
|     "description": "Der Flashstorage konnte nicht initialisiert werden. Aktualisieren sie Flash & Firmware" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "Flashstorage Version falsch", |  | ||||||
|     "description": "Die Version des Flashstorage stimmt nicht mit der Firmware-Version überein. Aktualisieren sie den Flash mit der passenden Update-Datei" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "Config-Validierung", |  | ||||||
|     "description": "Ein oder mehrer Einstellungswerte sind ausserhalb plausibler Werte. Prüfen Sie Ihre Einstellungen" |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| { |  | ||||||
|     "title": "LoRa-Modul Fehler", |  | ||||||
|     "description": "Es gibt ein Problem mit dem LoRa-Modul. Es konnte keine LoRa-Verbindung aufgebaut werden. Bitte Hardware prüfen" |  | ||||||
| } |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| 1.03 |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| import subprocess |  | ||||||
|  |  | ||||||
| revision = ( |  | ||||||
|     subprocess.check_output(["git", "rev-parse", "--short=10", "HEAD"]) |  | ||||||
|     .strip() |  | ||||||
|     .decode("utf-8") |  | ||||||
| ) |  | ||||||
| print("-DGIT_REV='\"%s\"'" % revision) |  | ||||||
| @@ -3,14 +3,20 @@ | |||||||
| #define _COMMON_H_ | #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 | ||||||
|  |  | ||||||
| #define HOST_NAME "AirsoftTimer_%08X" | #ifndef DEVICE_NAME | ||||||
|  | #define HOST_NAME "AirsoftTimer" | ||||||
|  | #else | ||||||
|  | #define HOST_NAME DEVICE_NAME | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #define SHUTDOWN_DELAY_MS 5000 | #define SHUTDOWN_DELAY_MS 5000 | ||||||
| #define STARTUP_DELAY_MS 20000 | #define STARTUP_DELAY_MS 20000 | ||||||
| @@ -44,6 +50,27 @@ | |||||||
| #define OTA_DELAY 50 // ticks -> 10ms / tick | #define OTA_DELAY 50 // ticks -> 10ms / tick | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | typedef enum eSystem_Status | ||||||
|  | { | ||||||
|  |   sysStat_Init, | ||||||
|  |   sysStat_Startup, | ||||||
|  |   sysStat_Normal, | ||||||
|  |   sysStat_Error, | ||||||
|  |   sysStat_Shutdown | ||||||
|  | } tSystem_Status; | ||||||
|  |  | ||||||
|  | typedef enum batteryType_e | ||||||
|  | { | ||||||
|  |   BATTERY_UNDEFINED, | ||||||
|  |   BATTERY_LIPO_2S, | ||||||
|  |   BATTERY_LIPO_3S | ||||||
|  | } batteryType_t; | ||||||
|  |  | ||||||
|  | // String representation of SpeedSource enum | ||||||
|  | extern const char *BatteryString[]; | ||||||
|  | extern const size_t BatteryString_Elements; | ||||||
|  |  | ||||||
|  | #define STARTUP_DELAY 2500 | ||||||
|  | #define SHUTDOWN_DELAY_MS 2500 | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -1,16 +1,31 @@ | |||||||
|  | /** | ||||||
|  |  * @file debugger.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for debugging functions and status in the DE-Timer application. | ||||||
|  |  * | ||||||
|  |  * This file declares functions and status definitions for debugging purposes in the DE-Timer project. | ||||||
|  |  * It includes functions to print system information, WiFi information, format EEPROM data, | ||||||
|  |  * handle debug messages, and manage the status of different debug ports. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   09.01.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #ifndef _DEBUGGER_H_ | #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" | ||||||
|  |                                "reboot      - System Reboot\n" | ||||||
|                                "netinfo     - WiFi Info\n" |                                "netinfo     - WiFi Info\n" | ||||||
|                                "formatPDS   - Format Persistence EEPROM Data\n" |                                "formatPDS   - Format Persistence EEPROM Data\n" | ||||||
|                                "formatCFG   - Format Configuration EEPROM Data\n" |                                "formatCFG   - Format Configuration EEPROM Data\n" | ||||||
|                                "checkEE     - Check EEPROM with checksum\n" |                                "checkEE     - Check EEPROM with checksum\n" | ||||||
|                                "dumpEE1k    - dump the first 1kb of EEPROM to Serial\n" |                                "dumpEE1k    - dump the first 1kb of EEPROM to Serial\n" | ||||||
|                                "dumpEE      - dump the whole EPPROM to Serial\n" |                                "dumpEE      - dump the whole EPPROM to Serial\n" | ||||||
|  |                                "killEE      - kill the first 1024 byte of EEPROM\n" | ||||||
|  |                                "zeroEE      - zero the first 1024 byte of EEPROM\n" | ||||||
|                                "resetPageEE - Reset the PersistenceData Page\n" |                                "resetPageEE - Reset the PersistenceData Page\n" | ||||||
|                                "dumpCFG     - print Config struct\n" |                                "dumpCFG     - print Config struct\n" | ||||||
|                                "dumpPDS     - print PersistanceStruct\n" |                                "dumpPDS     - print PersistanceStruct\n" | ||||||
|   | |||||||
| @@ -1,57 +1,39 @@ | |||||||
|  | /** | ||||||
|  |  * @file dtc.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for handling Diagnostic Trouble Codes (DTC) in the DE-Timer application. | ||||||
|  |  * | ||||||
|  |  * This file provides definitions and functions for handling Diagnostic Trouble Codes (DTC) | ||||||
|  |  * in the DE-Timer project. It includes structures for DTC entries, severity levels, | ||||||
|  |  * and functions for DTC maintenance and processing. DTCs are used to track system errors and issues. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   09.01.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #ifndef _DTC_H_ | #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,52 +1,63 @@ | |||||||
|  | /** | ||||||
|  |  * @file eeprom.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for configuration settings and EEPROM operations in the DE-Timer application. | ||||||
|  |  * | ||||||
|  |  * This file defines configuration settings for the DE-Timer project, including default values, | ||||||
|  |  * EEPROM structures, and functions for EEPROM operations. It also defines enums for different Battery Types. | ||||||
|  |  * Additionally, it includes functions for EEPROM handling such as storing, retrieving, and formatting configuration data. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   09.01.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #ifndef _EEPROM_H_ | #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; | // Structure for configuration settings stored in EEPROM | ||||||
| typedef enum |  | ||||||
| { |  | ||||||
|   BATTERY_UNDEFINED, |  | ||||||
|   BATTERY_LIPO_2S, |  | ||||||
|   BATTERY_LIPO_3S |  | ||||||
| } batteryType_t; |  | ||||||
|  |  | ||||||
| const char BatteryString[][10]{ |  | ||||||
|     "Undefined", |  | ||||||
|     "LiPo 2S", |  | ||||||
|     "LiPo 3S"}; |  | ||||||
|  |  | ||||||
| const size_t BatteryString_Elements = sizeof(BatteryString) / sizeof(BatteryString[0]); |  | ||||||
|  |  | ||||||
| typedef struct | typedef struct | ||||||
| { | { | ||||||
|   uint8_t EEPROM_Version; |   uint8_t EEPROM_Version; | ||||||
| @@ -55,25 +66,31 @@ 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 |     HOST_NAME, | ||||||
|  |     QUOTE(WIFI_AP_PASSWORD), | ||||||
|  |     QUOTE(WIFI_SSID_CLIENT), | ||||||
|  |     QUOTE(WIFI_PASSWORD_CLIENT), | ||||||
|  |     true, | ||||||
|  |     0 // checksum | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const uint16_t startofConfigData = 16; | boolean InitEEPROM(); | ||||||
| const uint16_t startofPersistence = 16 + sizeof(ConfigData) + (sizeof(ConfigData) % 16); |  | ||||||
|  |  | ||||||
| void InitEEPROM(); |  | ||||||
| void EEPROM_Process(); | void EEPROM_Process(); | ||||||
| void StoreConfig_EEPROM(); | void StoreConfig_EEPROM(); | ||||||
| void GetConfig_EEPROM(); | void GetConfig_EEPROM(); | ||||||
| @@ -85,5 +102,10 @@ uint32_t Checksum_EEPROM(uint8_t const *data, size_t len); | |||||||
| void dumpEEPROM(uint16_t memoryAddress, uint16_t length); | void 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); | ||||||
|  | void writeSequentialToEEPROM(uint16_t memoryAddress, uint16_t length); | ||||||
|  | void writeZeroToEEPROM(uint16_t memoryAddress, uint16_t length); | ||||||
|  |  | ||||||
| #endif // _EEPROM_H_ | extern configData_t ConfigData; | ||||||
|  | extern persistenceData_t PersistenceData; | ||||||
|  | extern uint16_t eePersistenceMarker; | ||||||
|  | #endif // _CONFIG_H_ | ||||||
|   | |||||||
| @@ -2,68 +2,44 @@ | |||||||
| #define _GLOBALS_H_ | #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[25];                           /**< Device name */ | ||||||
|   uint16_t eePersistanceAdress; |   char DeviceNameId[sizeof(DeviceName) + 8];     /**< Device name plus 8 chars chipID */ | ||||||
|  |   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,3,     // Firmware_Version |     FW_MAJOR, FW_MINOR,   // Firmware_Version | ||||||
|  1,3,     // Required Flash Version |     FL_MAJOR, FL_MINOR,   // 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 |  | ||||||
| @@ -19,10 +19,10 @@ | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
|     #ifndef WIFI_CLIENT_PASSWORD |     #ifndef WIFI_PASSWORD_CLIENT | ||||||
|         #error "You must define an WIFI_PASSWORD for OTA-Update" |         #error "You must define an WIFI_PASSWORD for OTA-Update" | ||||||
|     #endif |     #endif | ||||||
|     #ifndef WIFI_CLIENT_SSID |     #ifndef WIFI_SSID_CLIENT | ||||||
|         #error "You must define an WIFI_SSID for OTA-Update" |         #error "You must define an WIFI_SSID for OTA-Update" | ||||||
|     #endif |     #endif | ||||||
| #endif | #endif | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								Software/include/struct2json.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Software/include/struct2json.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | /** | ||||||
|  |  * @file struct2json.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for converting structs to JSON objects. | ||||||
|  |  * | ||||||
|  |  * @note This file is auto-generated by a script on 2024-05-30 22:54:25. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   30.05.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _STRUCT2JSON_H_ | ||||||
|  | #define _STRUCT2JSON_H_ | ||||||
|  |  | ||||||
|  | #include <Arduino.h> | ||||||
|  | #include <ArduinoJson.h> | ||||||
|  |  | ||||||
|  | #include "eeprom.h" | ||||||
|  |  | ||||||
|  | void generateJsonObject_ConfigData(JsonObject data); | ||||||
|  | void generateJsonObject_PersistenceData(JsonObject data); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* _STRUCT2JSON_H_ */ | ||||||
|  |  | ||||||
|  | // CODEGENERATOR_CHECKSUM: 735cd4daf9a46bd773bdf5e6cd5a58d61b0d877196399bc2784a0d0ea7af717d | ||||||
							
								
								
									
										36
									
								
								Software/include/utilities.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Software/include/utilities.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | #ifndef UTILITIES_H | ||||||
|  | #define UTILITIES_H | ||||||
|  |  | ||||||
|  | #include <Arduino.h> | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Validates whether a given string contains only characters allowed in WiFi SSIDs and passwords. | ||||||
|  |  * | ||||||
|  |  * This function checks each character in the provided string to ensure | ||||||
|  |  * that it contains only characters allowed in WiFi SSIDs and passwords. | ||||||
|  |  * It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as | ||||||
|  |  * the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ | ||||||
|  |  * | ||||||
|  |  * @param string Pointer to the string to be validated. | ||||||
|  |  * @param size Size of the string including the null-terminator. | ||||||
|  |  * @return true if the string contains only allowed characters or is NULL, | ||||||
|  |  *         false otherwise. | ||||||
|  |  */ | ||||||
|  | bool validateWiFiString(char *string, size_t size); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Copies a string to a buffer, replacing invalid WiFi SSID characters with a placeholder. | ||||||
|  |  * | ||||||
|  |  * This function checks each character in the provided input string to ensure | ||||||
|  |  * that it contains only characters allowed in WiFi SSIDs and passwords. If a character | ||||||
|  |  * is invalid, it replaces it with a placeholder character (e.g., '_'). | ||||||
|  |  * It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as | ||||||
|  |  * the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ | ||||||
|  |  * | ||||||
|  |  * @param input Pointer to the input string to be validated and copied. | ||||||
|  |  * @param buffer Pointer to the buffer where the output string will be copied. | ||||||
|  |  * @param bufferSize Size of the buffer including the null-terminator. | ||||||
|  |  */ | ||||||
|  | void sanitizeWiFiString(const char *input, char *buffer, size_t bufferSize); | ||||||
|  |  | ||||||
|  | #endif // UTILITIES_H | ||||||
| @@ -1,3 +1,16 @@ | |||||||
|  | /** | ||||||
|  |  * @file webui.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for the web-based user interface (WebUI) in the DE-Timer application. | ||||||
|  |  * | ||||||
|  |  * This file contains declarations for functions related to the initialization and processing of the | ||||||
|  |  * web-based user interface (WebUI). It includes the necessary libraries and dependencies for handling | ||||||
|  |  * web server functionality, asynchronous JSON operations, and live debugging through WebSockets. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   09.01.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #ifndef _WEBUI_H_ | #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_ | ||||||
|   | |||||||
| @@ -17,25 +17,26 @@ platform = espressif8266 | |||||||
| framework = arduino | framework = arduino | ||||||
| board = d1_mini | board = d1_mini | ||||||
|  |  | ||||||
|  | custom_firmware_version = 1.07 | ||||||
|  | custom_flash_version = 1.07 | ||||||
|  |  | ||||||
| upload_protocol = esptool | upload_protocol = esptool | ||||||
| upload_speed = 921600 | upload_speed = 921600 | ||||||
| ;upload_port = 10.0.1.48 | ;upload_port = 10.0.1.48 | ||||||
| ;upload_protocol = espota |  | ||||||
| ;upload_flags = | ;upload_flags = | ||||||
| ;   --auth=${wifi_cred.ota_password} | ;  --port=8266 | ||||||
|  | ;  --auth=${wifi_cred.admin_password} | ||||||
|  |  | ||||||
| build_flags= | build_flags= | ||||||
|   !python 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.admin_password} | ||||||
|   -DWIFI_CLIENT_SSID=${wifi_cred.wifi_client_ssid} |   -DWIFI_SSID_CLIENT=${wifi_cred.wifi_ssid_client} | ||||||
|   -DWIFI_CLIENT_PASSWORD=${wifi_cred.wifi_client_password} |   -DWIFI_PASSWORD_CLIENT=${wifi_cred.wifi_password_client} | ||||||
|   -DWIFI_AP_SSID=${wifi_cred.wifi_ap_ssid} |   -DADMIN_PASSWORD=${wifi_cred.admin_password} | ||||||
|   -DWIFI_AP_PASSWORD=${wifi_cred.wifi_ap_password} |   -DWIFI_AP_PASSWORD=${wifi_cred.wifi_ap_password} | ||||||
|   -DDEVICE_NAME='"Dark Emergency Timer"' |   -DDEVICE_NAME='"Dark Emergency Timer"' | ||||||
|  |  | ||||||
| @@ -43,18 +44,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 | ||||||
							
								
								
									
										9
									
								
								Software/src/common.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Software/src/common.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | #include "common.h" | ||||||
|  |  | ||||||
|  | const char *BatteryString[] = { | ||||||
|  |     "Undefined", | ||||||
|  |     "LiPo 2S", | ||||||
|  |     "LiPo 3S" | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | const size_t BatteryString_Elements = sizeof(BatteryString) / sizeof(BatteryString[0]); | ||||||
| @@ -1,52 +1,178 @@ | |||||||
|  | /** | ||||||
|  |  * @file debugger.cpp | ||||||
|  |  * @brief Implementation of debugging functions for monitoring and diagnostics. | ||||||
|  |  * | ||||||
|  |  * This file contains the implementation of various debugging functions to monitor | ||||||
|  |  * and diagnose the system. It includes functions to print system information, WiFi | ||||||
|  |  * details, EEPROM status, dump configuration settings, dump persistence data, show | ||||||
|  |  * Diagnostic Trouble Codes (DTCs), and more. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date 09.04.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #include "debugger.h" | #include "debugger.h" | ||||||
|  |  | ||||||
| DebugStatus_t DebuggerStatus[dbg_cntElements]; | DebugStatus_t DebuggerStatus[dbg_cntElements]; | ||||||
|  |  | ||||||
| String IpAddress2String(const IPAddress &ipAddress); | // Funktionszeiger | ||||||
| void processCmdDebug(String command); | typedef void (*CommandFunction)(); | ||||||
|  |  | ||||||
|  | // Struktur zur Zuordnung von Commands zu Funktionen | ||||||
|  | struct CommandMapping | ||||||
|  | { | ||||||
|  |     const char *command; | ||||||
|  |     CommandFunction function; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | void processCmdDebug(const char *command); | ||||||
| void Debug_formatCFG(); | void Debug_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(); | ||||||
|  | void Debug_Reboot(); | ||||||
|  | const char *uint32_to_binary_string(uint32_t num); | ||||||
|  |  | ||||||
|  | // Adapter-Functions for Debug-Commands | ||||||
|  | void adapterCheckEEPOM() { Debug_CheckEEPOM(false); } | ||||||
|  | void adapterCheckEEPOMFix() { Debug_CheckEEPOM(true); } | ||||||
|  | void adapterDumpEEPROM1k() { dumpEEPROM(0, 1024); } | ||||||
|  | void adapterDumpEEPROMAll() { dumpEEPROM(0, EEPROM_SIZE_BYTES); } | ||||||
|  | void adapterKillEEPROM() { writeSequentialToEEPROM(0, 1024); } | ||||||
|  | void adapterZeroEEPROM() { writeZeroToEEPROM(0, 1024); } | ||||||
|  | void adapterResetPageEEPROM() { MovePersistencePage_EEPROM(true); } | ||||||
|  | void adapterSaveEEPROM() { globals.requestEEAction = EE_ALL_SAVE; } | ||||||
|  | void adapterSetDebugPort() { SetDebugportStatus(dbg_Serial, enabled); } | ||||||
|  | void adapterCritDTC() { MaintainDTC(DTC_FAKE_DTC_CRIT, true, millis()); } | ||||||
|  | void adapterWarnDTC() { MaintainDTC(DTC_FAKE_DTC_WARN, true, millis()); } | ||||||
|  | void adapterInfoDTC() { MaintainDTC(DTC_FAKE_DTC_INFO, true, millis()); } | ||||||
|  | void adapterNotifyError() { Websocket_PushNotification("Debug Error Notification", error); } | ||||||
|  | void adapterNotifyWarning() { Websocket_PushNotification("Debug Warning Notification", warning); } | ||||||
|  | void adapterNotifySuccess() { Websocket_PushNotification("Debug Success Notification", success); } | ||||||
|  | void adapterNotifyInfo() { Websocket_PushNotification("Debug Info Notification", info); } | ||||||
|  |  | ||||||
|  | // Definition der Command-Mapping-Tabelle | ||||||
|  | const CommandMapping commandMappings[] = { | ||||||
|  |     {"help", Debug_printHelp}, | ||||||
|  |     {"reboot", Debug_Reboot}, | ||||||
|  |     {"sysinfo", Debug_printSystemInfo}, | ||||||
|  |     {"netinfo", Debug_printWifiInfo}, | ||||||
|  |     {"formatCFG", Debug_formatCFG}, | ||||||
|  |     {"formatPDS", Debug_formatPersistence}, | ||||||
|  |     {"checkEE", adapterCheckEEPOM}, | ||||||
|  |     {"checkEEfix", adapterCheckEEPOMFix}, | ||||||
|  |     {"dumpEE1k", adapterDumpEEPROM1k}, | ||||||
|  |     {"dumpEE", adapterDumpEEPROMAll}, | ||||||
|  |     {"killEE", adapterKillEEPROM}, | ||||||
|  |     {"zeroEE", adapterZeroEEPROM}, | ||||||
|  |     {"resetPageEE", adapterResetPageEEPROM}, | ||||||
|  |     {"dumpCFG", Debug_dumpConfig}, | ||||||
|  |     {"dumpPDS", Debug_dumpPersistance}, | ||||||
|  |     {"saveEE", adapterSaveEEPROM}, | ||||||
|  |     {"dumpGlobals", Debug_dumpGlobals}, | ||||||
|  |     {"sdbg", adapterSetDebugPort}, | ||||||
|  |     {"dtc_show", Debug_ShowDTCs}, | ||||||
|  |     {"dtc_clear", ClearAllDTC}, | ||||||
|  |     {"dtc_crit", adapterCritDTC}, | ||||||
|  |     {"dtc_warn", adapterWarnDTC}, | ||||||
|  |     {"dtc_info", adapterInfoDTC}, | ||||||
|  |     {"notify_error", adapterNotifyError}, | ||||||
|  |     {"notify_warning", adapterNotifyWarning}, | ||||||
|  |     {"notify_success", adapterNotifySuccess}, | ||||||
|  |     {"notify_info", adapterNotifyInfo}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const size_t NUM_COMMANDS = sizeof(commandMappings) / sizeof(commandMappings[0]); | ||||||
|  |  | ||||||
|  | const char helpText[][64] PROGMEM = { | ||||||
|  |     "help           - Print this help text", | ||||||
|  |     "sysinfo        - System Info", | ||||||
|  |     "reboot         - System Reboot", | ||||||
|  |     "netinfo        - WiFi Info", | ||||||
|  |     "formatPDS      - Format Persistence EEPROM Data", | ||||||
|  |     "formatCFG      - Format Configuration EEPROM Data", | ||||||
|  |     "checkEE        - Check EEPROM with checksum", | ||||||
|  |     "checkEEfix     - Check and fix EEPROM with checksum", | ||||||
|  |     "dumpEE1k       - Dump the first 1kb of EEPROM to Serial", | ||||||
|  |     "dumpEE         - Dump the whole EEPROM to Serial", | ||||||
|  |     "killEE         - Kill the first 1024 bytes of EEPROM", | ||||||
|  |     "zeroEE         - Zero the first 1024 bytes of EEPROM", | ||||||
|  |     "resetPageEE    - Reset the PersistenceData Page", | ||||||
|  |     "dumpCFG        - Print Config struct", | ||||||
|  |     "dumpPDS        - Print PersistenceStruct", | ||||||
|  |     "saveEE         - Save EE-Data", | ||||||
|  |     "dumpGlobals    - Print globals", | ||||||
|  |     "sdbg           - Set debug port status", | ||||||
|  |     "dtc_show       - Show all DTCs", | ||||||
|  |     "dtc_clear      - Clear all DTCs", | ||||||
|  |     "dtc_crit       - Maintain critical DTC", | ||||||
|  |     "dtc_warn       - Maintain warning DTC", | ||||||
|  |     "dtc_info       - Maintain info DTC", | ||||||
|  |     "notify_error   - Send error notification", | ||||||
|  |     "notify_warning - Send warning notification", | ||||||
|  |     "notify_success - Send success notification", | ||||||
|  |     "notify_info    - Send info notification" | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const size_t NUM_HELP_LINES = sizeof(helpText) / sizeof(helpText[0]); | ||||||
|  |  | ||||||
|  | // Überprüfen, ob die Anzahl der Commands und Hilfetext-Zeilen übereinstimmen | ||||||
|  | static_assert(NUM_COMMANDS == NUM_HELP_LINES, "Number of commands and help text lines do not match!"); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Initializes the debugger by setting the initial status for different debug ports. | ||||||
|  |  *        Serial debug output is turned off. | ||||||
|  |  */ | ||||||
| void initDebugger() | 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 +181,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 +200,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: | ||||||
| @@ -79,44 +208,69 @@ void Debug_Process() | |||||||
|         break; |         break; | ||||||
|  |  | ||||||
|     case CMD_COMPLETE: |     case CMD_COMPLETE: | ||||||
|         processCmdDebug(String(inputBuffer)); |         processCmdDebug(inputBuffer); | ||||||
|         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,54 +278,52 @@ void Debug_pushMessage(const char *format, ...) | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| void processCmdDebug(String command) | /** | ||||||
|  |  * @brief Processes a debug command and performs corresponding actions. | ||||||
|  |  * | ||||||
|  |  * @param command The debug command to be processed. | ||||||
|  |  */ | ||||||
|  | void processCmdDebug(const char *command) | ||||||
| { | { | ||||||
|     if (command == "help") |     bool commandFound = false; | ||||||
|         Debug_printHelp(); |     for (size_t i = 0; i < NUM_COMMANDS; ++i) | ||||||
|     else if (command == "sysinfo") |     { | ||||||
|         Debug_printSystemInfo(); |         if (strcmp(command, commandMappings[i].command) == 0) | ||||||
|     else if (command == "netinfo") |         { | ||||||
|         Debug_printWifiInfo(); |             commandMappings[i].function(); | ||||||
|     else if (command == "formatCFG") |             commandFound = true; | ||||||
|         Debug_formatCFG(); |             break; | ||||||
|     else if (command == "formatPDS") |         } | ||||||
|         Debug_formatPersistence(); |     } | ||||||
|     else if (command == "checkEE") |     if (!commandFound) | ||||||
|         Debug_CheckEEPOM(); |     { | ||||||
|     else if (command == "dumpEE1k") |  | ||||||
|         dumpEEPROM(0, 1024); |  | ||||||
|     else if (command == "dumpEE") |  | ||||||
|         dumpEEPROM(0, EEPROM_SIZE_BYTES); |  | ||||||
|     else if (command == "resetPageEE") |  | ||||||
|         MovePersistencePage_EEPROM(true); |  | ||||||
|     else if (command == "dumpCFG") |  | ||||||
|         Debug_dumpConfig(); |  | ||||||
|     else if (command == "dumpPDS") |  | ||||||
|         Debug_dumpPersistance(); |  | ||||||
|     else if (command == "saveEE") |  | ||||||
|         globals.requestEEAction = EE_ALL_SAVE; |  | ||||||
|     else if (command == "showdtc") |  | ||||||
|         Debug_ShowDTCs(); |  | ||||||
|     else if (command == "dumpGlobals") |  | ||||||
|         Debug_dumpGlobals(); |  | ||||||
|     else if (command == "sdbg") |  | ||||||
|         SetDebugportStatus(dbg_Serial, enabled); |  | ||||||
|     else |  | ||||||
|         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,17 +342,44 @@ 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); | ||||||
|  |  | ||||||
|  |     Debug_pushMessage("globals.systemStatus: %d\n", globals.systemStatus); | ||||||
|  |     Debug_pushMessage("globals.resumeStatus: %d\n", globals.resumeStatus); | ||||||
|  |     Debug_pushMessage("globals.systemStatustxt: %s\n", globals.systemStatustxt); | ||||||
|  |     Debug_pushMessage("globals.requestEEAction: %d\n", globals.requestEEAction); | ||||||
|  |     Debug_pushMessage("globals.DeviceName: %s\n", globals.DeviceName); | ||||||
|  |     Debug_pushMessage("globals.FlashVersion: %s\n", globals.FlashVersion); | ||||||
|  |     Debug_pushMessage("globals.eePersistanceAdress: %u\n", globals.eePersistanceAdress); | ||||||
|  |     Debug_pushMessage("globals.hasDTC: %d\n", globals.hasDTC); | ||||||
|  |     Debug_pushMessage("globals.loadvoltage_mV: %d\n", globals.loadvoltage_mV); | ||||||
|  |     Debug_pushMessage("globals.battery_level: %d\n", globals.battery_level); | ||||||
|  |     Debug_pushMessage("globals.timer_disabled: %d\n", globals.timer_disabled); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Dumps the current configuration parameters to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_dumpConfig() | void Debug_dumpConfig() | ||||||
| { | { | ||||||
|     Debug_pushMessage("batteryType: %d\n", ConfigData.batteryType); |     Debug_pushMessage("batteryType: %d\n", ConfigData.batteryType); | ||||||
|  |     Debug_pushMessage("Faction_1_Name: %s\n", ConfigData.Faction_1_Name); | ||||||
|  |     Debug_pushMessage("Faction_1_Name: %s\n", ConfigData.Faction_2_Name); | ||||||
|  |     Debug_pushMessage("Faction_1_Name: %s\n", ConfigData.Faction_3_Name); | ||||||
|  |     Debug_pushMessage("active_faction_on_reboot: %d\n", ConfigData.active_faction_on_reboot); | ||||||
|  |     Debug_pushMessage("wifi_autoconnect: %d\n", ConfigData.wifi_autoconnect); | ||||||
|  |     Debug_pushMessage("wifi_ap_password: %s\n", ConfigData.wifi_ap_password); | ||||||
|  |     Debug_pushMessage("wifi_ap_ssid: %s\n", ConfigData.wifi_ap_ssid); | ||||||
|  |     Debug_pushMessage("wifi_client_ssid: %s\n", ConfigData.wifi_client_ssid); | ||||||
|  |     Debug_pushMessage("wifi_client_password: %s\n", ConfigData.wifi_client_password); | ||||||
|     Debug_pushMessage("EEPROM_Version: %d\n", ConfigData.EEPROM_Version); |     Debug_pushMessage("EEPROM_Version: %d\n", ConfigData.EEPROM_Version); | ||||||
|     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 +387,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 +406,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 +435,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 +448,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 | debugval\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 +494,63 @@ 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, DTCStorage[i].debugVal); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Prints the help information stored in PROGMEM. | ||||||
|  |  */ | ||||||
| void Debug_printHelp() | void Debug_printHelp() | ||||||
| { | { | ||||||
|     char buff[64]; |     char buffer[64]; | ||||||
|  |     for (size_t i = 0; i < NUM_HELP_LINES; ++i) | ||||||
|     for (unsigned int i = sizeof(helpCmd) / 63; i < sizeof(helpCmd) / 63; i++) |  | ||||||
|     { |     { | ||||||
|         memcpy_P(buff, (helpCmd + (i * 63)), 63); |         strcpy_P(buffer, (PGM_P)pgm_read_word(&(helpText[i]))); | ||||||
|         buff[63] = 0; |  | ||||||
|         Debug_pushMessage(buff); |         // Display the help command | ||||||
|  |         Debug_pushMessage(buffer); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Initiates a system reboot by setting the system status to shutdown. | ||||||
|  |  * | ||||||
|  |  * This function sets the global system status to `sysStat_Shutdown`, | ||||||
|  |  * which will trigger a system reboot sequence. | ||||||
|  |  */ | ||||||
|  | void Debug_Reboot() | ||||||
|  | { | ||||||
|  |     globals.systemStatus = sysStat_Shutdown; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Convert a uint32_t value to a binary string with nibbles separated by a space. | ||||||
|  |  * | ||||||
|  |  * This function takes a uint32_t value and converts it to a binary string | ||||||
|  |  * representation. The binary string is stored in a static buffer and returned | ||||||
|  |  * as a const char pointer. Each nibble (4 bits) in the binary representation | ||||||
|  |  * is separated by a space. The buffer is overwritten on subsequent calls to | ||||||
|  |  * this function. | ||||||
|  |  * | ||||||
|  |  * @param num The uint32_t value to convert. | ||||||
|  |  * @return A pointer to a const char string containing the binary representation | ||||||
|  |  * of the input number with nibbles separated by a space. | ||||||
|  |  */ | ||||||
|  | const char *uint32_to_binary_string(uint32_t num) | ||||||
|  | { | ||||||
|  |     static char binary_str[65]; // 32 bits + 31 spaces + null terminator | ||||||
|  |     int i, j; | ||||||
|  |     for (i = 31, j = 0; i >= 0; i--, j++) | ||||||
|  |     { | ||||||
|  |         binary_str[j] = ((num >> i) & 1) ? '1' : '0'; | ||||||
|  |         if (i % 4 == 0 && i != 0) | ||||||
|  |         { | ||||||
|  |             binary_str[++j] = ' '; // Insert space after every nibble | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     binary_str[j] = '\0'; // Null terminator | ||||||
|  |     return binary_str; | ||||||
|  | } | ||||||
| @@ -1,37 +1,61 @@ | |||||||
|  | /** | ||||||
|  |  * @file dtc.cpp | ||||||
|  |  * @brief Implementation of functions related to Diagnostic Trouble Codes (DTCs). | ||||||
|  |  * | ||||||
|  |  * This file contains the implementation of functions that manage the status | ||||||
|  |  * and registration of Diagnostic Trouble Codes in the system. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date 09.01.2024 | ||||||
|  |  */ | ||||||
| #include "dtc.h" | #include "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,105 @@ | |||||||
| #include "eeprom.h" | /** | ||||||
|  |  * @file config.cpp | ||||||
|  |  * @brief Implementation of EEPROM and configuration-related functions. | ||||||
|  |  * | ||||||
|  |  * This file contains functions for managing EEPROM storage and handling configuration data. | ||||||
|  |  * It includes the definitions of configuration structures, EEPROM access, and utility functions. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include "eeprom.h" | ||||||
|  | #include "debugger.h" | ||||||
|  | #include "globals.h" | ||||||
|  | #include "utilities.h" | ||||||
|  |  | ||||||
|  | // Instance of I2C_eeprom for EEPROM access | ||||||
| I2C_eeprom ee(I2C_EE_ADDRESS, EEPROM_SIZE_BYTES); | 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); |  | ||||||
|  |  | ||||||
| void InitEEPROM() | // Flag indicating whether EEPROM is available | ||||||
|  | boolean eeAvailable = false; | ||||||
|  |  | ||||||
|  | // Offsets within EEPROM for ConfigData and PersistenceData | ||||||
|  | const uint16_t startofConfigData = 16; | ||||||
|  | const uint16_t startofPersistence = 16 + sizeof(ConfigData) + (sizeof(ConfigData) % 16); | ||||||
|  |  | ||||||
|  | // Function prototype to check EEPROM availability | ||||||
|  | boolean checkEEPROMavailable(); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Initializes EEPROM and checks its availability. | ||||||
|  |  * | ||||||
|  |  * This function initializes the EEPROM using the I2C_eeprom instance and checks if it's available. | ||||||
|  |  */ | ||||||
|  | boolean InitEEPROM() | ||||||
| { | { | ||||||
|  |   Wire.begin(); | ||||||
|  |   ConfigData = ConfigData_defaults; | ||||||
|  |   PersistenceData = {0}; | ||||||
|   ee.begin(); |   ee.begin(); | ||||||
|   eeAvailable = checkEEPROMavailable(); |   return 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 +107,46 @@ void EEPROM_Process() | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Stores the configuration data in EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function calculates the checksum for the configuration data, updates it, and stores it in EEPROM. | ||||||
|  |  * It also performs a sanity check on the configuration and raises a diagnostic trouble code (DTC) if needed. | ||||||
|  |  */ | ||||||
| void StoreConfig_EEPROM() | void StoreConfig_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   // Berechnung der Prüfsumme | ||||||
|     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)); | ||||||
|  |  | ||||||
|   ee.updateBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); |   // Überprüfung, ob der EEPROM verfügbar ist | ||||||
|  |   if (!checkEEPROMavailable()) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   // Byteweise in den EEPROM schreiben | ||||||
|  |   uint8_t *dataPtr = (uint8_t *)&ConfigData; | ||||||
|  |   for (uint16_t i = 0; i < sizeof(ConfigData); i++) | ||||||
|  |   { | ||||||
|  |     ee.writeByte(startofConfigData + i, dataPtr[i]); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Sanity Check der Konfiguration | ||||||
|  |   uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); | ||||||
|  |   if (ConfigSanityCheckResult > 0) | ||||||
|  |   { | ||||||
|  |     MaintainDTC(DTC_EEPROM_CFG_SANITY, true, ConfigSanityCheckResult); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Retrieves the configuration data from EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function reads the configuration data from EEPROM, performs a checksum validation, | ||||||
|  |  * and conducts a sanity check on the configuration. It raises a diagnostic trouble code (DTC) if needed. | ||||||
|  |  */ | ||||||
| void GetConfig_EEPROM() | 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 +154,23 @@ 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(true); | ||||||
|  |  | ||||||
|   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 +179,27 @@ 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)); | ||||||
|  |  | ||||||
|   ee.updateBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData)); |   if (!checkEEPROMavailable()) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   // Byteweise in den EEPROM schreiben | ||||||
|  |   uint8_t *dataPtr = (uint8_t *)&PersistenceData; | ||||||
|  |   for (uint16_t i = 0; i < sizeof(PersistenceData); i++) | ||||||
|  |   { | ||||||
|  |     ee.writeByte(globals.eePersistanceAdress + i, dataPtr[i]); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Retrieves the persistence data from EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function reads the EEPROM to get the start address of the persistence data. | ||||||
|  |  * If the start address is out of range, it resets and stores defaults. Otherwise, | ||||||
|  |  * it reads from EEPROM and checks if the data is correct. | ||||||
|  |  */ | ||||||
| void GetPersistence_EEPROM() | 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 +209,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 +218,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 +273,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] = {0}; | ||||||
|   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; |       if (i > 0) // Ensure we don't print an empty ASCII buffer on the first iteration | ||||||
|       Serial.printf("  %s", ascii_buf); |       { | ||||||
|       Serial.printf("\n0x%05X:", memoryAddress); |         ascii_buf[BLOCK_TO_LENGTH] = 0; | ||||||
|  |         Debug_pushMessage("  %s", ascii_buf); | ||||||
|  |       } | ||||||
|  |       Debug_pushMessage("\n0x%05X:", memoryAddress); | ||||||
|  |       memset(ascii_buf, ' ', BLOCK_TO_LENGTH); // Clear the ASCII buffer with spaces | ||||||
|     } |     } | ||||||
|     ascii_buf[blockpoint] = ee.readByte(memoryAddress); |  | ||||||
|     Serial.printf(" %02X", ascii_buf[blockpoint]); |     // Read and print each byte | ||||||
|     if (ascii_buf[blockpoint] < 0x20 || ascii_buf[blockpoint] > 0x7E) |     uint8_t byte = ee.readByte(memoryAddress); | ||||||
|       ascii_buf[blockpoint] = '.'; |     ascii_buf[blockpoint] = (byte >= 0x20 && byte <= 0x7E) ? byte : '.'; | ||||||
|  |     Debug_pushMessage(" %02X", byte); | ||||||
|  |  | ||||||
|     memoryAddress++; |     memoryAddress++; | ||||||
|   } |   } | ||||||
|   Serial.println(); |  | ||||||
|  |   // Print remaining ASCII buffer | ||||||
|  |   ascii_buf[BLOCK_TO_LENGTH] = 0; | ||||||
|  |   Debug_pushMessage("  %s\n", ascii_buf); // Final ASCII line | ||||||
| } | } | ||||||
|  |  | ||||||
| bool checkEEPROMavailable() | /** | ||||||
|  |  * @brief Check if EEPROM is available and connected. | ||||||
|  |  * | ||||||
|  |  * This function checks if the EEPROM is available and connected. If not, it triggers | ||||||
|  |  * a diagnostic trouble code (DTC) indicating the absence of EEPROM. | ||||||
|  |  * | ||||||
|  |  * @return true if EEPROM is available, false otherwise. | ||||||
|  |  */ | ||||||
|  | boolean checkEEPROMavailable() | ||||||
| { | { | ||||||
|  |   // Check if EEPROM is connected | ||||||
|   if (!ee.isConnected()) |   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 Write sequential numbers to a portion of EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function writes sequential numbers starting from 0 to a specified portion of EEPROM. | ||||||
|  |  * If the number reaches 255, it wraps around and starts again from 1. | ||||||
|  |  * | ||||||
|  |  * @param memoryAddress Starting address in EEPROM. | ||||||
|  |  * @param length Number of bytes to write. | ||||||
|  |  */ | ||||||
|  | void writeSequentialToEEPROM(uint16_t memoryAddress, uint16_t length) | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   if (!checkEEPROMavailable()) | ||||||
|     return false; |     return; | ||||||
|  |  | ||||||
|   uint8_t EEPROMVersionOnChip = ee.readByte(startofConfigData); |   uint8_t value = 0; | ||||||
|  |   for (uint16_t i = 0; i < length; 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); |     ee.writeByte(memoryAddress + i, value); | ||||||
|     if (!MigrateEEPROM(EEPROMVersionOnChip)) |     value = (value == 255) ? 1 : value + 1; | ||||||
|     { |   } | ||||||
|       Serial.print("Error\n"); | } | ||||||
|       MaintainDTC(DTC_EEPROM_MIGRATE_FAILED, DTC_CRITICAL, true, EEPROMVersionOnChip); |  | ||||||
|       return false; | /** | ||||||
|     } |  * @brief Write 0 to a portion of EEPROM. | ||||||
|     else |  * | ||||||
|     { |  * This function writes 0 to a specified portion of EEPROM. | ||||||
|       Serial.print("Success\n"); |  * | ||||||
|     } |  * @param memoryAddress Starting address in EEPROM. | ||||||
|   } |  * @param length Number of bytes to write. | ||||||
|   return true; |  */ | ||||||
| } | void writeZeroToEEPROM(uint16_t memoryAddress, uint16_t length) | ||||||
|  | { | ||||||
| bool MigrateEEPROM(uint8_t fromVersion) |   if (!checkEEPROMavailable()) | ||||||
| { |     return; | ||||||
|   uint16_t persistanceMarker_onChip; |  | ||||||
|  |   for (uint16_t i = 0; i < length; i++) | ||||||
|   switch (fromVersion) |   { | ||||||
|   { |     ee.writeByte(memoryAddress + i, 0); | ||||||
|  |  | ||||||
|     // 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; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -6,4 +6,5 @@ void initGlobals() | |||||||
| { | { | ||||||
|   globals.systemStatus = sysStat_Startup; |   globals.systemStatus = sysStat_Startup; | ||||||
|   globals.requestEEAction = EE_IDLE; |   globals.requestEEAction = EE_IDLE; | ||||||
|  |   globals.systemStatustxt[0] = 0; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -68,6 +68,7 @@ bool InitLoRa(void (*MPinHelper)(int, int)) | |||||||
|     } |     } | ||||||
| #elif defined(FEATURE_ENABLE_UARTLORA) | #elif defined(FEATURE_ENABLE_UARTLORA) | ||||||
|     SerialLoRa.begin(9600); |     SerialLoRa.begin(9600); | ||||||
|  |     returnval = true; | ||||||
| #endif | #endif | ||||||
|     return returnval; |     return returnval; | ||||||
| } | } | ||||||
| @@ -78,7 +79,7 @@ void LoRa_Process() | |||||||
|     if (e220ttl.available() > 1) |     if (e220ttl.available() > 1) | ||||||
|     { |     { | ||||||
|         ResponseContainer rc = e220ttl.receiveMessageRSSI(); |         ResponseContainer rc = e220ttl.receiveMessageRSSI(); | ||||||
|         // Is something goes wrong print error |         // If something goes wrong, print error | ||||||
|         if (rc.status.code != 1) |         if (rc.status.code != 1) | ||||||
|         { |         { | ||||||
|             Serial.println(rc.status.getResponseDescription()); |             Serial.println(rc.status.getResponseDescription()); | ||||||
| @@ -94,41 +95,47 @@ void LoRa_Process() | |||||||
|     } |     } | ||||||
| #elif defined(FEATURE_ENABLE_UARTLORA) | #elif defined(FEATURE_ENABLE_UARTLORA) | ||||||
|  |  | ||||||
|     char packageInput[16]; |     static char packageInput[32]; | ||||||
|     bool packageRecieved = false; |     static bool packageReceived = false; | ||||||
|     unsigned int bufferPtr = 0; |     static unsigned int bufferPtr = 0; | ||||||
|  |     int receivedSize = 0; | ||||||
|  |  | ||||||
|     if (SerialLoRa.available() && packageRecieved == false) |     while (SerialLoRa.available() && !packageReceived) | ||||||
|     { |     { | ||||||
|         while (bufferPtr < sizeof(packageInput)) |         if (bufferPtr < sizeof(packageInput) - 1) | ||||||
|         { |         { | ||||||
|             packageInput[bufferPtr] = SerialLoRa.read(); |             char c = SerialLoRa.read(); | ||||||
|             if (packageInput[bufferPtr] == '\n') |             packageInput[bufferPtr] = c; | ||||||
|  |             packageInput[bufferPtr + 1] = '\0'; // always terminate string | ||||||
|  |  | ||||||
|  |             if (c == '\n') | ||||||
|             { |             { | ||||||
|                 packageInput[bufferPtr] = 0; // terminate String |                 packageReceived = true; | ||||||
|                 packageRecieved = true; |                 receivedSize = bufferPtr; | ||||||
|  |                 bufferPtr = 0; | ||||||
|                 Debug_pushMessage("Got LoRa UART: %s\n", packageInput); |                 Debug_pushMessage("Got LoRa UART: %s\n", packageInput); | ||||||
|                 break; |  | ||||||
|             } |             } | ||||||
|             else if ((packageInput[bufferPtr] >= 0x30) || (packageInput[bufferPtr] <= 0x5A)) // only accept Numbers, UpperCase-Letters and some special chars |             else if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c == ' ' || c == ',' || c == '.')) // Accept numbers, uppercase letters, and some special chars | ||||||
|             { |             { | ||||||
|                 if (bufferPtr < sizeof(packageInput) - 1) |                 bufferPtr++; | ||||||
|                 { |  | ||||||
|                     bufferPtr++; |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     packageInput[bufferPtr] = 0; // terminate String, bc Buffer is full (package to long) |  | ||||||
|                     packageRecieved = true;      // send it anyway to the parser |  | ||||||
|                     Debug_pushMessage("Got LoRa UART: %s\n", packageInput); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 Debug_pushMessage("Invalid character received: %c\n", c); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             Debug_pushMessage("Buffer overflow. Resetting buffer.\n"); | ||||||
|  |             bufferPtr = 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (packageRecieved) |     if (packageReceived) | ||||||
|         Parse_LoRa_UartCommand(packageInput, bufferPtr); |     { | ||||||
|  |         Parse_LoRa_UartCommand(packageInput, receivedSize); | ||||||
|  |         packageReceived = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| @@ -242,6 +249,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\n", size); | ||||||
|     char delimiter[] = ";"; |     char delimiter[] = ";"; | ||||||
|     char *ptr; |     char *ptr; | ||||||
|     char command[8]; |     char command[8]; | ||||||
| @@ -249,11 +257,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\n", 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); | ||||||
| @@ -261,10 +286,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\n"); | ||||||
|     } |     } | ||||||
|     else if (!strcmp(command, "DISABLE")) |     else if (!strcmp(command, "DISABLE")) | ||||||
|     { |     { | ||||||
|         globals.timer_disabled = false; |         globals.timer_disabled = true; | ||||||
|  |         Debug_pushMessage("Disabled by LoRa\n"); | ||||||
|     } |     } | ||||||
|     else if (!strcmp(command, "RESET")) |     else if (!strcmp(command, "RESET")) | ||||||
|     { |     { | ||||||
| @@ -272,6 +299,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\n"); | ||||||
|     } |     } | ||||||
|     else if (!strcmp(command, "TMRSTP")) |     else if (!strcmp(command, "TMRSTP")) | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ | |||||||
| #include "globals.h" | #include "globals.h" | ||||||
| #include "dtc.h" | #include "dtc.h" | ||||||
| #include "debugger.h" | #include "debugger.h" | ||||||
|  | #include "utilities.h" | ||||||
| #if defined(FEATURE_ENABLE_LORA) || defined(FEATURE_ENABLE_UARTLORA) | #if defined(FEATURE_ENABLE_LORA) || defined(FEATURE_ENABLE_UARTLORA) | ||||||
| #include "lora_net.h" | #include "lora_net.h" | ||||||
| #endif | #endif | ||||||
| @@ -42,12 +43,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 +61,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,24 +81,49 @@ 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 | ||||||
|  | 	strncpy(globals.DeviceName, HOST_NAME, sizeof(globals.DeviceName)); | ||||||
|  | 	snprintf(globals.DeviceNameId, sizeof(globals.DeviceNameId), "%s_%08X", globals.DeviceName, ESP.getChipId()); | ||||||
|  |  | ||||||
|  | 	// Disable WiFi persistent storage | ||||||
|  | 	WiFi.persistent(false); | ||||||
|  |  | ||||||
|  | 	// Initialize and clear Diagnostic Trouble Code (DTC) storage | ||||||
|  | 	ClearAllDTC(); | ||||||
|  |  | ||||||
|  | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
|  | 	// Configure WiFi settings for client mode if enabled | ||||||
|  | 	WiFi.mode(WIFI_STA); | ||||||
|  | 	WiFi.setHostname(globals.DeviceNameId); | ||||||
|  | 	wifiMulti.addAP(QUOTE(WIFI_SSID_CLIENT), QUOTE(WIFI_PASSWORD_CLIENT)); | ||||||
|  | 	tmrWiFiMaintainConnection.start(); | ||||||
|  | #else | ||||||
|  | 	// Disable WiFi if WiFi client feature is not enabled | ||||||
|  | 	WiFi.mode(WIFI_OFF); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | 	// Initialize Serial communication | ||||||
| 	Serial.begin(115200); | 	Serial.begin(115200); | ||||||
| 	Serial.setDebugOutput(false); | 	Serial.setDebugOutput(false); | ||||||
|  |  | ||||||
| 	Serial.print("\n\n-------------------START-------------------\n"); | 	Serial.print("\n\n-------------------START-------------------\n"); | ||||||
| 	Serial.print(globals.DeviceName); | 	Serial.print(globals.DeviceNameId); | ||||||
| 	Serial.print("\nby Hiabuto Defense\n"); | 	Serial.print("\nby Hiabuto Defense\n\n"); | ||||||
|  |  | ||||||
| 	ClearAllDTC(); // Init DTC-Storage | 	// Initialize EEPROM, load configuration, and persistence data from EEPROM | ||||||
|  | 	if (InitEEPROM()) | ||||||
| 	InitEEPROM(); | 	{ | ||||||
| 	GetConfig_EEPROM(); | 		GetConfig_EEPROM(); | ||||||
| 	GetPersistence_EEPROM(); | 		GetPersistence_EEPROM(); | ||||||
|  | 		Serial.printf("Initialized EEPROM at Address 0x%02X\n", I2C_EE_ADDRESS); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		Serial.print("EEPROM not Initialized\n"); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if (i2c_io.begin()) | 	if (i2c_io.begin()) | ||||||
| 	{ | 	{ | ||||||
| @@ -131,18 +156,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.DeviceNameId); | ||||||
| 	ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD)); | 	ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD)); | ||||||
|  |  | ||||||
| 	ArduinoOTA.onStart([]() | 	ArduinoOTA.onStart([]() | ||||||
| @@ -186,15 +202,20 @@ 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(); | ||||||
|  |  | ||||||
| 	disp_FAC_1.init(); | 	disp_FAC_1.init(); | ||||||
| 	disp_FAC_1.setBrightness(5); | 	disp_FAC_1.setBrightness(5); | ||||||
| @@ -203,7 +224,6 @@ void setup() | |||||||
| 	disp_FAC_3.init(); | 	disp_FAC_3.init(); | ||||||
| 	disp_FAC_3.setBrightness(5); | 	disp_FAC_3.setBrightness(5); | ||||||
|  |  | ||||||
| 	tmrEEPROMCyclicPDS.start(); |  | ||||||
| 	tmrFactionTicker.start(); | 	tmrFactionTicker.start(); | ||||||
| 	tmrInputGetter.start(); | 	tmrInputGetter.start(); | ||||||
|  |  | ||||||
| @@ -215,8 +235,6 @@ void setup() | |||||||
|  |  | ||||||
| void loop() | void loop() | ||||||
| { | { | ||||||
| 	maintainSysStat(); |  | ||||||
|  |  | ||||||
| 	tmrEEPROMCyclicPDS.update(); | 	tmrEEPROMCyclicPDS.update(); | ||||||
| 	tmrFactionTicker.update(); | 	tmrFactionTicker.update(); | ||||||
| 	tmrInputGetter.update(); | 	tmrInputGetter.update(); | ||||||
| @@ -234,13 +252,64 @@ 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_Init; | ||||||
|  |  | ||||||
|  | 	// Handle different system statuses | ||||||
|  | 	switch (globals.systemStatus) | ||||||
|  | 	{ | ||||||
|  | 	case sysStat_Startup: | ||||||
|  | 		if (lastStatus != globals.systemStatus) | ||||||
|  | 		{ | ||||||
|  | 			strcpy_P(globals.systemStatustxt, PSTR("Startup")); | ||||||
|  | 			lastStatus = globals.systemStatus; | ||||||
|  | 		} | ||||||
|  | 		// Transition to Normal status after startup delay | ||||||
|  | 		if (millis() > STARTUP_DELAY) | ||||||
|  | 		{ | ||||||
|  | 			globals.systemStatus = sysStat_Normal; | ||||||
|  | 			globals.resumeStatus = sysStat_Normal; | ||||||
|  | 		} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 	case sysStat_Normal: | ||||||
|  | 		if (lastStatus != globals.systemStatus) | ||||||
|  | 		{ | ||||||
|  | 			strcpy_P(globals.systemStatustxt, PSTR("Normal")); | ||||||
|  | 			lastStatus = globals.systemStatus; | ||||||
|  | 		} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		if (lastStatus != globals.systemStatus) | ||||||
|  | 		{ | ||||||
|  | 			strcpy_P(globals.systemStatustxt, PSTR("Error")); | ||||||
|  | 			lastStatus = globals.systemStatus; | ||||||
|  | 		} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 	case sysStat_Shutdown: | ||||||
|  | 		if (lastStatus != globals.systemStatus) | ||||||
|  | 		{ | ||||||
|  | 			strcpy_P(globals.systemStatustxt, PSTR("Shutdown")); | ||||||
|  | 			lastStatus = globals.systemStatus; | ||||||
|  | 		} | ||||||
|  | 		SystemShutdown(false); | ||||||
|  | 		break; | ||||||
|  | 	case sysStat_Error: | ||||||
|  | 		if (lastStatus != globals.systemStatus) | ||||||
|  | 		{ | ||||||
|  | 			strcpy_P(globals.systemStatustxt, PSTR("Error")); | ||||||
|  | 			lastStatus = globals.systemStatus; | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Yield to allow other tasks to run | ||||||
| 	yield(); | 	yield(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -254,7 +323,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 +339,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 +359,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); | ||||||
| 		} | 		} | ||||||
| @@ -347,36 +421,36 @@ void tmrCallback_InputGetter() | |||||||
|  |  | ||||||
| 	if (keysPressed > 1) | 	if (keysPressed > 1) | ||||||
| 	{ | 	{ | ||||||
| 		Debug_pushMessage("ERROR: More than one Flag active - setting no Faction active"); | 		Debug_pushMessage("ERROR: More than one Flag active - setting no Faction active\n"); | ||||||
| 		PersistenceData.activeFaction = NONE; | 		PersistenceData.activeFaction = NONE; | ||||||
| 		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) | ||||||
| 		{ | 		{ | ||||||
| 			Debug_pushMessage("Faction 1 captured !"); | 			Debug_pushMessage("Faction 1 captured !\n"); | ||||||
| 			globals.requestEEAction = EE_PDS_SAVE; | 			globals.requestEEAction = EE_PDS_SAVE; | ||||||
| 		} | 		} | ||||||
| 		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) | ||||||
| 		{ | 		{ | ||||||
| 			Debug_pushMessage("Faction 2 captured !"); | 			Debug_pushMessage("Faction 2 captured !\n"); | ||||||
| 			globals.requestEEAction = EE_PDS_SAVE; | 			globals.requestEEAction = EE_PDS_SAVE; | ||||||
| 		} | 		} | ||||||
| 		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) | ||||||
| 		{ | 		{ | ||||||
| 			Debug_pushMessage("Faction 3 captured !"); | 			Debug_pushMessage("Faction 3 captured !\n"); | ||||||
| 			globals.requestEEAction = EE_PDS_SAVE; | 			globals.requestEEAction = EE_PDS_SAVE; | ||||||
| 		} | 		} | ||||||
| 		PersistenceData.activeFaction = FACTION_3; | 		PersistenceData.activeFaction = FACTION_3; | ||||||
| @@ -425,8 +499,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 +510,133 @@ 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) | ||||||
| { | { | ||||||
|  | 	char buffer[33]; | ||||||
|  | 	// 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)); | 		sanitizeWiFiString(globals.DeviceNameId, buffer, sizeof(buffer)); | ||||||
|  | 		WiFi.softAP(buffer, QUOTE(WIFI_AP_PASSWORD)); | ||||||
|  |  | ||||||
|  | 		// Stop WiFi maintenance connection ticker if enabled and display debug messages | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #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) | ||||||
|  | 				; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -571,7 +704,7 @@ void ProcessKeyCombos(bool *btnState) | |||||||
|  |  | ||||||
| 		if (keyCount_Fac2 == 2 && keyCount_Fac3 == 0) | 		if (keyCount_Fac2 == 2 && keyCount_Fac3 == 0) | ||||||
| 		{ | 		{ | ||||||
| 			Debug_pushMessage("KeyCombo: WiFi AP ON"); | 			Debug_pushMessage("KeyCombo: WiFi AP ON\n"); | ||||||
| 			OverrideDisplay(5000, "NET ", "    ", "    "); | 			OverrideDisplay(5000, "NET ", "    ", "    "); | ||||||
| 			toggleWiFiAP(false); | 			toggleWiFiAP(false); | ||||||
| 		} | 		} | ||||||
| @@ -605,7 +738,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 +754,6 @@ void maintainSysStat() | |||||||
|  |  | ||||||
| 	case sysStat_Error: | 	case sysStat_Error: | ||||||
| 	case sysStat_Normal: | 	case sysStat_Normal: | ||||||
| 	case sysStat_null: |  | ||||||
| 	default: | 	default: | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
| @@ -641,7 +773,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 | ||||||
							
								
								
									
										94
									
								
								Software/src/utilities.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								Software/src/utilities.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | #include "utilities.h" | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Validates whether a given string contains only characters allowed in WiFi SSIDs and passwords. | ||||||
|  |  * | ||||||
|  |  * This function checks each character in the provided string to ensure | ||||||
|  |  * that it contains only characters allowed in WiFi SSIDs and passwords. | ||||||
|  |  * It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as | ||||||
|  |  * the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ | ||||||
|  |  * | ||||||
|  |  * @param string Pointer to the string to be validated. | ||||||
|  |  * @param size Size of the string including the null-terminator. | ||||||
|  |  * @return true if the string contains only allowed characters or is NULL, | ||||||
|  |  *         false otherwise. | ||||||
|  |  */ | ||||||
|  | bool validateWiFiString(char *string, size_t size) | ||||||
|  | { | ||||||
|  |     if (string == NULL) | ||||||
|  |         return false; | ||||||
|  |  | ||||||
|  |     for (size_t i = 0; i < size; i++) | ||||||
|  |     { | ||||||
|  |         char c = string[i]; | ||||||
|  |         if (c == '\0') | ||||||
|  |         { | ||||||
|  |             // Reached the end of the string, all characters were valid WiFi characters. | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || | ||||||
|  |               (c >= '0' && c <= '9') || c == '!' || c == '"' || c == '#' || | ||||||
|  |               c == '$' || c == '%' || c == '&' || c == '\'' || c == '(' || | ||||||
|  |               c == ')' || c == '*' || c == '+' || c == ',' || c == '-' || | ||||||
|  |               c == '.' || c == '/' || c == ':' || c == ';' || c == '<' || | ||||||
|  |               c == '=' || c == '>' || c == '?' || c == '@' || c == '[' || | ||||||
|  |               c == '\\' || c == ']' || c == '^' || c == '_' || c == '`' || | ||||||
|  |               c == '{' || c == '|' || c == '}' || c == '~')) | ||||||
|  |         { | ||||||
|  |             // Found a character that is not a valid WiFi character. | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     // If the loop completes without finding a null terminator, the string is invalid. | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Copies a string to a buffer, replacing invalid WiFi SSID characters with a placeholder. | ||||||
|  |  * | ||||||
|  |  * This function checks each character in the provided input string to ensure | ||||||
|  |  * that it contains only characters allowed in WiFi SSIDs and passwords. If a character | ||||||
|  |  * is invalid, it replaces it with a placeholder character (e.g., '_'). | ||||||
|  |  * It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as | ||||||
|  |  * the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ | ||||||
|  |  * | ||||||
|  |  * @param input Pointer to the input string to be validated and copied. | ||||||
|  |  * @param buffer Pointer to the buffer where the output string will be copied. | ||||||
|  |  * @param bufferSize Size of the buffer including the null-terminator. | ||||||
|  |  */ | ||||||
|  | void sanitizeWiFiString(const char *input, char *buffer, size_t bufferSize) | ||||||
|  | { | ||||||
|  |     if (input == NULL || buffer == NULL || bufferSize == 0) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|  |     size_t i; | ||||||
|  |     for (i = 0; i < bufferSize - 1; i++) // Leave space for null-terminator | ||||||
|  |     { | ||||||
|  |         char c = input[i]; | ||||||
|  |         if (c == '\0') | ||||||
|  |         { | ||||||
|  |             // Reached the end of the input string, terminate the buffer | ||||||
|  |             buffer[i] = '\0'; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || | ||||||
|  |               (c >= '0' && c <= '9') || c == '!' || c == '"' || c == '#' || | ||||||
|  |               c == '$' || c == '%' || c == '&' || c == '\'' || c == '(' || | ||||||
|  |               c == ')' || c == '*' || c == '+' || c == ',' || c == '-' || | ||||||
|  |               c == '.' || c == '/' || c == ':' || c == ';' || c == '<' || | ||||||
|  |               c == '=' || c == '>' || c == '?' || c == '@' || c == '[' || | ||||||
|  |               c == '\\' || c == ']' || c == '^' || c == '_' || c == '`' || | ||||||
|  |               c == '{' || c == '|' || c == '}' || c == '~')) | ||||||
|  |         { | ||||||
|  |             // Replace invalid character with placeholder | ||||||
|  |             buffer[i] = '_'; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             // Copy valid character to buffer | ||||||
|  |             buffer[i] = c; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     // Null-terminate the buffer | ||||||
|  |     buffer[i] = '\0'; | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,5 @@ | |||||||
| [wifi_cred] | [wifi_cred] | ||||||
| wifi_ap_ssid = wifi-ap-ssid | admin_password = chainlube | ||||||
| wifi_ap_password = wifiappass | wifi_ap_password = wifiappass | ||||||
| wifi_client_ssid = wifi-ssid | wifi_ssid_client = wifi-ssid | ||||||
| wifi_client_password = wifi-pass | wifi_password_client = ota-password | ||||||
| ota_password = ota-password |  | ||||||
		Reference in New Issue
	
	Block a user