Compare commits
	
		
			18 Commits
		
	
	
		
			PCB_Rev_1.
			...
			68d571a747
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 68d571a747 | |||
| b7ccffc157 | |||
| c7af5619eb | |||
| 3357691a21 | |||
| 5638c03e76 | |||
| 051796b19b | |||
| cf032fe516 | |||
| 618ee4ce80 | |||
| 41d38a2afc | |||
| bb824f0376 | |||
| d08e0cfbb3 | |||
| 479b1861d1 | |||
| b276d74907 | |||
| 8c022188c6 | |||
| 4d020b5ced | |||
| c399672755 | |||
| dd34bfe645 | |||
| 125fc17c39 | 
@@ -11738,7 +11738,7 @@
 | 
			
		||||
		(layer "Edge.Cuts")
 | 
			
		||||
		(uuid "da6f4122-0ecc-496f-b0fd-e4abef534976")
 | 
			
		||||
	)
 | 
			
		||||
	(gr_text "KTM CAN ChainLube Mk1\nPCB Rev.: 1.4\n(c) 2025 - Marcel Peterkau"
 | 
			
		||||
	(gr_text "KTM CAN ChainLube Mk1\nPCB Rev.: 1.5\n(c) 2025 - Marcel Peterkau"
 | 
			
		||||
		(at 156.464 85.344 90)
 | 
			
		||||
		(layer "B.SilkS")
 | 
			
		||||
		(uuid "00000000-0000-0000-0000-000061de6936")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										119
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
pipeline {
 | 
			
		||||
    agent any
 | 
			
		||||
 | 
			
		||||
    environment {
 | 
			
		||||
        BUILD_ENV = "pcb_rev_1-4_serial"
 | 
			
		||||
        PIO_HOME_DIR = "${WORKSPACE}/.pio"
 | 
			
		||||
        VENV_PATH = "${WORKSPACE}/Software/.venv"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parameters {
 | 
			
		||||
        choice(
 | 
			
		||||
            name: 'BUILD_ENV',
 | 
			
		||||
            choices: ['pcb_rev_1-2_serial', 'pcb_rev_1-3_serial', 'pcb_rev_1-4_serial'],
 | 
			
		||||
            description: 'Firmware-Umgebung auswählen'
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stages {
 | 
			
		||||
 | 
			
		||||
        stage('🧰 Setup PlatformIO') {
 | 
			
		||||
            steps {
 | 
			
		||||
                dir('Software') {
 | 
			
		||||
                    sh """
 | 
			
		||||
                        [ -d .venv ] || python3 -m venv .venv
 | 
			
		||||
                        ${env.VENV_PATH}/bin/pip install --upgrade pip platformio
 | 
			
		||||
                    """
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stage('📄 Dummy WiFi-Creds') {
 | 
			
		||||
            steps {
 | 
			
		||||
                dir('Software') {
 | 
			
		||||
                    writeFile file: 'wifi_credentials.ini', text: '''
 | 
			
		||||
[wifi_cred]
 | 
			
		||||
wifi_ssid_client = DummySSID
 | 
			
		||||
wifi_password_client = DummyPass
 | 
			
		||||
admin_password = Admin123
 | 
			
		||||
wifi_ap_password = DummyAP
 | 
			
		||||
                    '''.stripIndent()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stage('🧪 Build Firmware') {
 | 
			
		||||
            steps {
 | 
			
		||||
                dir('Software') {
 | 
			
		||||
                    sh """
 | 
			
		||||
                        ${env.VENV_PATH}/bin/platformio run -e ${params.BUILD_ENV}
 | 
			
		||||
                    """
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stage('📁 Build Filesystem (LittleFS)') {
 | 
			
		||||
            steps {
 | 
			
		||||
                dir('Software') {
 | 
			
		||||
                    sh """
 | 
			
		||||
                        ${env.VENV_PATH}/bin/platformio run -t buildfs -e ${params.BUILD_ENV}
 | 
			
		||||
                    """
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stage('📦 Find & Archive Firmware') {
 | 
			
		||||
            steps {
 | 
			
		||||
                dir('Software') {
 | 
			
		||||
                    script {
 | 
			
		||||
                        echo "🔍 Suche nach Firmware (.fw.bin) und Filesystem (.fs.gz) Artefakten..."
 | 
			
		||||
 | 
			
		||||
                        def firmwareFiles = findFiles(glob: '.pio/build/**/*.fw.bin')
 | 
			
		||||
                        def fsFiles       = findFiles(glob: '.pio/build/**/*.fs.gz')
 | 
			
		||||
 | 
			
		||||
                        if (firmwareFiles.length == 0 && fsFiles.length == 0) {
 | 
			
		||||
                        echo "⚠️ Keine passenden Artefakte (.fw.bin / .fs.gz) gefunden – nichts zu archivieren."
 | 
			
		||||
                        } else {
 | 
			
		||||
                        firmwareFiles.each { echo "📦 Firmware: ${it.path}" }
 | 
			
		||||
                        fsFiles.each      { echo "📦 Filesystem: ${it.path}" }
 | 
			
		||||
 | 
			
		||||
                        def allArtifacts = (firmwareFiles + fsFiles).collect { it.path }
 | 
			
		||||
                        archiveArtifacts artifacts: allArtifacts.join(', ')
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stage('🔌 Flash Hardware (Dummy)') {
 | 
			
		||||
            steps {
 | 
			
		||||
                echo "TODO: Flash-Script aufrufen, z. B.: python3 Hardware/flash.py /dev/ttyUSB0"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stage('🧠 Run Tests (Dummy)') {
 | 
			
		||||
            steps {
 | 
			
		||||
                dir('Testing') {
 | 
			
		||||
                    echo "TODO: Testing mit z. B.: python3 test_runner.py oder pytest starten"
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stage('🧹 Cleanup Build Output') {
 | 
			
		||||
            steps {
 | 
			
		||||
                dir('Software') {
 | 
			
		||||
                    sh "rm -rf .pio"
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    post {
 | 
			
		||||
        success {
 | 
			
		||||
            echo "✅ CI abgeschlossen – Firmware gebaut, Dummy-Stages bereit"
 | 
			
		||||
        }
 | 
			
		||||
        failure {
 | 
			
		||||
            echo "❌ Fehler im Build – Logs checken, Commander Seraphon"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								Software/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								Software/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -2,4 +2,7 @@ data/
 | 
			
		||||
.pio
 | 
			
		||||
.vscode
 | 
			
		||||
wifi_credentials.ini
 | 
			
		||||
__pycache__
 | 
			
		||||
__pycache__
 | 
			
		||||
# Node-Tools für Build-Scripts
 | 
			
		||||
/tools_node/
 | 
			
		||||
/data_stripped/
 | 
			
		||||
 
 | 
			
		||||
@@ -11,23 +11,35 @@ import platform
 | 
			
		||||
Import("env")
 | 
			
		||||
Import("projenv")
 | 
			
		||||
 | 
			
		||||
# Überprüfe die Betriebssystemplattform
 | 
			
		||||
if platform.system() == "Windows":
 | 
			
		||||
    # Setze die Pfade zu den Tools für Windows
 | 
			
		||||
    html_minifier_path = os.path.join(os.getenv("APPDATA"), "npm", "html-minifier.cmd")
 | 
			
		||||
    uglifyjs_path = os.path.join(os.getenv("APPDATA"), "npm", "uglifyjs.cmd")
 | 
			
		||||
    terser_path = os.path.join(os.getenv("APPDATA"), "npm", "terser.cmd")
 | 
			
		||||
    cssnano_path = os.path.join(os.getenv("APPDATA"), "npm", "cssnano.cmd")
 | 
			
		||||
elif platform.system() == "Linux":
 | 
			
		||||
    # Setze die Namen der Tools für Linux
 | 
			
		||||
    html_minifier_path = "html-minifier"
 | 
			
		||||
    uglifyjs_path = "uglifyjs"
 | 
			
		||||
    terser_path = "terser"
 | 
			
		||||
    cssnano_path = "cssnano"
 | 
			
		||||
else:
 | 
			
		||||
    # Hier könntest du weitere Bedingungen für andere Betriebssysteme hinzufügen
 | 
			
		||||
    raise Exception("Unterstütztes Betriebssystem nicht erkannt")
 | 
			
		||||
def ensure_node_tool(package_name, binary_name=None):
 | 
			
		||||
    """Installiert das Tool lokal, wenn es fehlt – mit npm init bei Bedarf"""
 | 
			
		||||
    if binary_name is None:
 | 
			
		||||
        binary_name = package_name
 | 
			
		||||
 | 
			
		||||
    project_dir = env.subst('$PROJECT_DIR')
 | 
			
		||||
    tools_dir = os.path.join(project_dir, 'tools_node')
 | 
			
		||||
    local_bin = os.path.join(tools_dir, 'node_modules', '.bin', binary_name)
 | 
			
		||||
 | 
			
		||||
    os.makedirs(tools_dir, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
    # Initialisiere npm, falls noch nicht geschehen
 | 
			
		||||
    if not os.path.isfile(os.path.join(tools_dir, 'package.json')):
 | 
			
		||||
        print("🛠️  Initializing local npm project in tools_node...")
 | 
			
		||||
        subprocess.run(['npm', 'init', '-y'], cwd=tools_dir, check=True)
 | 
			
		||||
 | 
			
		||||
    # Installiere Tool (idempotent)
 | 
			
		||||
    try:
 | 
			
		||||
        subprocess.run(['npm', 'install', package_name], cwd=tools_dir, check=True)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print(f"❌ Fehler beim Installieren von {package_name}: {e}")
 | 
			
		||||
 | 
			
		||||
    return local_bin if os.path.isfile(local_bin) else binary_name
 | 
			
		||||
 | 
			
		||||
# Tools sicherstellen
 | 
			
		||||
# Tools sicherstellen – Package-Name und CLI-Binary ggf. unterschiedlich
 | 
			
		||||
html_minifier_path = ensure_node_tool("html-minifier")
 | 
			
		||||
terser_path         = ensure_node_tool("terser")
 | 
			
		||||
cssnano_path        = ensure_node_tool("cssnano-cli", "cssnano")
 | 
			
		||||
 | 
			
		||||
def minify_html(input_path, output_path):
 | 
			
		||||
    subprocess.run([html_minifier_path, '--collapse-whitespace', '--remove-comments', input_path, '-o', output_path])
 | 
			
		||||
@@ -40,13 +52,7 @@ def minify_css(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)
 | 
			
		||||
 | 
			
		||||
    os.makedirs(os.path.dirname(dest_path), exist_ok=True)
 | 
			
		||||
    if file_extension.lower() == '.js':
 | 
			
		||||
        minify_js(src_path, dest_path)
 | 
			
		||||
    elif file_extension.lower() == '.css':
 | 
			
		||||
@@ -54,128 +60,89 @@ def process_file(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)
 | 
			
		||||
            rel_path = os.path.relpath(src_path, src_dir)
 | 
			
		||||
            dest_path = os.path.join(dest_dir, rel_path)
 | 
			
		||||
            process_file(src_path, dest_path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def gzip_file(src_path, dst_path):
 | 
			
		||||
 | 
			
		||||
    with open(src_path, 'rb') as src, gzip.open(dst_path, 'wb') as dst:
 | 
			
		||||
        for chunk in iter(lambda: src.read(4096), b""):
 | 
			
		||||
            dst.write(chunk)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getListOfFiles(dirName):
 | 
			
		||||
    # create a list of file and sub directories
 | 
			
		||||
    # names in the given directory
 | 
			
		||||
    listOfFile = os.listdir(dirName)
 | 
			
		||||
    allFiles = list()
 | 
			
		||||
    # Iterate over all the entries
 | 
			
		||||
    for entry in listOfFile:
 | 
			
		||||
        # Create full path
 | 
			
		||||
    entries = os.listdir(dirName)
 | 
			
		||||
    allFiles = []
 | 
			
		||||
    for entry in entries:
 | 
			
		||||
        fullPath = os.path.join(dirName, entry)
 | 
			
		||||
        # If entry is a directory then get the list of files in this directory
 | 
			
		||||
        if os.path.isdir(fullPath):
 | 
			
		||||
            allFiles = allFiles + getListOfFiles(fullPath)
 | 
			
		||||
            allFiles += getListOfFiles(fullPath)
 | 
			
		||||
        else:
 | 
			
		||||
            allFiles.append(fullPath)
 | 
			
		||||
 | 
			
		||||
    return allFiles
 | 
			
		||||
 | 
			
		||||
def remove_prefix(text, prefix):
 | 
			
		||||
    if text.startswith(prefix):
 | 
			
		||||
        return text[len(prefix):]
 | 
			
		||||
    return text  # or whatever
 | 
			
		||||
 | 
			
		||||
# Compress files from 'data_src/' to 'data/'
 | 
			
		||||
 | 
			
		||||
def safe_relpath(path, start):
 | 
			
		||||
    return os.path.relpath(path, start).replace("\\", "/")
 | 
			
		||||
 | 
			
		||||
def gzip_webfiles(source, target, env):
 | 
			
		||||
    # Filetypes to compress
 | 
			
		||||
    filetypes_to_gzip = ['.css', '.png', '.js', '.ico', '.woff2', '.json']
 | 
			
		||||
    print('\nGZIP: Starting gzip-Process for LittleFS-Image...\n')
 | 
			
		||||
    data_src_dir_path = os.path.join(env.get('PROJECT_DIR'), 'data_src')
 | 
			
		||||
    data_temp_dir_path = os.path.join(env.get('PROJECT_DIR'), 'data_stripped')
 | 
			
		||||
    strip_files(data_src_dir_path, data_temp_dir_path)
 | 
			
		||||
    data_dir_path = env.get('PROJECT_DATA_DIR')
 | 
			
		||||
    # check if data and datasrc exist. If the first exists and not the second, it renames it
 | 
			
		||||
    if(os.path.exists(data_dir_path) and not os.path.exists(data_temp_dir_path)):
 | 
			
		||||
        print('GZIP: Directory "'+data_dir_path +
 | 
			
		||||
              '" exists, "'+data_temp_dir_path+'" is not found.')
 | 
			
		||||
        print('GZIP: Renaming "' + data_dir_path +
 | 
			
		||||
              '" to "' + data_temp_dir_path + '"')
 | 
			
		||||
        os.rename(data_dir_path, data_temp_dir_path)
 | 
			
		||||
    # Delete the 'data' directory
 | 
			
		||||
    if(os.path.exists(data_dir_path)):
 | 
			
		||||
        print('GZIP: Deleting the "data" directory ' + data_dir_path)
 | 
			
		||||
        shutil.rmtree(data_dir_path)
 | 
			
		||||
    # Recreate empty 'data' directory
 | 
			
		||||
    print('GZIP: Re-creating an empty data directory ' + data_dir_path)
 | 
			
		||||
    os.mkdir(data_dir_path)
 | 
			
		||||
    # Determine the files to compress
 | 
			
		||||
    src_dir = os.path.join(env.get('PROJECT_DIR'), 'data_src')
 | 
			
		||||
    temp_dir = os.path.join(env.get('PROJECT_DIR'), 'data_stripped')
 | 
			
		||||
    dst_dir = env.get('PROJECT_DATA_DIR')
 | 
			
		||||
 | 
			
		||||
    strip_files(src_dir, temp_dir)
 | 
			
		||||
 | 
			
		||||
    if os.path.exists(dst_dir):
 | 
			
		||||
        shutil.rmtree(dst_dir)
 | 
			
		||||
    os.mkdir(dst_dir)
 | 
			
		||||
 | 
			
		||||
    files_to_copy = []
 | 
			
		||||
    files_to_gzip = []
 | 
			
		||||
 | 
			
		||||
    all_data_src = getListOfFiles(data_temp_dir_path)
 | 
			
		||||
    for file in all_data_src:
 | 
			
		||||
        file_name, file_extension = os.path.splitext(file)
 | 
			
		||||
        print(file_name + " has filetype " + file_extension)
 | 
			
		||||
        if file_extension in filetypes_to_gzip:
 | 
			
		||||
    for file in getListOfFiles(temp_dir):
 | 
			
		||||
        _, ext = os.path.splitext(file)
 | 
			
		||||
        if ext in filetypes_to_gzip:
 | 
			
		||||
            files_to_gzip.append(file)
 | 
			
		||||
        else:
 | 
			
		||||
            filename_subdir = remove_prefix(file, data_temp_dir_path)
 | 
			
		||||
            files_to_copy.append(filename_subdir)
 | 
			
		||||
            files_to_copy.append(safe_relpath(file, temp_dir))
 | 
			
		||||
 | 
			
		||||
    for file in files_to_copy:
 | 
			
		||||
        print('GZIP: Copying file from: ' + data_temp_dir_path + file + ' to: ' + data_dir_path + file)
 | 
			
		||||
        os.makedirs(os.path.dirname(data_dir_path + file), exist_ok=True)
 | 
			
		||||
        shutil.copy(data_temp_dir_path + file, data_dir_path + file)
 | 
			
		||||
    # Compress and move files
 | 
			
		||||
    
 | 
			
		||||
        full_dst = os.path.join(dst_dir, file)
 | 
			
		||||
        os.makedirs(os.path.dirname(full_dst), exist_ok=True)
 | 
			
		||||
        shutil.copy(os.path.join(temp_dir, file), full_dst)
 | 
			
		||||
 | 
			
		||||
    was_error = False
 | 
			
		||||
    try:
 | 
			
		||||
        for source_file_path in files_to_gzip:
 | 
			
		||||
            print('GZIP: compressing... ' + source_file_path)
 | 
			
		||||
            filename_subdir = remove_prefix(source_file_path, data_temp_dir_path)
 | 
			
		||||
            target_file_path = data_dir_path + filename_subdir
 | 
			
		||||
            os.makedirs(os.path.dirname(target_file_path), exist_ok=True)
 | 
			
		||||
            print('GZIP: Compressed... ' + target_file_path)
 | 
			
		||||
            gzip_file(source_file_path, target_file_path + ".gz")
 | 
			
		||||
        for src in files_to_gzip:
 | 
			
		||||
            rel_path = safe_relpath(src, temp_dir)
 | 
			
		||||
            dst_path = os.path.join(dst_dir, rel_path + '.gz')
 | 
			
		||||
            os.makedirs(os.path.dirname(dst_path), exist_ok=True)
 | 
			
		||||
            print('GZIP: compressing... ' + rel_path)
 | 
			
		||||
            gzip_file(src, dst_path)
 | 
			
		||||
    except IOError as e:
 | 
			
		||||
        was_error = True
 | 
			
		||||
        print('GZIP: Failed to compress file: ' + source_file_path)
 | 
			
		||||
        # print( 'GZIP: EXCEPTION... {}'.format( e ) )
 | 
			
		||||
    if was_error:
 | 
			
		||||
        print('GZIP: Failure/Incomplete.\n')
 | 
			
		||||
    else:
 | 
			
		||||
        print('GZIP: Compressed correctly.\n')
 | 
			
		||||
        shutil.rmtree(data_temp_dir_path)
 | 
			
		||||
        print('GZIP: Fehler beim Komprimieren:', e)
 | 
			
		||||
 | 
			
		||||
    if was_error:
 | 
			
		||||
        print('⚠️  GZIP: Nicht alle Dateien konnten verarbeitet werden.\n')
 | 
			
		||||
    else:
 | 
			
		||||
        print('✅ GZIP: Komprimierung abgeschlossen.\n')
 | 
			
		||||
        shutil.rmtree(temp_dir)
 | 
			
		||||
 | 
			
		||||
    return
 | 
			
		||||
 
 | 
			
		||||
def gzip_binffiles(source, target, env):
 | 
			
		||||
    littlefsbin = target[0].get_abspath()
 | 
			
		||||
    targetbin = os.path.join(os.path.dirname(littlefsbin), 'filesystem.fs')
 | 
			
		||||
    shutil.copyfile(littlefsbin, targetbin)
 | 
			
		||||
    gzip_file(targetbin, os.path.join(str(targetbin) + '.gz'))
 | 
			
		||||
    os.remove(targetbin)
 | 
			
		||||
    return
 | 
			
		||||
    tmpbin = os.path.join(os.path.dirname(littlefsbin), 'filesystem.fs')
 | 
			
		||||
    shutil.copyfile(littlefsbin, tmpbin)
 | 
			
		||||
    gzip_file(tmpbin, tmpbin + '.gz')
 | 
			
		||||
    os.remove(tmpbin)
 | 
			
		||||
 | 
			
		||||
# IMPORTANT, this needs to be added to call the routine
 | 
			
		||||
# Hooks setzen
 | 
			
		||||
env.AddPreAction('$BUILD_DIR/littlefs.bin', gzip_webfiles)
 | 
			
		||||
env.AddPostAction('$BUILD_DIR/littlefs.bin', gzip_binffiles)
 | 
			
		||||
 
 | 
			
		||||
@@ -349,6 +349,33 @@
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="form-group row">
 | 
			
		||||
            <label for="washdistance" class="control-label col-4">Waschmodus Distanz</label>
 | 
			
		||||
            <div class="col-8">
 | 
			
		||||
              <div class="input-group">
 | 
			
		||||
                <input id="washdistance" type="text"
 | 
			
		||||
                  class="set-wsevent data-washdistance form-control" required="required">
 | 
			
		||||
                <div class="input-group-append">
 | 
			
		||||
                  <span class="input-group-text">m</span>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="form-group row">
 | 
			
		||||
            <label for="washinterval" class="control-label col-4">Waschmodus Interval</label>
 | 
			
		||||
            <div class="col-8">
 | 
			
		||||
              <div class="input-group">
 | 
			
		||||
                <input id="washinterval" type="text"
 | 
			
		||||
                  class="set-wsevent data-washinterval form-control" required="required">
 | 
			
		||||
                <div class="input-group-append">
 | 
			
		||||
                  <span class="input-group-text">m</span>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
        </p>
 | 
			
		||||
        <!-- Div Group Lube Settings-->
 | 
			
		||||
        <!-- Div Group Oiltank Settings -->
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								Software/include/button_actions.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Software/include/button_actions.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
// === button_actions.h ===
 | 
			
		||||
#ifndef _BUTTON_ACTIONS_H_
 | 
			
		||||
#define _BUTTON_ACTIONS_H_
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include "buttoncontrol.h"
 | 
			
		||||
 | 
			
		||||
// Deklarationen der Button-Callbacks
 | 
			
		||||
void ButtonAction_ToggleMode();
 | 
			
		||||
void ButtonAction_StartPurge();
 | 
			
		||||
void ButtonAction_ToggleWiFi();
 | 
			
		||||
void ButtonAction_WashMode();
 | 
			
		||||
 | 
			
		||||
// Bereitstellung der Aktionsliste
 | 
			
		||||
extern const ButtonActionEntry buttonActions[];
 | 
			
		||||
extern const uint8_t buttonActionCount;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										30
									
								
								Software/include/buttoncontrol.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Software/include/buttoncontrol.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
// === buttoncontrol.h ===
 | 
			
		||||
#ifndef _BUTTONCONTROL_H_
 | 
			
		||||
#define _BUTTONCONTROL_H_
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
 | 
			
		||||
// Aktionen, die vom Button ausgelöst werden können
 | 
			
		||||
enum ButtonAction
 | 
			
		||||
{
 | 
			
		||||
    BTN_NONE,
 | 
			
		||||
    BTN_CUSTOM
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Callback-Funktionstyp
 | 
			
		||||
typedef void (*ButtonCallback)();
 | 
			
		||||
 | 
			
		||||
struct ButtonActionEntry
 | 
			
		||||
{
 | 
			
		||||
    uint32_t holdTimeMs;
 | 
			
		||||
    uint32_t ledColor;
 | 
			
		||||
    ButtonCallback callback;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Initialisierung des Buttonmoduls
 | 
			
		||||
void ButtonControl_Init(uint8_t pin, const ButtonActionEntry *actions, uint8_t actionCount);
 | 
			
		||||
 | 
			
		||||
// Muss regelmäßig in loop() aufgerufen werden
 | 
			
		||||
void ButtonControl_Update();
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -41,9 +41,10 @@
 | 
			
		||||
#elif PCB_REV == 4
 | 
			
		||||
    #define GPIO_BUTTON D4
 | 
			
		||||
    #define GPIO_LED D3
 | 
			
		||||
    #define GPIO_TRIGGER D6
 | 
			
		||||
    #define GPIO_TRIGGER D8
 | 
			
		||||
    #define GPIO_PUMP D0
 | 
			
		||||
    #define GPIO_CS_CAN D8
 | 
			
		||||
    #define GPIO_CE_KLINE D8
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef HOST_NAME
 | 
			
		||||
@@ -72,6 +73,7 @@ typedef enum eSystem_Status
 | 
			
		||||
  sysStat_Startup,
 | 
			
		||||
  sysStat_Normal,
 | 
			
		||||
  sysStat_Rain,
 | 
			
		||||
  sysStat_Wash,
 | 
			
		||||
  sysStat_Purge,
 | 
			
		||||
  sysStat_Error,
 | 
			
		||||
  sysStat_Shutdown
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
#include "dtc.h"
 | 
			
		||||
#include "common.h"
 | 
			
		||||
 | 
			
		||||
#define EEPROM_STRUCTURE_REVISION 3 // Increment this version when changing EEPROM structures
 | 
			
		||||
#define EEPROM_STRUCTURE_REVISION 4 // Increment this version when changing EEPROM structures
 | 
			
		||||
 | 
			
		||||
#if PCB_REV == 1 || PCB_REV == 2 || PCB_REV == 3
 | 
			
		||||
#define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC64
 | 
			
		||||
@@ -69,6 +69,8 @@ typedef struct
 | 
			
		||||
  uint32_t RimDiameter_Inch;
 | 
			
		||||
  uint32_t DistancePerRevolution_mm;
 | 
			
		||||
  uint16_t BleedingPulses;
 | 
			
		||||
  uint16_t WashMode_Distance;
 | 
			
		||||
  uint16_t WashMode_Interval;
 | 
			
		||||
  SpeedSource_t SpeedSource;
 | 
			
		||||
  GPSBaudRate_t GPSBaudRate;
 | 
			
		||||
  CANSource_t CANSource;
 | 
			
		||||
@@ -85,7 +87,7 @@ typedef struct
 | 
			
		||||
 | 
			
		||||
// Default configuration settings
 | 
			
		||||
const LubeConfig_t LubeConfig_defaults = {
 | 
			
		||||
    0, 8000, 4000, 320, DEFAULT_PUMP_DOSE, 30, 1, 150, 70, 18, 2000, 25, SOURCE_IMPULSE,
 | 
			
		||||
    0, 8000, 4000, 320, DEFAULT_PUMP_DOSE, 30, 1, 150, 70, 18, 2000, 25, 500, 10, SOURCE_IMPULSE,
 | 
			
		||||
    BAUD_115200,
 | 
			
		||||
    KTM_890_ADV_R_2021,
 | 
			
		||||
    false,
 | 
			
		||||
 
 | 
			
		||||
@@ -16,19 +16,6 @@
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include "webui.h"
 | 
			
		||||
const char PROGMEM helpCmd[] = "sysinfo     - System Info\n"
 | 
			
		||||
                               "netinfo     - WiFi Info\n"
 | 
			
		||||
                               "formatPDS   - Format Persistence EEPROM Data\n"
 | 
			
		||||
                               "formatCFG   - Format Configuration EEPROM Data\n"
 | 
			
		||||
                               "checkEE     - Check EEPROM with checksum\n"
 | 
			
		||||
                               "dumpEE1k    - dump the first 1kb of EEPROM to Serial\n"
 | 
			
		||||
                               "dumpEE      - dump the whole EPPROM to Serial\n"
 | 
			
		||||
                               "resetPageEE - Reset the PersistenceData Page\n"
 | 
			
		||||
                               "dumpCFG     - print Config struct\n"
 | 
			
		||||
                               "dumpPDS     - print PersistanceStruct\n"
 | 
			
		||||
                               "saveEE      - save EE-Data\n"
 | 
			
		||||
                               "showdtc     - Show all DTCs\n"
 | 
			
		||||
                               "dumpGlobals - print globals\n";
 | 
			
		||||
 | 
			
		||||
typedef enum DebugStatus_e
 | 
			
		||||
{
 | 
			
		||||
@@ -49,6 +36,13 @@ const char sDebugPorts[dbg_cntElements][7] = {
 | 
			
		||||
 | 
			
		||||
extern DebugStatus_t DebuggerStatus[dbg_cntElements];
 | 
			
		||||
 | 
			
		||||
enum LogLevel
 | 
			
		||||
{
 | 
			
		||||
    LOG_INFO,
 | 
			
		||||
    LOG_WARN,
 | 
			
		||||
    LOG_ERROR
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void initDebugger();
 | 
			
		||||
void pushCANDebug(uint32_t id, uint8_t dlc, uint8_t *data);
 | 
			
		||||
void Debug_pushMessage(const char *format, ...);
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ typedef struct Globals_s
 | 
			
		||||
  tSystem_Status resumeStatus = sysStat_Startup;      /**< Status to resume after rain mode */
 | 
			
		||||
  char systemStatustxt[16] = "";                      /**< Text representation of system status */
 | 
			
		||||
  uint16_t purgePulses = 0;                           /**< Number of purge pulses */
 | 
			
		||||
  EERequest_t requestEEAction = EE_IDLE;;              /**< EEPROM-related request */
 | 
			
		||||
  EERequest_t requestEEAction = EE_IDLE;              /**< EEPROM-related request */
 | 
			
		||||
  char DeviceName[33];                                /**< Device name */
 | 
			
		||||
  char FlashVersion[10];                              /**< Flash version */
 | 
			
		||||
  uint16_t eePersistanceAdress;                       /**< EEPROM persistence address */
 | 
			
		||||
 
 | 
			
		||||
@@ -37,9 +37,10 @@
 | 
			
		||||
#define LED_STARTUP_TANKWARN    COLOR_AMBER
 | 
			
		||||
#define LED_NORMAL_COLOR        COLOR_GREEN
 | 
			
		||||
#define LED_RAIN_COLOR          COLOR_BLUE
 | 
			
		||||
#define LED_WIFI_BLINK          COLOR_YELLOW
 | 
			
		||||
#define LED_WASH_COLOR          COLOR_JADE
 | 
			
		||||
#define LED_WIFI_COLOR          COLOR_YELLOW
 | 
			
		||||
#define LED_PURGE_COLOR         COLOR_MAGENTA
 | 
			
		||||
#define LED_ERROR_BLINK         COLOR_RED
 | 
			
		||||
#define LED_SHUTDOWN_BLINK      COLOR_CYAN
 | 
			
		||||
#define LED_ERROR_COLOR         COLOR_RED
 | 
			
		||||
#define LED_SHUTDOWN_COLOR      COLOR_CYAN
 | 
			
		||||
 | 
			
		||||
#endif /* _LED_COLORS_H_ */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								Software/include/ledcontrol.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Software/include/ledcontrol.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
// === ledcontrol.h ===
 | 
			
		||||
#ifndef _LEDCONTROL_H_
 | 
			
		||||
#define _LEDCONTROL_H_
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include "led_colors.h"
 | 
			
		||||
 | 
			
		||||
// LED-Muster
 | 
			
		||||
enum LedPattern
 | 
			
		||||
{
 | 
			
		||||
    LED_PATTERN_ON,
 | 
			
		||||
    LED_PATTERN_FLASH,
 | 
			
		||||
    LED_PATTERN_FLASH_FAST,
 | 
			
		||||
    LED_PATTERN_BLINK,
 | 
			
		||||
    LED_PATTERN_BLINK_FAST,
 | 
			
		||||
    LED_PATTERN_BREATH,
 | 
			
		||||
    LED_PATTERN_BREATH_REVERSE
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Initialisiert die LED-Steuerung
 | 
			
		||||
void LEDControl_Init(uint8_t pin);
 | 
			
		||||
 | 
			
		||||
// Setzt den Basiszustand (Farbe + Pattern), wird verwendet wenn kein Override aktiv ist
 | 
			
		||||
void LEDControl_SetBasic(uint32_t color, LedPattern pattern);
 | 
			
		||||
 | 
			
		||||
// Setzt ein Override mit Timeout (0 = bis explizit gecleart)
 | 
			
		||||
void LEDControl_SetOverride(uint32_t color, LedPattern pattern, uint32_t durationMs);
 | 
			
		||||
 | 
			
		||||
// Hebt das Override wieder auf
 | 
			
		||||
void LEDControl_ClearOverride();
 | 
			
		||||
 | 
			
		||||
// Muss regelmäßig aus loop() aufgerufen werden
 | 
			
		||||
void LEDControl_Update();
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -3,10 +3,10 @@
 | 
			
		||||
 *
 | 
			
		||||
 * @brief Header file for converting structs to JSON objects.
 | 
			
		||||
 *
 | 
			
		||||
 * @note This file is auto-generated by a script on 2024-01-30 20:29:34.
 | 
			
		||||
 * @note This file is auto-generated by a script on 2025-06-15 11:37:51.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Marcel Peterkau
 | 
			
		||||
 * @date   30.01.2024
 | 
			
		||||
 * @date   15.06.2025
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef _STRUCT2JSON_H_
 | 
			
		||||
@@ -23,4 +23,4 @@ void generateJsonObject_PersistenceData(JsonObject data);
 | 
			
		||||
 | 
			
		||||
#endif /* _STRUCT2JSON_H_ */
 | 
			
		||||
 | 
			
		||||
// CODEGENERATOR_CHECKSUM: 59f35aadffd0bbef253210ea2fbaaf9a515553a2e3cc9bf4cfa2819b63c969ce
 | 
			
		||||
// CODEGENERATOR_CHECKSUM: 4702cb49ea55617cbb34715164810bb58d3c3f46fb1653b6f47bd4fd9cb0031e
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
[platformio]
 | 
			
		||||
extra_configs =
 | 
			
		||||
  wifi_credentials.ini
 | 
			
		||||
default_envs = pcb_rev_1-3_serial, pcb_rev_1-3_ota, pcb_rev_1-2_serial, pcb_rev_1-2_ota
 | 
			
		||||
default_envs = pcb_rev_1-4_serial
 | 
			
		||||
 | 
			
		||||
[env]
 | 
			
		||||
platform = espressif8266
 | 
			
		||||
@@ -42,15 +42,36 @@ monitor_speed = 115200
 | 
			
		||||
 | 
			
		||||
lib_ldf_mode = deep
 | 
			
		||||
lib_deps = 
 | 
			
		||||
    olikraus/U8g2 @ ^2.35.9
 | 
			
		||||
    adafruit/Adafruit NeoPixel @ ^1.12.0
 | 
			
		||||
    olikraus/U8g2 @ ^2.36.5
 | 
			
		||||
    adafruit/Adafruit NeoPixel @ ^1.15.1
 | 
			
		||||
    sstaub/Ticker @ ^4.4.0
 | 
			
		||||
    robtillaart/I2C_EEPROM @ ^1.8.2
 | 
			
		||||
    esphome/ESPAsyncWebServer-esphome @ 3.1.0
 | 
			
		||||
    bblanchon/ArduinoJson @ ^7.0.1
 | 
			
		||||
    coryjfowler/mcp_can @ ^1.5.0
 | 
			
		||||
    mikalhart/TinyGPSPlus @ ^1.0.3
 | 
			
		||||
    robtillaart/I2C_EEPROM @ ^1.9.2
 | 
			
		||||
    esphome/ESPAsyncWebServer-esphome @ 3.3.0
 | 
			
		||||
    bblanchon/ArduinoJson @ ^7.4.1
 | 
			
		||||
    coryjfowler/mcp_can @ ^1.5.1
 | 
			
		||||
    mikalhart/TinyGPSPlus @ ^1.1.0
 | 
			
		||||
 | 
			
		||||
[env:pcb_rev_1-4_serial]
 | 
			
		||||
extends = env
 | 
			
		||||
custom_pcb_revision = 4
 | 
			
		||||
upload_protocol = esptool
 | 
			
		||||
build_flags =
 | 
			
		||||
  ${env.build_flags}
 | 
			
		||||
  -DPCB_REV=${this.custom_pcb_revision}
 | 
			
		||||
board_build.ldscript = eagle.flash.4m1m.ld
 | 
			
		||||
 | 
			
		||||
[env:pcb_rev_1-4_ota]
 | 
			
		||||
extends = env
 | 
			
		||||
custom_pcb_revision = 4
 | 
			
		||||
upload_protocol = espota
 | 
			
		||||
upload_port = 10.0.1.14
 | 
			
		||||
upload_flags =
 | 
			
		||||
  --port=8266
 | 
			
		||||
  --auth=${wifi_cred.admin_password}
 | 
			
		||||
build_flags =
 | 
			
		||||
  ${env.build_flags}
 | 
			
		||||
  -DPCB_REV=${this.custom_pcb_revision}
 | 
			
		||||
board_build.ldscript = eagle.flash.4m1m.ld
 | 
			
		||||
 | 
			
		||||
[env:pcb_rev_1-3_serial]
 | 
			
		||||
extends = env
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								Software/src/button_actions.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								Software/src/button_actions.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
// === button_actions.cpp ===
 | 
			
		||||
#include "button_actions.h"
 | 
			
		||||
#include "globals.h"
 | 
			
		||||
#include "debugger.h"
 | 
			
		||||
#include "led_colors.h"
 | 
			
		||||
 | 
			
		||||
void ButtonAction_ToggleMode()
 | 
			
		||||
{
 | 
			
		||||
    if (globals.systemStatus == sysStat_Normal)
 | 
			
		||||
    {
 | 
			
		||||
        globals.systemStatus = sysStat_Rain;
 | 
			
		||||
    }
 | 
			
		||||
    else if (globals.systemStatus == sysStat_Rain)
 | 
			
		||||
    {
 | 
			
		||||
        globals.systemStatus = sysStat_Normal;
 | 
			
		||||
    }
 | 
			
		||||
    Debug_pushMessage("Toggling Mode\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ButtonAction_StartPurge()
 | 
			
		||||
{
 | 
			
		||||
    globals.systemStatus = sysStat_Purge;
 | 
			
		||||
    Debug_pushMessage("Starting Purge\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ButtonAction_ToggleWiFi()
 | 
			
		||||
{
 | 
			
		||||
    extern void toggleWiFiAP(bool shutdown = false);
 | 
			
		||||
    toggleWiFiAP();
 | 
			
		||||
 | 
			
		||||
    Debug_pushMessage("Toggling WiFi\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ButtonAction_WashMode()
 | 
			
		||||
{
 | 
			
		||||
    globals.systemStatus = sysStat_Wash;
 | 
			
		||||
    Debug_pushMessage("Setting WashMode\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Liste der Aktionen, sortiert nach Mindest-Haltezeit (ms)
 | 
			
		||||
const ButtonActionEntry buttonActions[] = {
 | 
			
		||||
    {500, LED_RAIN_COLOR, ButtonAction_ToggleMode},
 | 
			
		||||
    {3500, LED_PURGE_COLOR, ButtonAction_StartPurge},
 | 
			
		||||
    {6500, LED_WIFI_COLOR, ButtonAction_ToggleWiFi},
 | 
			
		||||
    {9500, LED_WASH_COLOR, ButtonAction_WashMode}};
 | 
			
		||||
 | 
			
		||||
const uint8_t buttonActionCount = sizeof(buttonActions) / sizeof(ButtonActionEntry);
 | 
			
		||||
							
								
								
									
										64
									
								
								Software/src/buttoncontrol.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								Software/src/buttoncontrol.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
 | 
			
		||||
// === buttoncontrol.cpp ===
 | 
			
		||||
#include "buttoncontrol.h"
 | 
			
		||||
#include "ledcontrol.h"
 | 
			
		||||
 | 
			
		||||
static uint8_t btnPin;
 | 
			
		||||
static uint32_t pressStart = 0;
 | 
			
		||||
static bool pressed = false;
 | 
			
		||||
static const ButtonActionEntry *btnActions = nullptr;
 | 
			
		||||
static uint8_t btnActionCount = 0;
 | 
			
		||||
static uint8_t currentActionIndex = 0xFF;
 | 
			
		||||
static uint32_t lastColor = 0;
 | 
			
		||||
 | 
			
		||||
void ButtonControl_Init(uint8_t pin, const ButtonActionEntry *actions, uint8_t actionCount)
 | 
			
		||||
{
 | 
			
		||||
    btnPin = pin;
 | 
			
		||||
    pinMode(btnPin, INPUT_PULLUP);
 | 
			
		||||
    btnActions = actions;
 | 
			
		||||
    btnActionCount = actionCount;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ButtonControl_Update()
 | 
			
		||||
{
 | 
			
		||||
    bool currentState = digitalRead(btnPin) == LOW;
 | 
			
		||||
    uint32_t now = millis();
 | 
			
		||||
 | 
			
		||||
    if (currentState && !pressed)
 | 
			
		||||
    {
 | 
			
		||||
        pressStart = now;
 | 
			
		||||
        pressed = true;
 | 
			
		||||
        currentActionIndex = 0xFF;
 | 
			
		||||
        lastColor = 0;
 | 
			
		||||
    }
 | 
			
		||||
    else if (currentState && pressed)
 | 
			
		||||
    {
 | 
			
		||||
        uint32_t duration = now - pressStart;
 | 
			
		||||
        // Finde passende Aktion basierend auf Zeit
 | 
			
		||||
        for (uint8_t i = 0; i < btnActionCount; i++)
 | 
			
		||||
        {
 | 
			
		||||
            if (duration >= btnActions[i].holdTimeMs)
 | 
			
		||||
            {
 | 
			
		||||
                if (currentActionIndex != i)
 | 
			
		||||
                {
 | 
			
		||||
                    currentActionIndex = i;
 | 
			
		||||
                    lastColor = btnActions[i].ledColor;
 | 
			
		||||
                    // Farbe + Pattern setzen
 | 
			
		||||
                    LEDControl_SetOverride(lastColor, LED_PATTERN_BREATH, 0); // Kein Timeout, wird bei Release beendet
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else if (!currentState && pressed)
 | 
			
		||||
    {
 | 
			
		||||
        pressed = false;
 | 
			
		||||
        if (currentActionIndex != 0xFF && currentActionIndex < btnActionCount)
 | 
			
		||||
        {
 | 
			
		||||
            if (btnActions[currentActionIndex].callback)
 | 
			
		||||
            {
 | 
			
		||||
                btnActions[currentActionIndex].callback();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        LEDControl_ClearOverride(); // Override-Modus zurücksetzen
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -11,7 +11,10 @@
 | 
			
		||||
 * @date 09.04.2024
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "debugger.h"
 | 
			
		||||
 #include "debugger.h"
 | 
			
		||||
 #include <map>
 | 
			
		||||
 #include <functional>
 | 
			
		||||
 #include <vector>
 | 
			
		||||
 | 
			
		||||
DebugStatus_t DebuggerStatus[dbg_cntElements];
 | 
			
		||||
 | 
			
		||||
@@ -27,6 +30,7 @@ void Debug_ShowDTCs();
 | 
			
		||||
void Debug_dumpGlobals();
 | 
			
		||||
void Debug_printHelp();
 | 
			
		||||
void Debug_Purge();
 | 
			
		||||
void Debug_refillTank();
 | 
			
		||||
const char *uint32_to_binary_string(uint32_t num);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -94,12 +98,11 @@ void Debug_Process()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check for input buffer overflow
 | 
			
		||||
        if (inputCnt > sizeof(inputBuffer))
 | 
			
		||||
        {
 | 
			
		||||
        if (inputCnt >= sizeof(inputBuffer) - 1) {
 | 
			
		||||
            inputBuffer[sizeof(inputBuffer) - 1] = '\0';
 | 
			
		||||
            inputCnt = 0;
 | 
			
		||||
            inputBuffer[sizeof(inputBuffer) - 1] = 0; // terminate the String
 | 
			
		||||
            InputProcessed = CMD_OVERFLOW;
 | 
			
		||||
        }
 | 
			
		||||
        }        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Process the command based on the detected state of input processing
 | 
			
		||||
@@ -147,6 +150,29 @@ void SetDebugportStatus(DebugPorts_t port, DebugStatus_t status)
 | 
			
		||||
        Debug_pushMessage("Enabled DebugPort %s\n", sDebugPorts[port]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void Debug_log(LogLevel level, const char *format, ...)
 | 
			
		||||
{
 | 
			
		||||
    if ((DebuggerStatus[dbg_Serial] == enabled) || (DebuggerStatus[dbg_Webui] == enabled))
 | 
			
		||||
    {
 | 
			
		||||
        char buff[128];
 | 
			
		||||
        va_list arg;
 | 
			
		||||
        va_start(arg, format);
 | 
			
		||||
        vsnprintf(buff, sizeof(buff), format, arg);
 | 
			
		||||
        va_end(arg);
 | 
			
		||||
 | 
			
		||||
        if (DebuggerStatus[dbg_Serial] == enabled)
 | 
			
		||||
        {
 | 
			
		||||
            Serial.print(buff);
 | 
			
		||||
        }
 | 
			
		||||
        if (DebuggerStatus[dbg_Webui] == enabled)
 | 
			
		||||
        {
 | 
			
		||||
            Websocket_PushLiveDebug(String(buff));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Pushes a formatted debug message to the enabled debug ports (Serial or WebUI).
 | 
			
		||||
 *
 | 
			
		||||
@@ -218,68 +244,100 @@ void pushCANDebug(uint32_t id, uint8_t dlc, uint8_t *data)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Processes a debug command and performs corresponding actions.
 | 
			
		||||
 *
 | 
			
		||||
 * @param command The debug command to be processed.
 | 
			
		||||
 */
 | 
			
		||||
void processCmdDebug(String command)
 | 
			
		||||
// === splitArgs Helper ===
 | 
			
		||||
std::vector<String> splitArgs(const String &input)
 | 
			
		||||
{
 | 
			
		||||
    // Check the received command and execute corresponding actions
 | 
			
		||||
    if (command == "help")
 | 
			
		||||
        Debug_printHelp();
 | 
			
		||||
    else if (command == "sysinfo")
 | 
			
		||||
        Debug_printSystemInfo();
 | 
			
		||||
    else if (command == "netinfo")
 | 
			
		||||
        Debug_printWifiInfo();
 | 
			
		||||
    else if (command == "formatCFG")
 | 
			
		||||
        Debug_formatCFG();
 | 
			
		||||
    else if (command == "formatPDS")
 | 
			
		||||
        Debug_formatPersistence();
 | 
			
		||||
    else if (command == "checkEE")
 | 
			
		||||
        Debug_CheckEEPOM(false);
 | 
			
		||||
    else if (command == "checkEEfix")
 | 
			
		||||
        Debug_CheckEEPOM(true);
 | 
			
		||||
    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 == "dumpGlobals")
 | 
			
		||||
        Debug_dumpGlobals();
 | 
			
		||||
    else if (command == "sdbg")
 | 
			
		||||
        SetDebugportStatus(dbg_Serial, enabled);
 | 
			
		||||
    else if (command == "dtc_show")
 | 
			
		||||
        Debug_ShowDTCs();
 | 
			
		||||
    else if (command == "dtc_clear")
 | 
			
		||||
        ClearAllDTC();
 | 
			
		||||
    else if (command == "dtc_crit")
 | 
			
		||||
        MaintainDTC(DTC_FAKE_DTC_CRIT, true, millis());
 | 
			
		||||
    else if (command == "dtc_warn")
 | 
			
		||||
        MaintainDTC(DTC_FAKE_DTC_WARN, true, millis());
 | 
			
		||||
    else if (command == "dtc_info")
 | 
			
		||||
        MaintainDTC(DTC_FAKE_DTC_INFO, true, millis());
 | 
			
		||||
    else if (command == "notify_error")
 | 
			
		||||
        Websocket_PushNotification("Debug Error Notification", error);
 | 
			
		||||
    else if (command == "notify_warning")
 | 
			
		||||
        Websocket_PushNotification("Debug Warning Notification", warning);
 | 
			
		||||
    else if (command == "notify_success")
 | 
			
		||||
        Websocket_PushNotification("Debug Success Notification", success);
 | 
			
		||||
    else if (command == "notify_info")
 | 
			
		||||
        Websocket_PushNotification("Debug Info Notification", info);
 | 
			
		||||
    else if (command == "purge")
 | 
			
		||||
        Debug_Purge();
 | 
			
		||||
    else if (command == "toggle_wifi")
 | 
			
		||||
        globals.toggle_wifi = true;
 | 
			
		||||
    std::vector<String> tokens;
 | 
			
		||||
    int start = 0, end = 0;
 | 
			
		||||
    while ((end = input.indexOf(' ', start)) != -1)
 | 
			
		||||
    {
 | 
			
		||||
        tokens.push_back(input.substring(start, end));
 | 
			
		||||
        start = end + 1;
 | 
			
		||||
    }
 | 
			
		||||
    if (start < input.length())
 | 
			
		||||
        tokens.push_back(input.substring(start));
 | 
			
		||||
    return tokens;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === getArg helper ===
 | 
			
		||||
String getArg(const std::vector<String> &args, size_t index, const String &defaultVal = "")
 | 
			
		||||
{
 | 
			
		||||
    if (index < args.size())
 | 
			
		||||
        return args[index];
 | 
			
		||||
    return defaultVal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === Command Handler Map ===
 | 
			
		||||
typedef std::function<void(const String &args)> DebugCmdHandler;
 | 
			
		||||
 | 
			
		||||
static const std::map<String, DebugCmdHandler> &getCmdMap()
 | 
			
		||||
{
 | 
			
		||||
    static const std::map<String, DebugCmdHandler> cmdMap = {
 | 
			
		||||
        {"help", [](const String &) {
 | 
			
		||||
             Debug_log(LOG_INFO, "Available commands:\n");
 | 
			
		||||
             for (const auto &entry : getCmdMap())
 | 
			
		||||
                 Debug_log(LOG_INFO, " - %s\n", entry.first.c_str());
 | 
			
		||||
         }},
 | 
			
		||||
        {"sysinfo", [](const String &) { Debug_printSystemInfo(); }},
 | 
			
		||||
        {"netinfo", [](const String &) { Debug_printWifiInfo(); }},
 | 
			
		||||
        {"formatCFG", [](const String &) { Debug_formatCFG(); }},
 | 
			
		||||
        {"formatPDS", [](const String &) { Debug_formatPersistence(); }},
 | 
			
		||||
        {"checkEE", [](const String &) { Debug_CheckEEPOM(false); }},
 | 
			
		||||
        {"checkEEfix", [](const String &) { Debug_CheckEEPOM(true); }},
 | 
			
		||||
        {"dumpEE1k", [](const String &) { dumpEEPROM(0, 1024); }},
 | 
			
		||||
        {"dumpEE", [](const String &args) {
 | 
			
		||||
             int start = 0, len = EEPROM_SIZE_BYTES;
 | 
			
		||||
             auto tokens = splitArgs(args);
 | 
			
		||||
             if (tokens.size() >= 2)
 | 
			
		||||
             {
 | 
			
		||||
                 start = tokens[0].toInt();
 | 
			
		||||
                 len = tokens[1].toInt();
 | 
			
		||||
             }
 | 
			
		||||
             dumpEEPROM(start, len);
 | 
			
		||||
         }},
 | 
			
		||||
        {"resetPageEE", [](const String &) { MovePersistencePage_EEPROM(true); }},
 | 
			
		||||
        {"dumpCFG", [](const String &) { Debug_dumpConfig(); }},
 | 
			
		||||
        {"dumpPDS", [](const String &) { Debug_dumpPersistance(); }},
 | 
			
		||||
        {"saveEE", [](const String &) { globals.requestEEAction = EE_ALL_SAVE; }},
 | 
			
		||||
        {"dumpGlobals", [](const String &) { Debug_dumpGlobals(); }},
 | 
			
		||||
        {"sdbg", [](const String &) { SetDebugportStatus(dbg_Serial, enabled); }},
 | 
			
		||||
        {"dtc_show", [](const String &) { Debug_ShowDTCs(); }},
 | 
			
		||||
        {"dtc_clear", [](const String &) { ClearAllDTC(); }},
 | 
			
		||||
        {"dtc_crit", [](const String &) { MaintainDTC(DTC_FAKE_DTC_CRIT, true, millis()); }},
 | 
			
		||||
        {"dtc_warn", [](const String &) { MaintainDTC(DTC_FAKE_DTC_WARN, true, millis()); }},
 | 
			
		||||
        {"dtc_info", [](const String &) { MaintainDTC(DTC_FAKE_DTC_INFO, true, millis()); }},
 | 
			
		||||
        {"notify_error", [](const String &) { Websocket_PushNotification("Debug Error Notification", error); }},
 | 
			
		||||
        {"notify_warning", [](const String &) { Websocket_PushNotification("Debug Warning Notification", warning); }},
 | 
			
		||||
        {"notify_success", [](const String &) { Websocket_PushNotification("Debug Success Notification", success); }},
 | 
			
		||||
        {"notify_info", [](const String &) { Websocket_PushNotification("Debug Info Notification", info); }},
 | 
			
		||||
        {"purge", [](const String &) { Debug_Purge(); }},
 | 
			
		||||
        {"toggle_wifi", [](const String &) { globals.toggle_wifi = true; }},
 | 
			
		||||
        {"dtc_add", [](const String &args) {
 | 
			
		||||
             auto tokens = splitArgs(args);
 | 
			
		||||
             if (!tokens.empty())
 | 
			
		||||
             {
 | 
			
		||||
                 int code = tokens[0].toInt();
 | 
			
		||||
                 MaintainDTC((DTCNum_t)code, true, millis());
 | 
			
		||||
             }
 | 
			
		||||
         }},
 | 
			
		||||
         {"tank_refill", [](const String &) { Debug_refillTank(); }},
 | 
			
		||||
    };
 | 
			
		||||
    return cmdMap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void processCmdDebug(String input)
 | 
			
		||||
{
 | 
			
		||||
    input.trim();
 | 
			
		||||
    int splitIndex = input.indexOf(' ');
 | 
			
		||||
    String command = splitIndex == -1 ? input : input.substring(0, splitIndex);
 | 
			
		||||
    String args = splitIndex == -1 ? "" : input.substring(splitIndex + 1);
 | 
			
		||||
 | 
			
		||||
    auto &cmdMap = getCmdMap();
 | 
			
		||||
    auto it = cmdMap.find(command);
 | 
			
		||||
    if (it != cmdMap.end())
 | 
			
		||||
        it->second(args);
 | 
			
		||||
    else
 | 
			
		||||
        Debug_pushMessage("unknown Command\n");
 | 
			
		||||
        Debug_log(LOG_WARN, "Unknown command: '%s'\n", command.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -473,25 +531,6 @@ void Debug_ShowDTCs()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Displays the help commands for debugging through Serial or WebUI.
 | 
			
		||||
 *        Each command is printed individually in a formatted manner.
 | 
			
		||||
 */
 | 
			
		||||
void Debug_printHelp()
 | 
			
		||||
{
 | 
			
		||||
    char buff[64];
 | 
			
		||||
 | 
			
		||||
    // Iterate through helpCmd and display each command
 | 
			
		||||
    for (unsigned int i = 0; i < sizeof(helpCmd) / 63; i++)
 | 
			
		||||
    {
 | 
			
		||||
        // Copy a portion of helpCmd to buff for display
 | 
			
		||||
        memcpy_P(buff, (helpCmd + (i * 63)), 63);
 | 
			
		||||
        buff[63] = 0;
 | 
			
		||||
 | 
			
		||||
        // Display the help command
 | 
			
		||||
        Debug_pushMessage(buff);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Start purging for 10 pulses.
 | 
			
		||||
@@ -505,6 +544,13 @@ void Debug_Purge()
 | 
			
		||||
    Debug_pushMessage("Purging 10 pulses\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Debug_refillTank()
 | 
			
		||||
{
 | 
			
		||||
    PersistenceData.tankRemain_microL = LubeConfig.tankCapacity_ml * 1000;
 | 
			
		||||
    globals.requestEEAction = EE_PDS_SAVE;
 | 
			
		||||
    Debug_pushMessage("Setting Tank to 100%\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Convert a uint32_t value to a binary string with nibbles separated by a space.
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										131
									
								
								Software/src/ledcontrol.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								Software/src/ledcontrol.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
 | 
			
		||||
// === ledcontrol.cpp ===
 | 
			
		||||
#include "ledcontrol.h"
 | 
			
		||||
#include <Adafruit_NeoPixel.h>
 | 
			
		||||
#include "globals.h"
 | 
			
		||||
 | 
			
		||||
static Adafruit_NeoPixel leds(1, GPIO_LED, NEO_RGB + NEO_KHZ800);
 | 
			
		||||
 | 
			
		||||
static uint32_t basicColor = 0x000000;
 | 
			
		||||
static LedPattern basicPattern = LED_PATTERN_ON;
 | 
			
		||||
 | 
			
		||||
static uint32_t overrideColor = 0;
 | 
			
		||||
static LedPattern overridePattern = LED_PATTERN_ON;
 | 
			
		||||
static uint32_t overrideEndTime = 0;
 | 
			
		||||
static bool overrideActive = false;
 | 
			
		||||
 | 
			
		||||
void LEDControl_Init(uint8_t pin)
 | 
			
		||||
{
 | 
			
		||||
    leds.begin();
 | 
			
		||||
    leds.setBrightness(LubeConfig.LED_Max_Brightness);
 | 
			
		||||
    leds.setPixelColor(0, 0);
 | 
			
		||||
    leds.show();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LEDControl_SetBasic(uint32_t color, LedPattern pattern)
 | 
			
		||||
{
 | 
			
		||||
    basicColor = color;
 | 
			
		||||
    basicPattern = pattern;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LEDControl_SetOverride(uint32_t color, LedPattern pattern, uint32_t durationMs)
 | 
			
		||||
{
 | 
			
		||||
    overrideColor = color;
 | 
			
		||||
    overridePattern = pattern;
 | 
			
		||||
    overrideEndTime = millis() + durationMs;
 | 
			
		||||
    overrideActive = true;
 | 
			
		||||
    if (durationMs == 0)
 | 
			
		||||
        overrideEndTime = 0xFFFFFFFF; // Kein Timeout
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LEDControl_ClearOverride()
 | 
			
		||||
{
 | 
			
		||||
    overrideActive = false;
 | 
			
		||||
    overrideEndTime = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LEDControl_Update()
 | 
			
		||||
{
 | 
			
		||||
    uint32_t now = millis();
 | 
			
		||||
    uint32_t color = basicColor;
 | 
			
		||||
    LedPattern pattern = basicPattern;
 | 
			
		||||
 | 
			
		||||
    // Check override
 | 
			
		||||
    if (overrideActive)
 | 
			
		||||
    {
 | 
			
		||||
        if (overrideEndTime != 0xFFFFFFFF && now >= overrideEndTime)
 | 
			
		||||
        {
 | 
			
		||||
            LEDControl_ClearOverride();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            color = overrideColor;
 | 
			
		||||
            pattern = overridePattern;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint8_t brightness = LubeConfig.LED_Min_Brightness;
 | 
			
		||||
    bool on = true;
 | 
			
		||||
 | 
			
		||||
    switch (pattern)
 | 
			
		||||
    {
 | 
			
		||||
    case LED_PATTERN_ON:
 | 
			
		||||
        brightness = LubeConfig.LED_Max_Brightness;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case LED_PATTERN_FLASH:
 | 
			
		||||
        on = (now % 1000) < 100;
 | 
			
		||||
        brightness = LubeConfig.LED_Max_Brightness;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case LED_PATTERN_FLASH_FAST:
 | 
			
		||||
        on = (now % 500) < 50;
 | 
			
		||||
        brightness = LubeConfig.LED_Max_Brightness;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case LED_PATTERN_BLINK:
 | 
			
		||||
        on = (now % 1000) < 500;
 | 
			
		||||
        brightness = on ? LubeConfig.LED_Max_Brightness : 0;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case LED_PATTERN_BLINK_FAST:
 | 
			
		||||
        on = (now % 400) < 200;
 | 
			
		||||
        brightness = on ? LubeConfig.LED_Max_Brightness : 0;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case LED_PATTERN_BREATH:
 | 
			
		||||
    {
 | 
			
		||||
        uint32_t t = now % 2000;
 | 
			
		||||
        if (t < 600)
 | 
			
		||||
        {
 | 
			
		||||
            // Schnell hochdimmen (600 ms)
 | 
			
		||||
            brightness = map(t, 0, 600, LubeConfig.LED_Min_Brightness, LubeConfig.LED_Max_Brightness);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            // Langsam runterdimmen (1400 ms)
 | 
			
		||||
            brightness = map(t, 600, 2000, LubeConfig.LED_Max_Brightness, LubeConfig.LED_Min_Brightness);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    case LED_PATTERN_BREATH_REVERSE:
 | 
			
		||||
    {
 | 
			
		||||
        uint32_t t = now % 2000;
 | 
			
		||||
        if (t < 1400)
 | 
			
		||||
        {
 | 
			
		||||
            // Langsam hochdimmen (1400 ms)
 | 
			
		||||
            brightness = map(t, 0, 1400, LubeConfig.LED_Min_Brightness, LubeConfig.LED_Max_Brightness);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            // Schnell runterdimmen (600 ms)
 | 
			
		||||
            brightness = map(t, 1400, 2000, LubeConfig.LED_Max_Brightness, LubeConfig.LED_Min_Brightness);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    leds.setBrightness(brightness);
 | 
			
		||||
    leds.setPixelColor(0, on ? color : 0);
 | 
			
		||||
    leds.show();
 | 
			
		||||
}
 | 
			
		||||
@@ -12,6 +12,7 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "lubeapp.h"
 | 
			
		||||
#include "ledcontrol.h"
 | 
			
		||||
 | 
			
		||||
uint32_t lubePulseTimestamp = 0;
 | 
			
		||||
 | 
			
		||||
@@ -27,6 +28,9 @@ uint32_t lubePulseTimestamp = 0;
 | 
			
		||||
 */
 | 
			
		||||
void RunLubeApp(uint32_t add_milimeters)
 | 
			
		||||
{
 | 
			
		||||
    static tSystem_Status lastSystemStatus = sysStat_Startup;
 | 
			
		||||
    static uint16_t washModeDistance = 0;   
 | 
			
		||||
 | 
			
		||||
    // Calculate and update tank percentage
 | 
			
		||||
    globals.TankPercentage = PersistenceData.tankRemain_microL / (LubeConfig.tankCapacity_ml * 10);
 | 
			
		||||
 | 
			
		||||
@@ -49,17 +53,31 @@ void RunLubeApp(uint32_t add_milimeters)
 | 
			
		||||
    switch (globals.systemStatus)
 | 
			
		||||
    {
 | 
			
		||||
    case sysStat_Startup:
 | 
			
		||||
        strcpy_P(globals.systemStatustxt, PSTR("Startup"));
 | 
			
		||||
 | 
			
		||||
        if (lastSystemStatus != globals.systemStatus)
 | 
			
		||||
        {
 | 
			
		||||
            strcpy_P(globals.systemStatustxt, PSTR("Startup"));
 | 
			
		||||
            LEDControl_SetBasic(LED_STARTUP_NORMAL, LED_PATTERN_BLINK);
 | 
			
		||||
            lastSystemStatus = globals.systemStatus;
 | 
			
		||||
            globals.resumeStatus = sysStat_Startup;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Transition to Normal status after startup delay
 | 
			
		||||
        if (millis() > STARTUP_DELAY)
 | 
			
		||||
        {
 | 
			
		||||
            globals.systemStatus = sysStat_Normal;
 | 
			
		||||
            globals.resumeStatus = sysStat_Normal;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case sysStat_Normal:
 | 
			
		||||
        strcpy_P(globals.systemStatustxt, PSTR("Normal"));
 | 
			
		||||
        if (lastSystemStatus != globals.systemStatus)
 | 
			
		||||
        {
 | 
			
		||||
            strcpy_P(globals.systemStatustxt, PSTR("Normal"));
 | 
			
		||||
            LEDControl_SetBasic(LED_NORMAL_COLOR, LED_PATTERN_ON);
 | 
			
		||||
            lastSystemStatus = globals.systemStatus;
 | 
			
		||||
            globals.resumeStatus = sysStat_Normal;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Trigger lube pulse if traveled distance exceeds the configured limit
 | 
			
		||||
        if (PersistenceData.TravelDistance_highRes_mm / 1000 > LubeConfig.DistancePerLube_Default)
 | 
			
		||||
        {
 | 
			
		||||
@@ -69,7 +87,14 @@ void RunLubeApp(uint32_t add_milimeters)
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case sysStat_Rain:
 | 
			
		||||
        strcpy_P(globals.systemStatustxt, PSTR("Rain"));
 | 
			
		||||
        if (lastSystemStatus != globals.systemStatus)
 | 
			
		||||
        {
 | 
			
		||||
            strcpy_P(globals.systemStatustxt, PSTR("Rain"));
 | 
			
		||||
            LEDControl_SetBasic(LED_RAIN_COLOR, LED_PATTERN_ON);
 | 
			
		||||
            lastSystemStatus = globals.systemStatus;
 | 
			
		||||
            globals.resumeStatus = sysStat_Rain;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Trigger lube pulse if traveled distance exceeds the configured limit in Rain mode
 | 
			
		||||
        if (PersistenceData.TravelDistance_highRes_mm / 1000 > LubeConfig.DistancePerLube_Rain)
 | 
			
		||||
        {
 | 
			
		||||
@@ -78,8 +103,41 @@ void RunLubeApp(uint32_t add_milimeters)
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case sysStat_Wash:
 | 
			
		||||
        if (lastSystemStatus != globals.systemStatus)
 | 
			
		||||
        {
 | 
			
		||||
            washModeDistance = LubeConfig.WashMode_Distance;
 | 
			
		||||
            strcpy_P(globals.systemStatustxt, PSTR("Wash"));
 | 
			
		||||
            LEDControl_SetBasic(LED_WASH_COLOR, LED_PATTERN_BREATH);
 | 
			
		||||
            lastSystemStatus = globals.systemStatus;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Trigger lube pulse if traveled distance exceeds the configured Interval in Wash mode
 | 
			
		||||
        if (PersistenceData.TravelDistance_highRes_mm / 1000 > LubeConfig.WashMode_Interval)
 | 
			
		||||
        {
 | 
			
		||||
            LubePulse();
 | 
			
		||||
            PersistenceData.TravelDistance_highRes_mm = 0;
 | 
			
		||||
 | 
			
		||||
            if (washModeDistance >= LubeConfig.WashMode_Distance)
 | 
			
		||||
            {
 | 
			
		||||
                washModeDistance = washModeDistance - LubeConfig.WashMode_Interval;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                globals.systemStatus = globals.resumeStatus;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case sysStat_Purge:
 | 
			
		||||
        strcpy_P(globals.systemStatustxt, PSTR("Purge"));
 | 
			
		||||
        if (lastSystemStatus != globals.systemStatus)
 | 
			
		||||
        {
 | 
			
		||||
            globals.purgePulses = LubeConfig.BleedingPulses;
 | 
			
		||||
            strcpy_P(globals.systemStatustxt, PSTR("Purge"));
 | 
			
		||||
            LEDControl_SetBasic(LED_PURGE_COLOR, LED_PATTERN_BLINK);
 | 
			
		||||
            lastSystemStatus = globals.systemStatus;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Execute lube pulses during the Purge status
 | 
			
		||||
        if (globals.purgePulses > 0)
 | 
			
		||||
        {
 | 
			
		||||
@@ -99,12 +157,26 @@ void RunLubeApp(uint32_t add_milimeters)
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case sysStat_Error:
 | 
			
		||||
        strcpy_P(globals.systemStatustxt, PSTR("Error"));
 | 
			
		||||
 | 
			
		||||
        if (lastSystemStatus != globals.systemStatus)
 | 
			
		||||
        {
 | 
			
		||||
            strcpy_P(globals.systemStatustxt, PSTR("Error"));
 | 
			
		||||
            LEDControl_SetBasic(LED_ERROR_COLOR, LED_PATTERN_BLINK_FAST);
 | 
			
		||||
            lastSystemStatus = globals.systemStatus;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        globals.purgePulses = 0;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case sysStat_Shutdown:
 | 
			
		||||
        strcpy_P(globals.systemStatustxt, PSTR("Shutdown"));
 | 
			
		||||
 | 
			
		||||
        if (lastSystemStatus != globals.systemStatus)
 | 
			
		||||
        {
 | 
			
		||||
            strcpy_P(globals.systemStatustxt, PSTR("Shutdown"));
 | 
			
		||||
            LEDControl_SetBasic(LED_SHUTDOWN_COLOR, LED_PATTERN_BREATH_REVERSE);
 | 
			
		||||
            lastSystemStatus = globals.systemStatus;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,10 @@
 | 
			
		||||
#include "led_colors.h"
 | 
			
		||||
#include "obd2_kline.h"
 | 
			
		||||
#include "obd2_can.h"
 | 
			
		||||
#include "buttoncontrol.h"
 | 
			
		||||
#include "button_actions.h"
 | 
			
		||||
#include "ledcontrol.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#ifdef FEATURE_ENABLE_WIFI_CLIENT
 | 
			
		||||
#include <ESP8266WiFiMulti.h>
 | 
			
		||||
@@ -59,12 +63,10 @@ Adafruit_NeoPixel leds(1, GPIO_LED, NEO_RGB + NEO_KHZ800);
 | 
			
		||||
 | 
			
		||||
// Function-Prototypes
 | 
			
		||||
void IRAM_ATTR trigger_ISR();
 | 
			
		||||
void LED_Process(uint8_t override = false, uint32_t setColor = LED_DEFAULT_COLOR);
 | 
			
		||||
#ifdef FEATURE_ENABLE_OLED
 | 
			
		||||
U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(-1);
 | 
			
		||||
void Display_Process();
 | 
			
		||||
#endif
 | 
			
		||||
void Button_Process();
 | 
			
		||||
void toggleWiFiAP(bool shutdown = false);
 | 
			
		||||
void SystemShutdown(bool restart = false);
 | 
			
		||||
uint32_t Process_Impulse_WheelSpeed();
 | 
			
		||||
@@ -134,7 +136,7 @@ void setup()
 | 
			
		||||
  Serial.print("\nEE-Init done");
 | 
			
		||||
 | 
			
		||||
  // Initialize LEDs
 | 
			
		||||
  leds.begin();
 | 
			
		||||
  LEDControl_Init(GPIO_LED);
 | 
			
		||||
  Serial.print("\nLED-Init done");
 | 
			
		||||
 | 
			
		||||
  // Initialize based on the chosen speed source (CAN, GPS, Impulse)
 | 
			
		||||
@@ -175,6 +177,8 @@ void setup()
 | 
			
		||||
  pinMode(GPIO_BUTTON, INPUT_PULLUP);
 | 
			
		||||
  pinMode(GPIO_PUMP, OUTPUT);
 | 
			
		||||
 | 
			
		||||
  ButtonControl_Init(GPIO_BUTTON, buttonActions, buttonActionCount);
 | 
			
		||||
 | 
			
		||||
  // Set up OTA updates
 | 
			
		||||
  ArduinoOTA.setPort(8266);
 | 
			
		||||
  ArduinoOTA.setHostname(globals.DeviceName);
 | 
			
		||||
@@ -249,8 +253,8 @@ void loop()
 | 
			
		||||
 | 
			
		||||
  // Process button input, manage LED behavior, perform EEPROM tasks, handle webserver operations,
 | 
			
		||||
  // process Diagnostic Trouble Codes (DTC), and manage debugging
 | 
			
		||||
  Button_Process();
 | 
			
		||||
  LED_Process();
 | 
			
		||||
  ButtonControl_Update();
 | 
			
		||||
  LEDControl_Update();
 | 
			
		||||
  EEPROM_Process();
 | 
			
		||||
  Webserver_Process();
 | 
			
		||||
  DTC_Process();
 | 
			
		||||
@@ -340,208 +344,6 @@ void trigger_ISR()
 | 
			
		||||
  wheel_pulse++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Manages LED behavior based on the current system status and user overrides.
 | 
			
		||||
 *
 | 
			
		||||
 * This function handles LED behavior, including startup animations, confirmation animations for
 | 
			
		||||
 * normal and rain modes, indication for purge, error, shutdown, and normal operation. It supports
 | 
			
		||||
 * user overrides to set a specific LED color. The LED status is determined by the current system
 | 
			
		||||
 * status, and specific LED patterns are displayed accordingly.
 | 
			
		||||
 *
 | 
			
		||||
 * @param override Flag indicating whether to override the LED behavior (0: No override, 1: Override, 2: Resume previous state).
 | 
			
		||||
 * @param SetColor The color to set when overriding the LED behavior.
 | 
			
		||||
 */
 | 
			
		||||
void LED_Process(uint8_t override, uint32_t SetColor)
 | 
			
		||||
{
 | 
			
		||||
  // Enumeration to represent LED status
 | 
			
		||||
  typedef enum
 | 
			
		||||
  {
 | 
			
		||||
    LED_Startup,
 | 
			
		||||
    LED_Normal,
 | 
			
		||||
    LED_Confirm_Normal,
 | 
			
		||||
    LED_Rain,
 | 
			
		||||
    LED_Confirm_Rain,
 | 
			
		||||
    LED_Purge,
 | 
			
		||||
    LED_Error,
 | 
			
		||||
    LED_Shutdown,
 | 
			
		||||
    LED_Override
 | 
			
		||||
  } tLED_Status;
 | 
			
		||||
 | 
			
		||||
  // Static variables to track LED status, system status, override color, and previous LED status
 | 
			
		||||
  static tSystem_Status oldSysStatus = sysStat_Startup;
 | 
			
		||||
  static tLED_Status LED_Status = LED_Startup;
 | 
			
		||||
  static uint32_t LED_override_color = 0;
 | 
			
		||||
  static tLED_Status LED_ResumeOverrideStatus = LED_Startup;
 | 
			
		||||
 | 
			
		||||
  // Variables for managing LED animation timing
 | 
			
		||||
  uint8_t color = 0;
 | 
			
		||||
  uint32_t timer = 0;
 | 
			
		||||
  uint32_t animtimer = 0;
 | 
			
		||||
  static uint32_t timestamp = 0;
 | 
			
		||||
  timer = millis();
 | 
			
		||||
 | 
			
		||||
  // Handle LED overrides
 | 
			
		||||
  if (override == 1)
 | 
			
		||||
  {
 | 
			
		||||
    if (LED_Status != LED_Override)
 | 
			
		||||
    {
 | 
			
		||||
      LED_ResumeOverrideStatus = LED_Status;
 | 
			
		||||
      Debug_pushMessage("Override LED_Status\n");
 | 
			
		||||
    }
 | 
			
		||||
    LED_Status = LED_Override;
 | 
			
		||||
    LED_override_color = SetColor;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (override == 2)
 | 
			
		||||
  {
 | 
			
		||||
    if (LED_Status == LED_Override)
 | 
			
		||||
    {
 | 
			
		||||
      LED_Status = LED_ResumeOverrideStatus;
 | 
			
		||||
      Debug_pushMessage("Resume LED_Status\n");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Update LED status when system status changes
 | 
			
		||||
  if (oldSysStatus != globals.systemStatus)
 | 
			
		||||
  {
 | 
			
		||||
    switch (globals.systemStatus)
 | 
			
		||||
    {
 | 
			
		||||
    case sysStat_Startup:
 | 
			
		||||
      LED_Status = LED_Startup;
 | 
			
		||||
      Debug_pushMessage("sysStat: Startup\n");
 | 
			
		||||
      break;
 | 
			
		||||
    case sysStat_Normal:
 | 
			
		||||
      timestamp = timer + 3500;
 | 
			
		||||
      LED_Status = LED_Confirm_Normal;
 | 
			
		||||
      Debug_pushMessage("sysStat: Normal\n");
 | 
			
		||||
      break;
 | 
			
		||||
    case sysStat_Rain:
 | 
			
		||||
      timestamp = timer + 3500;
 | 
			
		||||
      LED_Status = LED_Confirm_Rain;
 | 
			
		||||
      Debug_pushMessage("sysStat: Rain\n");
 | 
			
		||||
      break;
 | 
			
		||||
    case sysStat_Purge:
 | 
			
		||||
      LED_Status = LED_Purge;
 | 
			
		||||
      Debug_pushMessage("sysStat: Purge\n");
 | 
			
		||||
      break;
 | 
			
		||||
    case sysStat_Error:
 | 
			
		||||
      LED_Status = LED_Error;
 | 
			
		||||
      Debug_pushMessage("sysStat: Error\n");
 | 
			
		||||
      break;
 | 
			
		||||
    case sysStat_Shutdown:
 | 
			
		||||
      LED_Status = LED_Shutdown;
 | 
			
		||||
      Debug_pushMessage("sysStat: Shutdown\n");
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    oldSysStatus = globals.systemStatus;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Handle different LED statuses
 | 
			
		||||
  switch (LED_Status)
 | 
			
		||||
  {
 | 
			
		||||
  case LED_Startup:
 | 
			
		||||
    leds.setBrightness(LubeConfig.LED_Max_Brightness);
 | 
			
		||||
 | 
			
		||||
    if (globals.TankPercentage < LubeConfig.TankRemindAtPercentage)
 | 
			
		||||
      leds.setPixelColor(0, LED_STARTUP_TANKWARN);
 | 
			
		||||
    else
 | 
			
		||||
      leds.setPixelColor(0, LED_STARTUP_NORMAL);
 | 
			
		||||
    break;
 | 
			
		||||
 | 
			
		||||
  case LED_Confirm_Normal:
 | 
			
		||||
    animtimer = timer % 500;
 | 
			
		||||
    color = map(animtimer / 2, 0, 250, 0, LubeConfig.LED_Max_Brightness);
 | 
			
		||||
    leds.setPixelColor(0, LED_NORMAL_COLOR);
 | 
			
		||||
    if (animtimer < 250)
 | 
			
		||||
      leds.setBrightness(color);
 | 
			
		||||
    else
 | 
			
		||||
      leds.setBrightness(LubeConfig.LED_Max_Brightness - color);
 | 
			
		||||
 | 
			
		||||
    if (timestamp < timer)
 | 
			
		||||
    {
 | 
			
		||||
      LED_Status = LED_Normal;
 | 
			
		||||
      Debug_pushMessage("LED_Status: Confirm -> Normal\n");
 | 
			
		||||
    }
 | 
			
		||||
    break;
 | 
			
		||||
 | 
			
		||||
  case LED_Normal:
 | 
			
		||||
    leds.setBrightness(LubeConfig.LED_Min_Brightness);
 | 
			
		||||
    leds.setPixelColor(0, LED_NORMAL_COLOR);
 | 
			
		||||
 | 
			
		||||
    if (timer % 2000 > 1950 && LubeConfig.LED_Mode_Flash == true)
 | 
			
		||||
      leds.setBrightness(LubeConfig.LED_Max_Brightness);
 | 
			
		||||
    else if (timer % 2000 > 1500 && WiFi.getMode() != WIFI_OFF)
 | 
			
		||||
      leds.setPixelColor(0, LED_WIFI_BLINK);
 | 
			
		||||
 | 
			
		||||
    break;
 | 
			
		||||
 | 
			
		||||
  case LED_Confirm_Rain:
 | 
			
		||||
    animtimer = timer % 500;
 | 
			
		||||
    color = map(animtimer / 2, 0, 250, 0, LubeConfig.LED_Max_Brightness);
 | 
			
		||||
    leds.setPixelColor(0, LED_RAIN_COLOR);
 | 
			
		||||
    if (animtimer < 250)
 | 
			
		||||
      leds.setBrightness(color);
 | 
			
		||||
    else
 | 
			
		||||
      leds.setBrightness(LubeConfig.LED_Max_Brightness - color);
 | 
			
		||||
    if (timestamp < timer)
 | 
			
		||||
    {
 | 
			
		||||
      LED_Status = LED_Rain;
 | 
			
		||||
      Debug_pushMessage("LED_Status: Confirm -> Rain\n");
 | 
			
		||||
    }
 | 
			
		||||
    break;
 | 
			
		||||
 | 
			
		||||
  case LED_Rain:
 | 
			
		||||
    leds.setBrightness(LubeConfig.LED_Min_Brightness);
 | 
			
		||||
    leds.setPixelColor(0, LED_RAIN_COLOR);
 | 
			
		||||
 | 
			
		||||
    if (timer % 2000 > 1950 && LubeConfig.LED_Mode_Flash == true)
 | 
			
		||||
      leds.setBrightness(LubeConfig.LED_Max_Brightness);
 | 
			
		||||
    else if (timer % 2000 > 1500 && WiFi.getMode() != WIFI_OFF)
 | 
			
		||||
      leds.setPixelColor(0, LED_WIFI_BLINK);
 | 
			
		||||
 | 
			
		||||
    break;
 | 
			
		||||
 | 
			
		||||
  case LED_Purge:
 | 
			
		||||
    timer = timer % 500;
 | 
			
		||||
    color = map(timer / 2, 0, 250, LubeConfig.LED_Min_Brightness, LubeConfig.LED_Max_Brightness);
 | 
			
		||||
    leds.setPixelColor(0, LED_PURGE_COLOR);
 | 
			
		||||
    if (timer < 250)
 | 
			
		||||
      leds.setBrightness(color);
 | 
			
		||||
    else
 | 
			
		||||
      leds.setBrightness(LubeConfig.LED_Max_Brightness - color);
 | 
			
		||||
    break;
 | 
			
		||||
 | 
			
		||||
  case LED_Error:
 | 
			
		||||
    leds.setBrightness(LubeConfig.LED_Max_Brightness);
 | 
			
		||||
    leds.setPixelColor(0, timer % 500 > 250 ? LED_ERROR_BLINK : 0);
 | 
			
		||||
    break;
 | 
			
		||||
 | 
			
		||||
  case LED_Shutdown:
 | 
			
		||||
    timer = timer % 600;
 | 
			
		||||
    leds.setPixelColor(0, LED_SHUTDOWN_BLINK);
 | 
			
		||||
    if (timer < 500)
 | 
			
		||||
    {
 | 
			
		||||
      color = map(timer, 0, 500, LubeConfig.LED_Max_Brightness, LubeConfig.LED_Min_Brightness);
 | 
			
		||||
      leds.setBrightness(color);
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
      leds.setBrightness(LubeConfig.LED_Min_Brightness);
 | 
			
		||||
    }
 | 
			
		||||
    break;
 | 
			
		||||
 | 
			
		||||
  case LED_Override:
 | 
			
		||||
    leds.setBrightness(LubeConfig.LED_Max_Brightness);
 | 
			
		||||
    leds.setPixelColor(0, LED_override_color);
 | 
			
		||||
    break;
 | 
			
		||||
 | 
			
		||||
  default:
 | 
			
		||||
    break;
 | 
			
		||||
  }
 | 
			
		||||
  leds.show();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef FEATURE_ENABLE_OLED
 | 
			
		||||
/**
 | 
			
		||||
@@ -597,119 +399,6 @@ void Display_Process()
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Processes the button input and performs corresponding actions based on button state and timing.
 | 
			
		||||
 *
 | 
			
		||||
 * This function handles the button input, detecting button presses and executing actions based on
 | 
			
		||||
 * predefined time delays. Actions include toggling WiFi, starting purge, toggling operating modes,
 | 
			
		||||
 * and displaying feedback through LEDs. The function utilizes an enumeration to track button actions
 | 
			
		||||
 * and manages the timing for different actions.
 | 
			
		||||
 */
 | 
			
		||||
void Button_Process()
 | 
			
		||||
{
 | 
			
		||||
  // Time delays for different button actions
 | 
			
		||||
#define BUTTON_ACTION_DELAY_TOGGLEMODE 500
 | 
			
		||||
#define BUTTON_ACTION_DELAY_PURGE 3500
 | 
			
		||||
#define BUTTON_ACTION_DELAY_WIFI 6500
 | 
			
		||||
#define BUTTON_ACTION_DELAY_NOTHING 9500
 | 
			
		||||
 | 
			
		||||
  // Enumeration to represent button actions
 | 
			
		||||
  typedef enum buttonAction_e
 | 
			
		||||
  {
 | 
			
		||||
    BTN_INACTIVE,
 | 
			
		||||
    BTN_NOTHING,
 | 
			
		||||
    BTN_TOGGLEMODE,
 | 
			
		||||
    BTN_TOGGLEWIFI,
 | 
			
		||||
    BTN_STARTPURGE
 | 
			
		||||
  } buttonAction_t;
 | 
			
		||||
 | 
			
		||||
  // Static variables to track button state and timing
 | 
			
		||||
  static uint32_t buttonTimestamp = 0;
 | 
			
		||||
  static buttonAction_t buttonAction = BTN_INACTIVE;
 | 
			
		||||
 | 
			
		||||
  // Check if button is pressed (LOW)
 | 
			
		||||
  if (digitalRead(GPIO_BUTTON) == LOW)
 | 
			
		||||
  {
 | 
			
		||||
    // Update button timestamp on the first button press
 | 
			
		||||
    if (buttonTimestamp == 0)
 | 
			
		||||
      buttonTimestamp = millis();
 | 
			
		||||
 | 
			
		||||
    // Check and execute actions based on predefined time delays
 | 
			
		||||
    if (buttonTimestamp + BUTTON_ACTION_DELAY_NOTHING < millis())
 | 
			
		||||
    {
 | 
			
		||||
      LED_Process(1, COLOR_WARM_WHITE);
 | 
			
		||||
      buttonAction = BTN_NOTHING;
 | 
			
		||||
    }
 | 
			
		||||
    else if (buttonTimestamp + BUTTON_ACTION_DELAY_WIFI < millis())
 | 
			
		||||
    {
 | 
			
		||||
      LED_Process(1, LED_WIFI_BLINK);
 | 
			
		||||
      buttonAction = BTN_TOGGLEWIFI;
 | 
			
		||||
    }
 | 
			
		||||
    else if (buttonTimestamp + BUTTON_ACTION_DELAY_PURGE < millis())
 | 
			
		||||
    {
 | 
			
		||||
      LED_Process(1, LED_PURGE_COLOR);
 | 
			
		||||
      buttonAction = BTN_STARTPURGE;
 | 
			
		||||
    }
 | 
			
		||||
    else if (buttonTimestamp + BUTTON_ACTION_DELAY_TOGGLEMODE < millis())
 | 
			
		||||
    {
 | 
			
		||||
      uint32_t color = globals.systemStatus == sysStat_Normal ? LED_RAIN_COLOR : LED_NORMAL_COLOR;
 | 
			
		||||
      LED_Process(1, color);
 | 
			
		||||
      buttonAction = BTN_TOGGLEMODE;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  else // Button is released
 | 
			
		||||
  {
 | 
			
		||||
    // Execute corresponding actions based on the detected button action
 | 
			
		||||
    if (buttonAction != BTN_INACTIVE)
 | 
			
		||||
    {
 | 
			
		||||
      switch (buttonAction)
 | 
			
		||||
      {
 | 
			
		||||
      case BTN_TOGGLEWIFI:
 | 
			
		||||
        toggleWiFiAP();
 | 
			
		||||
        Debug_pushMessage("Starting WiFi AP\n");
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case BTN_STARTPURGE:
 | 
			
		||||
        globals.systemStatus = sysStat_Purge;
 | 
			
		||||
        globals.purgePulses = LubeConfig.BleedingPulses;
 | 
			
		||||
        Debug_pushMessage("Starting Purge\n");
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case BTN_TOGGLEMODE:
 | 
			
		||||
        switch (globals.systemStatus)
 | 
			
		||||
        {
 | 
			
		||||
        case sysStat_Normal:
 | 
			
		||||
          globals.systemStatus = sysStat_Rain;
 | 
			
		||||
          globals.resumeStatus = sysStat_Rain;
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case sysStat_Rain:
 | 
			
		||||
          globals.systemStatus = sysStat_Normal;
 | 
			
		||||
          globals.resumeStatus = sysStat_Normal;
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        Debug_pushMessage("Toggling Mode\n");
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case BTN_NOTHING:
 | 
			
		||||
      default:
 | 
			
		||||
        Debug_pushMessage("Nothing or invalid\n");
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Display feedback through LEDs
 | 
			
		||||
      LED_Process(2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Reset button state and timestamp
 | 
			
		||||
    buttonAction = BTN_INACTIVE;
 | 
			
		||||
    buttonTimestamp = 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Toggles the WiFi functionality based on the current status.
 | 
			
		||||
 *
 | 
			
		||||
@@ -722,6 +411,8 @@ void Button_Process()
 | 
			
		||||
 */
 | 
			
		||||
void toggleWiFiAP(bool shutdown)
 | 
			
		||||
{
 | 
			
		||||
  LEDControl_SetOverride(LED_WIFI_COLOR, LED_PATTERN_BLINK_FAST, 2500);
 | 
			
		||||
 | 
			
		||||
  // Check if WiFi is currently active
 | 
			
		||||
  if (WiFi.getMode() != WIFI_OFF)
 | 
			
		||||
  {
 | 
			
		||||
@@ -788,6 +479,36 @@ void SystemShutdown(bool restart)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void onToggleMode()
 | 
			
		||||
{
 | 
			
		||||
    if (globals.systemStatus == sysStat_Normal)
 | 
			
		||||
    {
 | 
			
		||||
        globals.systemStatus = sysStat_Rain;
 | 
			
		||||
        globals.resumeStatus = sysStat_Rain;
 | 
			
		||||
    }
 | 
			
		||||
    else if (globals.systemStatus == sysStat_Rain)
 | 
			
		||||
    {
 | 
			
		||||
        globals.systemStatus = sysStat_Normal;
 | 
			
		||||
        globals.resumeStatus = sysStat_Normal;
 | 
			
		||||
    }
 | 
			
		||||
    Debug_pushMessage("Toggling Mode\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void onStartPurge()
 | 
			
		||||
{
 | 
			
		||||
    globals.systemStatus = sysStat_Purge;
 | 
			
		||||
    globals.purgePulses = LubeConfig.BleedingPulses;
 | 
			
		||||
    Debug_pushMessage("Starting Purge\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void onWashMode()
 | 
			
		||||
{
 | 
			
		||||
    Debug_pushMessage("Wash mode start\n");
 | 
			
		||||
    // Hier könntest du später gezieltes Nachölen implementieren
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @brief Processes the impulses from the wheel speed sensor and converts them into traveled distance.
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,10 @@
 | 
			
		||||
 *
 | 
			
		||||
 * @brief Implementation file for converting structs to JSON objects.
 | 
			
		||||
 *
 | 
			
		||||
 * @note This file is auto-generated by a script on 2024-01-30 20:29:34.
 | 
			
		||||
 * @note This file is auto-generated by a script on 2025-06-15 11:37:51.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Marcel Peterkau
 | 
			
		||||
 * @date   30.01.2024
 | 
			
		||||
 * @date   15.06.2025
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +26,8 @@ void generateJsonObject_LubeConfig(JsonObject data)
 | 
			
		||||
    data["RimDiameter_Inch"] = LubeConfig.RimDiameter_Inch;
 | 
			
		||||
    data["DistancePerRevolution_mm"] = LubeConfig.DistancePerRevolution_mm;
 | 
			
		||||
    data["BleedingPulses"] = LubeConfig.BleedingPulses;
 | 
			
		||||
    data["WashMode_Distance"] = LubeConfig.WashMode_Distance;
 | 
			
		||||
    data["WashMode_Interval"] = LubeConfig.WashMode_Interval;
 | 
			
		||||
    data["SpeedSource"] = LubeConfig.SpeedSource;
 | 
			
		||||
    data["GPSBaudRate"] = LubeConfig.GPSBaudRate;
 | 
			
		||||
    data["CANSource"] = LubeConfig.CANSource;
 | 
			
		||||
@@ -52,4 +54,4 @@ void generateJsonObject_PersistenceData(JsonObject data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// CODEGENERATOR_CHECKSUM: 59f35aadffd0bbef253210ea2fbaaf9a515553a2e3cc9bf4cfa2819b63c969ce
 | 
			
		||||
// CODEGENERATOR_CHECKSUM: 4702cb49ea55617cbb34715164810bb58d3c3f46fb1653b6f47bd4fd9cb0031e
 | 
			
		||||
@@ -480,7 +480,6 @@ void Websocket_HandleButtons(uint8_t *data)
 | 
			
		||||
  else if (strcmp(identifier, "purgenow") == 0)
 | 
			
		||||
  {
 | 
			
		||||
    globals.systemStatus = sysStat_Purge;
 | 
			
		||||
    globals.purgePulses = LubeConfig.BleedingPulses;
 | 
			
		||||
  }
 | 
			
		||||
  else if (strcmp(identifier, "sourcesave") == 0)
 | 
			
		||||
  {
 | 
			
		||||
@@ -499,6 +498,7 @@ void Websocket_HandleButtons(uint8_t *data)
 | 
			
		||||
  else if (strcmp(identifier, "resettank") == 0)
 | 
			
		||||
  {
 | 
			
		||||
    PersistenceData.tankRemain_microL = LubeConfig.tankCapacity_ml * 1000;
 | 
			
		||||
    globals.requestEEAction = EE_PDS_SAVE;
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
@@ -580,6 +580,14 @@ void Websocket_HandleSettings(uint8_t *data)
 | 
			
		||||
  {
 | 
			
		||||
    strncpy(LubeConfig.wifi_client_password, value, sizeof(LubeConfig.wifi_client_password));
 | 
			
		||||
  }
 | 
			
		||||
  else if (strcmp(identifier, "washinterval") == 0)
 | 
			
		||||
  {
 | 
			
		||||
    LubeConfig.WashMode_Interval = atoi(value);
 | 
			
		||||
  }
 | 
			
		||||
  else if (strcmp(identifier, "washdistance") == 0)
 | 
			
		||||
  {
 | 
			
		||||
    LubeConfig.WashMode_Distance = atoi(value);
 | 
			
		||||
  }
 | 
			
		||||
  else
 | 
			
		||||
  {
 | 
			
		||||
    Debug_pushMessage("Got unknown Settings-id and value '%s' from ws-client\n", identifier);
 | 
			
		||||
@@ -700,6 +708,8 @@ void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping)
 | 
			
		||||
    const char mapping[] = "MAPPING_STATIC:"
 | 
			
		||||
                           "lubedistancenormal;"
 | 
			
		||||
                           "lubedistancerain;"
 | 
			
		||||
                           "washdistance;"
 | 
			
		||||
                           "washinterval;"
 | 
			
		||||
                           "tankcap;"
 | 
			
		||||
                           "pumppulse;"
 | 
			
		||||
                           "tankwarn;"
 | 
			
		||||
@@ -730,6 +740,8 @@ void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping)
 | 
			
		||||
 | 
			
		||||
  temp.concat(String(LubeConfig.DistancePerLube_Default) + ";");
 | 
			
		||||
  temp.concat(String(LubeConfig.DistancePerLube_Rain) + ";");
 | 
			
		||||
  temp.concat(String(LubeConfig.WashMode_Distance) + ";");
 | 
			
		||||
  temp.concat(String(LubeConfig.WashMode_Interval) + ";");
 | 
			
		||||
  temp.concat(String(LubeConfig.tankCapacity_ml) + ";");
 | 
			
		||||
  temp.concat(String(LubeConfig.amountPerDose_microL) + ";");
 | 
			
		||||
  temp.concat(String(LubeConfig.TankRemindAtPercentage) + ";");
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user