diff --git a/arduinoIDE2platformIO-convertor/.gitignore b/arduinoIDE2platformIO-convertor/.gitignore new file mode 100644 index 0000000..c31c14b --- /dev/null +++ b/arduinoIDE2platformIO-convertor/.gitignore @@ -0,0 +1,14 @@ +.pio +.pio.nosync +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +.vscode/**/* +testProject/*/PlatformIO/* +claude-dev +changes +backups +changes/* +next.py +saveNext.py diff --git a/arduinoIDE2platformIO-convertor/LICENSE b/arduinoIDE2platformIO-convertor/LICENSE new file mode 100644 index 0000000..aa24e39 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Willem Aandewiel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/arduinoIDE2platformIO-convertor/README.md b/arduinoIDE2platformIO-convertor/README.md new file mode 100644 index 0000000..df71210 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/README.md @@ -0,0 +1,23 @@ +# How to convert a ArduinoIDE project to PlatformIO + +1) You have to clone the repo to your computer. +2) In a terminal window you `cd` to the folder where you cloned the repo. +3) Then you type `python3 arduinoIDE2platformIO.py --project_dir ` +(note, there are two `--` before `project_dir`) + +The converted project is located in `/platformIO/` + +All you have to do is edit the `platformio.ini` file to your needs. + +Mind you: the convertor will not always do everything for you. Sometimes you have to iterate [compile] -> solve compile errors -> [compile] -> solve compile errors ... + +If it compiles without errors test it. If it works as expected you can cleanup the ‘arduinoGlue.h’ file with the ‘crossReference.py’ file. + +Next step can be to look for prototypes that are not used in any other file then where the function is defined. You can then move that prototype definition to the '.h' file of the corresponding '.cpp' file. +It is not necessary but it makes better C(++) code. + + +## structure ArduinoIDE project + +## structure PlatformIO project + diff --git a/arduinoIDE2platformIO-convertor/analizeNext.py b/arduinoIDE2platformIO-convertor/analizeNext.py new file mode 100644 index 0000000..b832ed7 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/analizeNext.py @@ -0,0 +1,54 @@ +import ast + +# Read the content of the uploaded file +file_path = '../../next.py' +with open(file_path, 'r') as file: + file_content = file.read() + +# Parse the content of the file +parsed_ast = ast.parse(file_content) + +# Initialize a dictionary to store function calls +function_calls = {} + +class FunctionVisitor(ast.NodeVisitor): + def __init__(self): + self.current_function = None + + def visit_FunctionDef(self, node): + self.current_function = node.name + function_calls[self.current_function] = [] + self.generic_visit(node) + + def visit_Call(self, node): + if isinstance(node.func, ast.Name) and self.current_function: + function_calls[self.current_function].append(node.func.id) + self.generic_visit(node) + +# Visit all nodes in the AST +visitor = FunctionVisitor() +visitor.visit(parsed_ast) + +# Filter only the functions defined in the file +defined_functions = set(function_calls.keys()) + +# Sort functions based on the order of calling +sorted_function_calls = sorted( + [(func, [call for call in calls if call in defined_functions]) + for func, calls in function_calls.items()], + key=lambda item: item[0] +) + +# Print the sorted function calls +for function, calls in sorted_function_calls: + if function != "main": + #print(f"Function '{function}' calls: {', '.join(calls) if calls else 'No calls'}") + print(f"Function '{function}' calls:") + for func in calls: + print(f"\t{func}") +for function, calls in sorted_function_calls: + if function == "main": + #print(f"Function '{function}' calls: {', '.join(calls) if calls else 'No calls'}") + print(f"Function '{function}' calls:") + for func in calls: + print(f"\t{func}") diff --git a/arduinoIDE2platformIO-convertor/arduinoIDE2platformIO.py b/arduinoIDE2platformIO-convertor/arduinoIDE2platformIO.py new file mode 100644 index 0000000..d991813 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/arduinoIDE2platformIO.py @@ -0,0 +1,2928 @@ +#------------------------------------------------------------ +# +# convert a ArduinoIDE project to a PlatformIO project +# +# file name : arduinoIDE2platformIO.py +# +# by : Willem Aandewiel +# +# Version : v0.79 (06-09-2024) +# +# license : MIT (see at the bottom of this file) +#------------------------------------------------------------ +import os +import sys +import shutil +import re +import argparse +import logging +import traceback +from datetime import datetime +from collections import OrderedDict + +# Extended list of known classes +dict_known_classes = [ + 'WiFiServer', 'ESP8266WebServer', 'WiFiClient', 'WebServer', + 'WiFiManager', 'Timezone', 'DNSServer', 'IPAddress' + 'ESP8266mDNS', 'ArduinoOTA', 'PubSubClient', 'NTPClient', 'Ticker', + 'ESP8266HTTPClient', 'WebSocketsServer', 'AsyncWebServer', 'AsyncWebSocket', + 'SPIFFSConfig', 'HTTPClient', 'WiFiUDP', 'ESP8266WiFiMulti', 'ESP8266SSDP', + 'ESP8266HTTPUpdateServer', 'ESP8266mDNS', 'Adafruit_Sensor', 'DHT', + 'LiquidCrystal', 'Servo', 'Stepper', 'SoftwareSerial', 'EEPROM', + 'TFT_eSPI', 'Adafruit_GFX', 'SD', 'Wire', 'SPI', 'OneWire', 'DallasTemperature', + 'Adafruit_NeoPixel', 'FastLED', 'IRremote', 'ESP32Encoder', 'CapacitiveSensor', + 'AccelStepper', 'ESP32Time', 'BluetoothSerial', 'BLEDevice', 'BLEServer', + 'BLEClient', 'SPIFFS', 'LittleFS', 'ESPAsyncWebServer', 'AsyncTCP', + 'ESP32_FTPClient', 'PCA9685', 'Adafruit_PWMServoDriver', 'MPU6050', + 'TinyGPS', 'RTClib', 'Preferences', 'ESPmDNS', 'Update', 'HTTPUpdate', + 'HTTPSServer', 'HTTPSServerRequest', 'HTTPSServerResponse' + ] + +# Dictionary of libraries and their associated objects +dict_singleton_classes = { + "LittleFS.h": ["Dir", "FSInfo", "FSinfo", "File", "exists", "FS", "LittleFS"], + "SPI.h": ["SPI", "SPISettings", "SPIClass"], + "Wire.h": ["Wire", "TwoWire"], + "IPAddress.h": ["IPAddress"] + # Add other libraries and their keywords here +} + +args = None +glob_project_name = "" +glob_ino_project_folder = "" +glob_working_dir = os.getcwd() +glob_pio_folder = "" +glob_pio_project_folder = "" +glob_pio_src = "" +glob_pio_include = "" +dict_all_includes = {} +dict_global_variables = {} +dict_undefined_vars_used = {} +dict_prototypes = {} +dict_class_instances = {} +dict_struct_declarations = {} +dict_includes = {} +platformio_marker = "/PlatformIO" +all_includes_marker = "//============ Includes ====================" +all_includes_added = False +all_defines_marker = "//============ Defines & Macros====================" +all_defines_added = False +struct_union_and_enum_marker = "//============ Structs, Unions & Enums ============" +struct_union_and_enum_added = False +global_pointer_arrays_marker = "//============ Pointer Arrays ============" +global_pointer_arrays_added = False +extern_variables_marker = "//============ Extern Variables ============" +extern_variables_added = False +extern_classes_marker = "//============ Extern Classes ==============" +extern_classes_added = False +prototypes_marker = "//============ Function Prototypes =========" +prototypes_added = False +convertor_marker = "//============ Added by Convertor ==========" +convertor_added = False + +#------------------------------------------------------------------------------------------------------ +def setup_logging(debug=False): + level = logging.DEBUG if debug else logging.INFO + logging.basicConfig( + level=level, + format='%(levelname)7s - :%(lineno)4d - %(message)s' + ) + +#------------------------------------------------------------------------------------------------------ +def parse_arguments(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser(description="Convert Arduino project to PlatformIO structure.") + parser.add_argument("--project_dir", default=os.getcwd(), help="Path to the project directory") + parser.add_argument("--backup", action="store_true", help="Create a backup of original files") + parser.add_argument("--debug", action="store_true", help="Enable debug-level logging") + return parser.parse_args() + +#------------------------------------------------------------------------------------------------------ +def backup_project(): + """Create a backup of the project folder.""" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_folder = f"{glob_ino_project_folder}_backup_{timestamp}" + shutil.copytree(glob_ino_project_folder, backup_folder) + logging.info(f"Project backup created at: {backup_folder}") + +#------------------------------------------------------------------------------------------------------ +def print_dict(dict): + keys = list(dict.keys()) + logging.info(f"Keys: {keys}") + # Iterate over keys and values + logging.info("Iterating over keys and values:") + for key, value in dict_global_variables.items(): + logging.info(f" key[{key}]: value[{value}]") + +#------------------------------------------------------------------------------------------------------ +def set_glob_project_info(project_dir): + """ + Get project folder, name, and PlatformIO-related paths. + + Returns: + tuple: Contains project_folder, glob_project_name, glob_pio_folder, glob_pio_src, glob_pio_include + """ + logging.info("") + logging.info(f"Processing: set_glob_project_info({os.path.basename(project_dir)})") + global glob_ino_project_folder + global glob_pio_project_folder + global glob_working_dir + global glob_project_name + global glob_root_folder + global glob_pio_folder + global glob_pio_src + global glob_pio_include + + # Use project_dir if provided, otherwise use the current working directory + glob_ino_project_folder = os.path.abspath(project_dir) if project_dir else os.path.abspath(glob_working_dir) + glob_project_name = os.path.basename(glob_ino_project_folder) + glob_pio_folder = os.path.join(glob_ino_project_folder, "PlatformIO") + glob_pio_project_folder = os.path.join(glob_pio_folder, glob_project_name) + glob_pio_src = os.path.join(glob_pio_folder, glob_project_name, "src") + glob_pio_include = os.path.join(glob_pio_folder, glob_project_name, "include") + + logging.debug(f"glob_ino_project_folder: {glob_ino_project_folder}") + logging.debug(f"glob_pio_project_folder: {glob_pio_project_folder}") + logging.debug(f" glob_project_name: {glob_project_name}") + logging.debug(f" glob_pio_folder: {glob_pio_folder}") + logging.debug(f" glob_pio_src: {glob_pio_src}") + logging.debug(f" glob_pio_include: {glob_pio_include}\n") + + return + +#------------------------------------------------------------------------------------------------------ +def extract_word_by_position(s, word_number, separator): + """ + Extracts the word at the specified position based on the given separator. + + Args: + s (str): The input string. + word_number (int): The position of the word to extract (0-based index). + separator (str): The separator character to split the string. + + Returns: + str: The extracted word or None if the position is out of range. + """ + # Split the string based on the separator + parts = s.split(separator) + + if separator == '(': + # Further split the part before the first '(' by whitespace to get individual words + words = parts[0].strip().split() + else: + words = parts + + # Return the word at the specified position if within range + if 0 <= word_number < len(words): + return words[word_number].strip() + + return None + +#------------------------------------------------------------------------------------------------------ +def short_path(directory_path): + """ + Format the directory path for logging, shortening it if it contains the {marker}. + + Args: + directory_path (str): The full directory path + marker (str): The marker to look for in the path + + Returns: + str: Formatted string suitable for logging + """ + marker_index = directory_path.find(glob_project_name) + if marker_index != -1: + part_of_path = directory_path[marker_index + len(glob_project_name):] + return f"../{glob_project_name}{part_of_path}" + else: + return f"{directory_path}" + +#------------------------------------------------------------------------------------------------------ +def create_arduinoglue_file(): + """ + Create arduinoGlue.h file with necessary markers and header guards. + """ + try: + all_defines_path = os.path.join(glob_pio_include, 'arduinoGlue.h') + logging.info(f"\tCreating arduinoGlue.h") + with open(all_defines_path, 'w') as f: + f.write("#ifndef ARDUINOGLUE_H\n#define ARDUINOGLUE_H\n\n") + f.write(f"\n{all_includes_marker}") + f.write(f"\n{all_defines_marker}") + f.write(f"\n\n{struct_union_and_enum_marker}") + f.write(f"\n{extern_variables_marker}") + f.write(f"\n{global_pointer_arrays_marker}") + f.write(f"\n{extern_classes_marker}") + f.write(f"\n{prototypes_marker}") + f.write(f"\n{convertor_marker}") + f.write("\n#endif // ARDUINOGLUE_H\n") + + logging.info(f"\tSuccessfully created {short_path(all_defines_path)}") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + logging.error(f"\tError creating arduinoGlue.h: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def find_marker_position(content, prio_marker): + + try: + if (content == ""): + logging.error(f"find_marker_position(): content is empty") + return -1 + + marker = prio_marker + marker_index = content.find(marker) + if marker_index != -1: + return marker_index + len(marker +'\n') + + marker = all_includes_marker + marker_index = content.find(marker) + if marker_index != -1: + return marker_index + len(marker +'\n') + + marker = extern_variables_marker + marker_index = content.find(marker) + if marker_index != -1: + return marker_index + len(marker +'\n') + + marker = prototypes_marker + marker_index = content.find(marker) + if marker_index != -1: + return marker_index + len(marker +'\n') + + marker = convertor_marker + marker_index = content.find(marker) + if marker_index != -1: + return marker_index + len(marker +'\n') + + marker = "" + header_guard_end = re.search(r'#define\s+\w+_H\s*\n', content) + if header_guard_end: + return header_guard_end.end() + + logging.info("") + logging.info("################################### no markers found! ##################################") + logging.info(f"{content}\n") + logging.info("################################### no markers found! ##################################\n\n") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + logging.error(f"\tError creating arduinoGlue.h: {str(e)}") + exit() + + logging.info("\t\t\t===> no markers found!") + return 0 + +#------------------------------------------------------------------------------------------------------ +def remove_comments(code): + # Remove single-line comments + code = re.sub(r'//.*', '', code) + # Remove multi-line comments + code = re.sub(r'/\*[\s\S]*?\*/', '', code) + return code + +#------------------------------------------------------------------------------------------------------ +def print_global_vars(global_vars): + """ + Print global variables line by line, grouped by file. + + Args: + global_vars (dict): Dictionary of global variables, where keys are file paths + and values are lists of tuples (var_type, var_name, function, is_pointer) + """ + if not any(vars_list for vars_list in global_vars.values()): + return + try: + sorted_global_vars = sort_global_vars(global_vars) + + if (len(sorted_global_vars) > 0): + logging.info("") + logging.info("--- Global Variables ---") + for file_path, vars_list in sorted_global_vars.items(): + if vars_list: # Only print for files that have global variables + for var_type, var_name, function, is_pointer in vars_list: + pointer_str = "*" if is_pointer else " " + function_str = function if function else "global scope" + logging.info(f" {var_type:<15} {var_name:<35} {function_str:<20} (in {file_path})") + + logging.info("") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def sort_global_vars(global_vars): + """ + Sort the global variables dictionary by file path and variable name. + + Args: + global_vars (dict): Dictionary of global variables, where keys are file paths + and values are lists of tuples (var_type, var_name, function, is_pointer) + + Returns: + OrderedDict: Sorted dictionary of global variables + """ + sorted_dict = OrderedDict() + for file_path in sorted(global_vars.keys()): + sorted_dict[file_path] = sorted(global_vars[file_path], key=lambda x: x[1]) # Sort by var_name + return sorted_dict + + +#------------------------------------------------------------------------------------------------------ +def print_global_vars_undefined(global_vars_undefined): + """ + Print global variables used in functions. + + Args: + global_vars_undefined (dict): Dictionary of global variables used in functions + """ + try: + if (len(global_vars_undefined) > 0): + logging.info("") + logging.info("--- Undefined Global Variables ---") + for key, info in sorted(global_vars_undefined.items(), key=lambda x: (x[1]['var_name'], x[1]['used_in'], x[1]['line'])): + pointer_str = "*" if info['var_is_pointer'] else "" + var_type_pointer = f"{info['var_type']}{pointer_str}" + logging.info(f" - {var_type_pointer:<15.15} {info['var_name']:<30} (line {info['line']:<4} in {info['used_in'][:20]:<20}) [{var_type_pointer:<25.25}] (defined in {info['defined_in']})") + + logging.info("") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def print_prototypes(functions_dict): + """ + Print the function prototypes and their source files. + + Args: + functions_dict (dict): Dictionary of function prototypes, with function names as keys and tuples (prototype, file_path) as values. + """ + try: + if not functions_dict: + logging.info("\tNo functions found.") + return + + logging.info("") + logging.info("--- Function Prototypes ---") + for key, value in functions_dict.items(): + func_name, params = key + prototype, file_name, bare_func_name = value + logging.info(f"{file_name:<25} {bare_func_name:<30} {prototype}") + + logging.info("") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + + +#------------------------------------------------------------------------------------------------------ +def print_class_instances(class_instances): + """Print the dictionary of class instances.""" + try: + if not any(vars_list for vars_list in class_instances.values()): + return + + logging.info("") + logging.info("--- Class Instances ---") + for file_path, class_list in class_instances.items(): + if class_list: # Only print for files that have classes + for class_type, instance_name, constructor_args, fbase in class_list: + parentacedConstructor = "("+constructor_args+")" + logging.info(f" {class_type:<25} {instance_name:<25} {parentacedConstructor:<15} (in {fbase})") + + logging.info("") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"A\tn error occurred at line {line_number}: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def print_includes(includes): + """ + Prints the list of include statements. + + Args: + includes (list): List of include statements to print. + """ + try: + if len(includes) == 0: + return + + logging.info("") + logging.info("--- Include Statements ---") + for include in includes: + logging.info(f" {include}") + logging.info("") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"A\tn error occurred at line {line_number}: {str(e)}") + exit() + + + +#------------------------------------------------------------------------------------------------------ +def print_struct_definitions(struct_definitions): + if struct_definitions: + logging.info(f"\tStruct definitions found:") + for struct_name, struct_body in struct_definitions.items(): + logging.info(f"\t\t{struct_name}:") + for line in struct_body.split('\n'): + logging.info(f"\t\t\t{line.strip()}") + else: + logging.info(f"\tNo struct definitions found in the file") + +#------------------------------------------------------------------------------------------------------ +def list_files_in_directory(directory_path): + """ + List and print all files in the specified directory. + + Args: + directory_path (str): The path to the directory to list files from. + """ + try: + # Get the list of all entries in the directory + entries = os.listdir(directory_path) + + # Filter out directories, keep only files + files = [entry for entry in entries if os.path.isfile(os.path.join(directory_path, entry))] + + #marker_index = directory_path.find(platformio_marker) + #if marker_index != -1: + # part_of_path = directory_path[marker_index + len(platformio_marker):] + # logging.info(f"Files in directory '{part_of_path}':") + #else: + # logging.info(f"Files in the directory '{directory_path}':") + logging.info(" ") + logging.info(f"Files in the directory '{short_path(directory_path)}':") + + if files: + for file in files: + logging.info(f"\t> {file}") + else: + logging.info("\tNo files found in this directory.") + + except FileNotFoundError: + logging.error(f"\tError: Directory '{short_path(directory_path)}' not found.") + except PermissionError: + logging.error(f"\tError: Permission denied to access directory '{short_path(directory_path)}'.") + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred in {fname} at line {line_number}: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def rename_file(old_name, new_name): + logging.info("") + logging.info(f"rename_file(): {short_path(old_name)} -> {short_path(new_name)}") + + # Check if the paths exist + if not os.path.exists(old_name): + logging.info(f"\tThe file {short_path(old_name)} does not exist") + return + + try: + os.rename(old_name, new_name) + logging.debug(f"\tFile renamed successfully from [{os.path.basename(old_name)}] to [{os.path.basename(new_name)}]") + except FileNotFoundError: + logging.info(f"\tThe file {short_path(old_name)} does not exist") + except PermissionError: + logging.info(f"\tYou don't have permission to rename this file [{short_path(old_name)}]") + except FileExistsError: + logging.info(f"\tA file with the name {short_path(new_name)} already exists") + except Exception as e: + logging.info(f"\tAn error occurred: {str(e)}") + +#------------------------------------------------------------------------------------------------------ +def remove_pio_tree(preserve_file): + logging.info("") + logging.info(f"remove_pio_tree(): {short_path(glob_pio_folder)}, project:[{glob_project_name}], preserve:[{preserve_file}]") + # Construct the full path + #full_path = os.path.join(glob_pio_folder, glob_project_name) + + # Check if the paths exist + if not os.path.exists(glob_pio_folder): + logging.error(f"\tError: The base path '{short_path(glob_pio_folder)}' does not exist.") + return + #if not os.path.exists(full_path): + # logging.error(f"Error: The full path '{short_path(full_path)}' does not exist.") + # return + + # Get the full path of the file to preserve + preserve_file_path = os.path.join(glob_pio_project_folder, preserve_file) + #logging.info(f"\t>>>> Preserve [{short_path(preserve_file_path)}]") + + # Check if the preserve_file exists and read its contents + preserve_file_contents = None + if os.path.exists(preserve_file_path): + with open(preserve_file_path, 'r') as f: + preserve_file_contents = f.read() + + try: + # Remove all contents of the last folder + for root, dirs, files in os.walk(glob_pio_folder, topdown=False): + for name in files: + if name == preserve_file: + logging.info(f"\tDONT REMOVE: [{short_path(preserve_file_path)}]") + else: + #logging.info(f"\tRemoving file: [{name}]") + os.remove(os.path.join(root, name)) + for name in dirs: + this_dir = os.path.join(root, name) + #logging.info(f"Removing dir: [{this_dir}]") + if len(os.listdir(this_dir)) != 0: + logging.info(f"\tRemoving dir: [{this_dir}] NOT EMPTY") + #if os.path.exists(this_dir): + else: + #logging.info(f"\tRemoving dir: [{name}]") + os.rmdir(os.path.join(root, name)) + + list_files_in_directory(glob_pio_folder) + # Remove all other contents in the base directory except the preserve_file + for item in os.listdir(glob_pio_folder): + item_path = os.path.join(glob_pio_folder, item) + #if item != preserve_file and os.path.isfile(item_path): + if os.path.isfile(item_path): + logging.info(f"\tremove: {item_path}") + os.remove(item_path) + #elif item != last_folder and os.path.isdir(item_path): + elif item != glob_pio_folder and os.path.isdir(item_path): + logging.info(f"\tRemove tree: {item_path}") + #shutil.rmtree(item_path) + + # Restore or create the preserve_file with its original contents + with open(preserve_file_path, 'w') as f: + if preserve_file_contents is not None: + f.write(preserve_file_contents) + + logging.info(f"\tSuccessfully removed all contents in [{short_path(glob_pio_folder)}]") + logging.info(f"\tand all other contents in [{short_path(glob_pio_folder)}] except [{preserve_file}]") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + #fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}:\n {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def recreate_pio_folders(): + """Create or recreate PlatformIO folder structure.""" + + logging.info("") + logging.info("Processing: recreat_pio_folders()") + + # Ensure the main PlatformIO folder exists + if not os.path.exists(glob_pio_folder): + os.makedirs(glob_pio_folder) + logging.debug(f"\tCreated PlatformIO folder: {short_path(glob_pio_folder)}") + + # Get the current working directory + current_dir = os.getcwd() + logging.debug(f"\tCurrent working directory: {current_dir}") + + # Construct the full base path + full_base_path = os.path.join(current_dir, glob_pio_folder, glob_project_name) + logging.debug(f"\t Full base path: {full_base_path}") + logging.debug(f"\t glob_pio_src: {glob_pio_src}") + logging.debug(f"\tglob_pio_include: {glob_pio_include}") + + # Recreate src and include folders + for folder in [glob_pio_src, glob_pio_include]: + if os.path.exists(folder): + shutil.rmtree(folder) + logging.debug(f"\tmakedirs [{folder}]") + os.makedirs(folder) + logging.debug(f"\tRecreated folder: [{folder}]") + + logging.info("\tPlatformIO folder structure recreated") + +#------------------------------------------------------------------------------------------------------ +def insert_include_in_header(header_lines, inserts): + """ + Insert #include statements in the header file. + """ + logging("") + logging.info("Processing: insert_include_in_header() ..") + + includes_to_add = [] + includes_added = set() + + # Process inserts + for item in inserts: + class_name = item[0] + logging.info(f"\t\tChecking if {class_name} ...") + + # Check if the class is in dict_singleton_classes + singleton_header = None + for header, classes in dict_singleton_classes.items(): + logging.info(f"\t\tChecking if {class_name} is in {header}") + if class_name in classes or class_name == header: + singleton_header = header + break + + if singleton_header: + if singleton_header not in includes_added: + includes_to_add.append(f'#include <{singleton_header}>\t\t//-- singleton') + includes_added.add(singleton_header) + logging.debug(f"\t\tAdding #{class_name} via <{singleton_header}>") + elif class_name.endswith('.h'): + if class_name not in includes_added: + includes_to_add.append(f'#include <{class_name}>') + includes_added.add(class_name) + logging.debug(f"\t\tAdding <{class_name}>") + else: + if class_name not in includes_added: + includes_to_add.append(f'#include <{class_name}.h>') + includes_added.add(class_name) + logging.debug(f"\t\tAdding <{class_name}.h>") + + # Find the position to insert the new includes + insert_position = 0 + for i, line in enumerate(header_lines): + if line.strip().startswith('#include'): + insert_position = i + 1 + elif not line.strip().startswith('#') and line.strip() != '': + break + + # Insert the new includes + for include in includes_to_add: + header_lines.insert(insert_position, include + '\n') + insert_position += 1 + + return header_lines + +#------------------------------------------------------------------------------------------------------ +def insert_method_include_in_header(header_file, include_statement): + logging.info("") + logging.info("Processing: insert_method_include_in_header() ..") + try: + with open(header_file, 'r') as file: + content = file.readlines() + + # Find the appropriate position to insert the include statement + convertor_marker_position = -1 + first_comment_end = -1 + + # Extract the library name from the include statement + library_name = re.search(r'#include\s*<(.+)>', include_statement) + if not library_name: + logging.error(f"Invalid include statement: {include_statement}") + return + + library_name = library_name.group(1) + + for i, line in enumerate(content): + stripped_line = line.strip() + + if stripped_line == convertor_marker: + convertor_marker_position = i + break + + if first_comment_end == -1 and (stripped_line.endswith('//') or stripped_line.endswith('*/')): + first_comment_end = i + + # If convertor_marker is not found, add it after the first comment + if convertor_marker_position == -1 and first_comment_end != -1: + content.insert(first_comment_end + 1, f"{convertor_marker}\n") + convertor_marker_position = first_comment_end + 1 + + # Always insert the include statement after the convertor_marker + insert_position = convertor_marker_position + 1 if convertor_marker_position != -1 else 0 + + # Check if the include statement already exists, ignoring comments + include_exists = any( + re.search(rf'#include\s*<{re.escape(library_name)}>\s*(//.*)?$', line.strip()) + for line in content + ) + + if not include_exists: + content.insert(insert_position, f"{include_statement}\t\t//-- added by instance.method()\n") + logging.debug(f"\tInserted include statement in {short_path(header_file)}: {include_statement}") + else: + logging.debug(f"\tInclude statement already exists in {short_path(header_file)}: {include_statement}") + + with open(header_file, 'w') as file: + file.writelines(content) + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}:\n {str(e)}") + exit() + + +#------------------------------------------------------------------------------------------------------ +def copy_data_folder(): + """ + Delete existing data folder in glob_pio_folder if it exists, + then copy the data folder from the project folder to the PlatformIO folder if it exists. + """ + logging.info("") + logging.info("Processing: copy_data_folder()") + + source_data_folder = os.path.join(glob_ino_project_folder, 'data') + destination_data_folder = os.path.join(glob_pio_folder, glob_project_name, 'data') + + # Delete existing data folder in glob_pio_folder if it exists + if os.path.exists(destination_data_folder): + try: + shutil.rmtree(destination_data_folder) + logging.debug(f"\tDeleted existing data folder in {short_path(glob_pio_folder)}") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + logging.error(f"\tError deleting existing data folder: {str(e)}") + return # Exit the function if we can't delete the existing folder + + # Copy data folder from project folder to glob_pio_folder if it exists + if os.path.exists(source_data_folder): + try: + logging.debug("\tCopy data folder ") + logging.debug(f"\t>> from: {short_path(source_data_folder)}") + logging.debug(f"\t>> to: {short_path(destination_data_folder)}") + shutil.copytree(source_data_folder, destination_data_folder) + logging.debug(f"\tCopied data folder from {short_path(source_data_folder)} to {short_path(destination_data_folder)}") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + #fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + logging.error(f"\tError deleting existing data folder: {str(e)}") + logging.error(f"\tError copying data folder: {str(e)}") + else: + logging.info("\tNo data folder found in the project folder") + +#------------------------------------------------------------------------------------------------------ +def create_platformio_ini(): + """Create a platformio.ini file if it doesn't exist.""" + logging.info("") + logging.info(f"Processing: create _platformio_ini() if it doesn't exist in [{short_path(glob_pio_project_folder)}]") + + platformio_ini_path = os.path.join(glob_pio_project_folder, 'platformio.ini') + if not os.path.exists(platformio_ini_path): + platformio_ini_content = """ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +workspace_dir = .pio.nosync +default_envs = myBoard + +[env:myBoard] +;-- esp32 +#platform = espressif32 +#board = esp32dev +#framework = arduino +#board_build.partitions = +#board_build.filesystem = | +#monitor_speed = 115200 +#upload_speed = 115200 +#build_flags = +#\t-D DEBUG +# +#lib_ldf_mode = deep+ +# +#lib_deps = +;\t Libraries +#monitor_filters = +# esp8266_exception_decoder + +;-- attiny85 +#platform = atmelavr +#board = attiny85 +#framework = arduino +#upload_protocol = usbtiny +#upload_speed = 19200 +;-- Clock source Int.RC Osc. 8MHz PWRDWN/RESET: 6 CK/1 +#board_fuses.lfuse = 0xE2 +;-- Serial program downloading (SPI) enabled +;-- brown-out Detection 1.8v (0xDE) +;board_fuses.hfuse = 0xDE +;-- brown-out detection 2.7v (0xDD) +#board_fuses.hfuse = 0xDD +;-- brown-out detection 4.3v (0xDC) +;board_fuses.hfuse = 0xDC +#board_fuses.efuse = 0xFF + +#framework = arduino +#board_build.filesystem = LittleFS +#monitor_speed = 115200 +#upload_speed = 115200 +#upload_port = Libraries +""" + with open(platformio_ini_path, 'w') as f: + f.write(platformio_ini_content) + logging.info(f"\tCreated platformio.ini file at {short_path(platformio_ini_path)}") + + else: + logging.info(f"\tplatformio.ini file already exists at [{short_path(platformio_ini_path)}]") + + +#------------------------------------------------------------------------------------------------------ +def move_struct_union_and_enum_declarations(): + logging.info("") + logging.info(f"Processing: move_struct_union_and_enum_declarations() ") + + global struct_union_and_enum_added + + search_folders = [glob_pio_src, glob_pio_include] + + def find_declaration_end(content, start_pos): + bracket_count = 0 + for i, char in enumerate(content[start_pos:]): + if char == '{': + bracket_count += 1 + elif char == '}': + bracket_count -= 1 + if bracket_count == 0: + # Look for the semicolon after the closing brace + semicolon_pos = content.find(';', start_pos + i) + if semicolon_pos != -1: + return semicolon_pos + 1 + return start_pos + i + 1 + return -1 + + def is_in_comment(content, pos): + # Check for single-line comment + line_start = content.rfind('\n', 0, pos) + 1 + if '//' in content[line_start:pos]: + return True + + # Check for multi-line comment + last_comment_start = content.rfind('/*', 0, pos) + if last_comment_start != -1: + comment_end = content.find('*/', last_comment_start) + if comment_end == -1 or comment_end > pos: + return True + + return False + + for folder in search_folders: + for root, _, files in os.walk(folder): + for file in files: + #??#if file.endswith(('.h', '.ino', '.cpp')) and not file.startswith('arduinoGlue'): + if file.endswith(('.h', '.ino')) and not file.startswith('arduinoGlue'): + file_path = os.path.join(root, file) + logging.debug(f"\tProcessing file: {short_path(file_path)}") + + try: + with open(file_path, 'r') as file: + content = file.read() + + # Updated regular expression to match struct, union, and enum declarations, including 'typedef struct' + declaration_pattern = r'\b(typedef\s+)?(struct|union|enum)\s+(?:\w+\s+)*(?:\w+\s*)?{' + + modified_content = content + declarations_to_move = [] + + for match in re.finditer(declaration_pattern, content): + start_pos = match.start() + + # Skip if the declaration is inside a comment + if is_in_comment(content, start_pos): + continue + + end_pos = find_declaration_end(content, start_pos) + + if end_pos != -1: + decl_type = match.group(2) # 'struct', 'union', or 'enum' + decl = content[start_pos:end_pos] + + # Check if the declaration is globally defined (not inside a function) + preceding_content = content[:start_pos] + brace_level = preceding_content.count('{') - preceding_content.count('}') + + if brace_level == 0: # Declaration is globally defined + # Prepare the declaration for arduinoGlue.h + arduinoGlue_decl = f"//-- from {os.path.basename(file_path)}\n{decl}" + declarations_to_move.append(arduinoGlue_decl) + + # Comment out the declaration in the original file + comment_text = f"*** {decl_type} moved to arduinoGlue.h ***" + commented_decl = f"/*\t\t\t\t{comment_text}\n{decl}\n*/" + modified_content = modified_content.replace(content[start_pos:end_pos], commented_decl) + struct_union_and_enum_added = True + + # Write modified content back to the original file (File Under Test) + with open(file_path, 'w') as file: + file.write(modified_content) + + # Insert declarations into arduinoGlue.h at the correct position + if declarations_to_move: + arduinoGlue_path = os.path.join(glob_pio_include, 'arduinoGlue.h') + with open(arduinoGlue_path, 'r+') as file: + arduinoGlue_content = file.read() + + # Find the correct insertion point + header_guard_match = re.search(r'#ifndef\s+\w+\s+#define\s+\w+', arduinoGlue_content) + if header_guard_match: + header_guard_end = header_guard_match.end() + # Find the struct_union_and_enum_marker after the header guard + struct_union_and_enum_marker_pos = arduinoGlue_content.rfind(f"{struct_union_and_enum_marker}", header_guard_end) + logging.info(f"\t\tstruct_union_and_enum_marker_pos: {struct_union_and_enum_marker_pos}") + if struct_union_and_enum_marker_pos != -1: + insert_point = arduinoGlue_content.find('\n', struct_union_and_enum_marker_pos) + 0 + else: + # If no #define found, insert after header guard + insert_point = arduinoGlue_content.find('\n', header_guard_end) + 1 + else: + # If no header guard found, insert at the beginning + insert_point = 0 + logging.info(f"\t\tinsert_point: {insert_point}") + + # Ensure there's an empty line before the declarations and one after each declaration + new_content = arduinoGlue_content[:insert_point] + '\n' + new_content += '\n'.join(decl + '\n' for decl in declarations_to_move) + new_content += arduinoGlue_content[insert_point:] + + # Write the updated content back to arduinoGlue.h + file.seek(0) + file.write(new_content) + file.truncate() + + logging.info(f"\tMoved {len(declarations_to_move)} struct/union/enum declaration(s) from [{os.path.basename(file_path)}] to arduinoGlue.h") + else: + logging.info(f"\tNo global struct/union/enum declarations found in [{os.path.basename(file_path)}]") + + except FileNotFoundError: + logging.error(f"Error: File {file_path} not found.") + except IOError: + logging.error(f"Error: Unable to read or write file {file_path}.") + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + + +""" +#------------------------------------------------------------------------------------------------------ +def extract_and_comment_defines(): + "" " + Extract all #define statements (including functional and multi-line) from .h, .ino, and .cpp files, + create arduinoGlue.h, and comment original statements with info. + "" " + logging.info("") + logging.info(f"Searching for #define statements in {short_path(glob_pio_folder)}") + + try: + all_defines = [] + define_pattern = r'^\s*#define\s+(\w+)(?:\(.*?\))?\s*(.*?)(?:(?=\\\n)|$)' + + # Only search within glob_pio_src and glob_pio_include folders + search_folders = [glob_pio_src, glob_pio_include] + + for folder in search_folders: + for root, _, files in os.walk(folder): + for file in files: + if file.endswith(('.h', '.ino')): + file_path = os.path.join(root, file) + logging.debug(f"\tProcessing file: {short_path(file_path)}") + with open(file_path, 'r') as f: + content = f.read() + + new_content = [] + lines = content.split('\n') + i = 0 + while i < len(lines): + line = lines[i] + match = re.match(define_pattern, line) + if match: + macro_name = match.group(1) + macro_value = match.group(2) + full_define = [line] + + # Check for multi-line defines + while macro_value.endswith('\\') and i + 1 < len(lines): + i += 1 + next_line = lines[i] + full_define.append(next_line) + macro_value += '\n' + next_line.strip() + + # Add the closing line if it's not already included + if i + 1 < len(lines) and not macro_value.endswith('\\'): + i += 1 + closing_line = lines[i] + if closing_line.strip().startswith(')'): + full_define.append(closing_line) + macro_value += '\n' + closing_line.strip() + else: + i -= 1 # If it's not a closing parenthesis, go back one line + + # Don't include header guards + if not macro_name.endswith('_H'): + all_defines.append((macro_name, '\n'.join(full_define))) + # Comment out the original #define with info + new_content.extend([f"\t//-- moved to arduinoGlue.h // {line}" for line in full_define]) + logging.debug(f"\tAdded #define: {macro_name}") + else: + new_content.extend(full_define) + else: + new_content.append(line) + i += 1 + + # Write the modified content back to the file + with open(file_path, 'w') as f: + f.write('\n'.join(new_content)) + logging.debug(f"\tUpdated {file} with commented out #defines") + + # Create arduinoGlue.h with all macros + all_defines_path = os.path.join(glob_pio_include, 'arduinoGlue.h') + logging.info(f"\tCreating arduinoGlue.h with {len(all_defines)} macros") + with open(all_defines_path, 'w') as f: + f.write("#ifndef ARDUINOGLUE_H\n#define ARDUINOGLUE_H\n\n") + for macro_name, macro_value in all_defines: + f.write(f"{macro_value}\n") + f.write(f"\n{all_includes_marker}") + f.write(f"\n{struct_union_and_enum_marker}") + f.write(f"\n\n{extern_variables_marker}") + f.write(f"\n{global_pointer_arrays_marker}") + f.write(f"\n{extern_classes_marker}") + f.write(f"\n{prototypes_marker}") + f.write(f"\n{convertor_marker}") + f.write("\n#endif // ARDUINOGLUE_H\n") + + logging.info(f"\tSuccessfully created {short_path(all_defines_path)}") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + #fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + logging.error(f"\tError creating arduinoGlue.h: {str(e)}") + exit() + + logging.info(f"\tExtracted {len(all_defines)} #define statements") +""" +#------------------------------------------------------------------------------------------------------ +#------------------------------------------------------------------------------------------------------ +def extract_and_comment_defines(): + """ + Extract all #define statements (including functional and multi-line) from .h, .ino, and .cpp files, + comment original statements with info, and insert them into arduinoGlue.h after the all_defines_marker. + """ + logging.info("") + logging.info(f"Searching for #define statements in {short_path(glob_pio_folder)}") + + try: + all_defines = [] + define_pattern = r'^\s*#define\s+(\w+)(?:\(.*?\))?\s*(.*?)(?:(?=\\\n)|$)' + + # Only search within glob_pio_src and glob_pio_include folders + search_folders = [glob_pio_src, glob_pio_include] + + for folder in search_folders: + for root, _, files in os.walk(folder): + for file in files: + if file.endswith(('.h', '.ino')): + file_path = os.path.join(root, file) + logging.debug(f"\tProcessing file: {short_path(file_path)}") + with open(file_path, 'r') as f: + content = f.read() + + new_content = [] + lines = content.split('\n') + i = 0 + while i < len(lines): + line = lines[i] + match = re.match(define_pattern, line) + if match: + macro_name = match.group(1) + macro_value = match.group(2) + full_define = [line] + + # Check for multi-line defines + while macro_value.endswith('\\') and i + 1 < len(lines): + i += 1 + next_line = lines[i] + full_define.append(next_line) + macro_value += '\n' + next_line.strip() + + # Add the closing line if it's not already included + if i + 1 < len(lines) and not macro_value.endswith('\\'): + i += 1 + closing_line = lines[i] + if closing_line.strip().startswith(')'): + full_define.append(closing_line) + macro_value += '\n' + closing_line.strip() + else: + i -= 1 # If it's not a closing parenthesis, go back one line + + # Don't include header guards + if not macro_name.endswith('_H'): + all_defines.append('\n'.join(full_define)) + # Comment out the original #define with info + new_content.extend([f"\t//-- moved to arduinoGlue.h // {line}" for line in full_define]) + logging.debug(f"\tAdded #define: {macro_name}") + else: + new_content.extend(full_define) + else: + new_content.append(line) + i += 1 + + # Write the modified content back to the file + with open(file_path, 'w') as f: + f.write('\n'.join(new_content)) + logging.debug(f"\tUpdated {file} with commented out #defines") + + # Insert all defines into arduinoGlue.h after the all_defines_marker + all_defines_path = os.path.join(glob_pio_include, 'arduinoGlue.h') + with open(all_defines_path, 'r') as f: + content = f.read() + + marker_index = content.find(all_defines_marker) + if marker_index != -1: + new_content = (content[:marker_index + len(all_defines_marker)] + + '\n' + '\n'.join(all_defines) + + content[marker_index + len(all_defines_marker):]) + + with open(all_defines_path, 'w') as f: + f.write(new_content) + + logging.info(f"\tInserted {len(all_defines)} #define statements into {short_path(all_defines_path)}") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + logging.error(f"\tError extracting and inserting #define statements: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def add_markers_to_header_file(file_path): + logging.info("") + logging.info(f"Processing: add_markers_to_header_file() from: {short_path(file_path)}") + + try: + with open(file_path, 'r') as file: + content = file.read() + + markers = [ + convertor_marker, + all_includes_marker, + ] + + lines = content.split('\n') + header_guard_start = -1 + comment_end = -1 + + # Find opening header guard + for i, line in enumerate(lines): + if line.strip().startswith('#ifndef') or line.strip().startswith('#define'): + header_guard_start = i + break + + # Find end of first comment block + in_multiline_comment = False + for i, line in enumerate(lines): + stripped_line = line.strip() + if stripped_line.startswith('//'): + comment_end = i + elif stripped_line.startswith('/*'): + in_multiline_comment = True + elif stripped_line.endswith('*/') and in_multiline_comment: + comment_end = i + in_multiline_comment = False + elif not in_multiline_comment and stripped_line and comment_end != -1: + break + + # Determine insertion point + if header_guard_start != -1: + insertion_point = header_guard_start + 2 # After both #ifndef and #define + elif comment_end != -1: + insertion_point = comment_end + 1 + else: + insertion_point = 0 + + # Insert markers + for marker in markers: + if marker not in content: + lines.insert(insertion_point, marker) + insertion_point += 1 + if insertion_point < len(lines): + lines.insert(insertion_point, '') + insertion_point += 1 + + # Join lines back into content + modified_content = '\n'.join(lines) + + with open(file_path, 'w') as file: + file.write(modified_content) + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + logging.error(f"\tError modifying {file_path}: {str(e)}") + exit() + + +#------------------------------------------------------------------------------------------------------ +def create_new_header_file(ino_name, header_name): + """Create new header file with basic structure.""" + logging.info("") + logging.info(f"Processing: create_new_header_file(): {header_name} for [{ino_name}]") + + header_path = os.path.join(glob_pio_include, f"{header_name}") + + try: + if os.path.exists(header_path): + logging.info(f"\tHeader file already exists: {header_name}") + add_markers_to_header_file(header_path) + return + + base_name = os.path.splitext(header_name)[0] + header_path = os.path.join(glob_pio_include, f"{header_name}") + with open(header_path, 'w') as f: + f.write(f"#ifndef {base_name.upper()}_H\n") + f.write(f"#define {base_name.upper()}_H\n\n") + f.write(f"{all_includes_marker}") + f.write("\n") + f.write("#include \"arduinoGlue.h\"\n\n") + f.write(f"{convertor_marker}") + f.write("\n") + f.write(f"#endif // {base_name.upper()}_H\n") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + logging.error(f"\tError creating arduinoGlue.h: {str(e)}") + exit() + + logging.info(f"\tCreated new header file: {header_name}") + + +#------------------------------------------------------------------------------------------------------ +def process_original_header_file(header_path, base_name): + logging.info(f"Processing: process_original_header_file(): [{base_name}]") + + try: + if os.path.exists(header_path): + with open(header_path, 'r') as f: + original_content = f.read() + + # Check for existing header guards + guard_match = re.search(r'#ifndef\s+(\w+_H).*?#define\s+\1', original_content, re.DOTALL) + has_guards = guard_match is not None + + new_content = original_content + + if not has_guards: + # Add header guards if they don't exist + logging.debug(f"\tAdding header guards for {base_name}") + guard_name = f"{base_name.upper()}_H" + new_content = f"#ifndef {guard_name}\n#define {guard_name}\n\n{new_content}\n#endif // {guard_name}\n" + else: + guard_name = guard_match.group(1) + + # Find the position to insert the all_includes_marker + if all_includes_marker not in new_content: + logging.debug(f"\tAdding {all_includes_marker} to {base_name}") + # Find the first closing comment after the header guard + comment_end = new_content.find('*/', new_content.find(guard_name)) + 2 + if comment_end > 1: # If a closing comment was found + insert_pos = comment_end + else: # If no closing comment, insert after the header guard + insert_pos = new_content.find('\n', new_content.find(guard_name)) + 1 + + new_content = new_content[:insert_pos] + f"\n{all_includes_marker}\n" + new_content[insert_pos:] + + # Check if arduinoGlue.h is already included + if '#include "arduinoGlue.h"' not in new_content: + logging.debug(f"\tAdding arduinoGlue.h include to {base_name}") + # Insert after the convertor_markerloca + insert_pos = new_content.find('\n', new_content.find(all_includes_marker)) + 1 + new_content = new_content[:insert_pos] + '#include "arduinoGlue.h"\n\n' + new_content[insert_pos:] + + # Only write to the file if changes were made + if new_content != original_content: + with open(header_path, 'w') as f: + f.write(new_content) + logging.debug(f"\tUpdated original header file: {short_path(header_path)}") + else: + logging.debug(f"\tNo changes needed for: {short_path(header_path)}") + else: + logging.debug(f"\tFile not found: {short_path(header_path)}") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + + return header_path + + +#------------------------------------------------------------------------------------------------------ +def add_local_includes_to_project_header(): + """ + Adds all necessary includes to the project header file. + + This function reads the content of the project header file, identifies existing includes, + and adds new includes that are not already present. It then writes the modified content + back to the header file. + """ + logging.info(""); + logging.info(f"Processing: add_local_includes_to_project_header(): {glob_project_name}") + + try: + project_header = os.path.join(glob_pio_include, f"{glob_project_name}.h") + # Read the content of the source file + with open(project_header, 'r') as file: + content = file.read() + + # Remove multi-line comments + content_without_multiline_comments = re.sub(r'/\*[\s\S]*?\*/', '', content) + + # Remove single-line comments + content_without_comments = re.sub(r'//.*$', '', content_without_multiline_comments, flags=re.MULTILINE) + + # Find all includes that are not commented out + existing_includes = set(re.findall(r'#include\s*[<"]([^>"]+)[>"]', content_without_comments)) + + # Prepare new includes + list_files_in_directory(glob_pio_include) + new_includes = [] + for file_name in os.listdir(glob_pio_include): + logging.debug(f"\tProcessing file: {file_name}") + header_name = os.path.basename(file_name) # Get the basename + if header_name == os.path.basename(project_header): + logging.info(f"Don't ever include {header_name} into {os.path.basename(project_header)}") + elif header_name not in existing_includes: + new_includes.append(f'#include "{header_name}"') + + if not new_includes: + return # No new includes to add + + logging.debug(f"\tFound {len(new_includes)} new includes: {', '.join(new_includes)}") + + # Find the insertion point + marker = all_includes_marker + insertion_point = content.find(marker) + if insertion_point == -1: + marker = convertor_marker + insertion_point = content.find(marker) + if insertion_point == -1: + # If neither marker is found, find the end of the header guard + marker = "" + header_guard_end = re.search(r'#define\s+\w+_H\s*\n', content) + if header_guard_end: + insertion_point = header_guard_end.end() + else: + logging.info("\tCannot find suitable insertion point") + return + + insertion_point += len(marker) + # Insert new includes + before = content[:insertion_point] + after = content[insertion_point:] + new_content = before + '\n' + '\n'.join(new_includes) + '\n' + after + + # Write the modified content back to the header file + with open(project_header, 'w') as file: + file.write(new_content) + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def copy_project_files(): + """Copy .ino, .cpp, .c files to glob_pio_src and .h files to glob_pio_include.""" + logging.info("") + logging.info("Processing: copy_project_files() ..") + + try: + # Remove the arduinoGlue.h file + # Check if the file exists + arduinoGlue_path = os.path.join(glob_pio_include, "arduinoGlue.h") + if os.path.exists(arduinoGlue_path): + # Delete the file + os.remove(arduinoGlue_path) + logging.info("\t'arduinoGlue.h' has been deleted.") + else: + logging.info("\t'arduinoGlue.h' does not (yet) exist.") + + for file in os.listdir(glob_ino_project_folder): + if file.endswith('.ino'): + logging.debug(f"\tCopy [{file}] ..") + shutil.copy2(os.path.join(glob_ino_project_folder, file), glob_pio_src) + elif file.endswith('.cpp'): + logging.debug(f"\tCopy [{file}] ..") + shutil.copy2(os.path.join(glob_ino_project_folder, file), glob_pio_src) + elif file.endswith('.c'): + logging.debug(f"\tCopy [{file}] ..") + shutil.copy2(os.path.join(glob_ino_project_folder, file), glob_pio_src) + elif file.endswith('.h'): + logging.debug(f"\tCopy [{file}] ..") + shutil.copy2(os.path.join(glob_ino_project_folder, file), glob_pio_include) + logging.info(f"\tProcessing original header file: {file}") + base_name = os.path.splitext(file)[0] + header_path = os.path.join(glob_pio_include, f"{base_name}.h") + process_original_header_file(header_path, base_name) + + if args.debug: + list_files_in_directory(glob_pio_src) + list_files_in_directory(glob_pio_include) + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + + logging.debug("\tCopied project files to PlatformIO folders") + +#------------------------------------------------------------------------------------------------------ +def extract_all_includes_from_file(file_path): + """ + Scans the file for (not commented out) "#include <..>" statements. + Removes comments behind the "#include <..>" statement (if any) and adds the statement to an "includes" array. + Adds a comment after the original include statement and modifies the file directly. + + Args: + file_path (str): Path to the file to be processed. + + Returns: + list: List of extracted include statements + """ + includes = [] + + try: + with open(file_path, 'r') as file: + lines = file.readlines() + + modified_lines = [] + for line in lines: + stripped_line = line.strip() + if stripped_line.startswith("#include <"): + include_match = re.match(r'(#include\s*<[^>]+>|#include\s*"[^"]+")', stripped_line) + if include_match: + include_statement = include_match.group(1) + includes.append(include_statement) + # Remove original comment and add the new comment + modified_line = f"//{include_statement:<50}\t\t//-- moved to arduinoGlue.h\n" + modified_lines.append(modified_line) + else: + modified_lines.append(line) + else: + modified_lines.append(line) + + # Write the modified content back to the file + with open(file_path, 'w') as file: + file.writelines(modified_lines) + + logging.info(f"Processed {os.path.basename(file_path)}") + logging.info(f"Found and modified {len(includes)} include statements") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + return [] + + return includes + + +#------------------------------------------------------------------------------------------------------ +def extract_global_variables(file_path): + """ + Extract global variable definitions from a single .ino, .cpp, or header file. + Only variables declared outside of all function blocks are considered global. + """ + logging.info("") + logging.info(f"Processing: extract_global_variables() from : {os.path.basename(file_path)}") + + global_vars = {} + + # Get the fbase (filename without extension) + file = os.path.basename(file_path) + fbase = os.path.splitext(file)[0] + + # Check if there are existing entries in dict_global_variables for this fbase + if fbase in dict_global_variables: + global_vars[fbase] = dict_global_variables[fbase] + logging.info(f"\t[1] Found {len(global_vars[fbase])} existing global variables for {fbase} in dict_global_variables") + + # More flexible type pattern to match any type, including custom types and structs + type_pattern = r'(?:\w+(?:::\w+)*(?:\s*<[^>]+>)?(?:\s*\*)*)' + + # Updated patterns to catch all types of variables and class instances, including static + var_pattern = rf'^\s*((?:static|volatile|const)?\s*{type_pattern})\s+((?:[a-zA-Z_]\w*(?:\[.*?\])?(?:\s*=\s*[^,;]+)?\s*,\s*)*[a-zA-Z_]\w*(?:\[.*?\])?(?:\s*=\s*[^,;]+)?)\s*;' + class_instance_pattern = rf'^\s*((?:static)?\s*{type_pattern})\s+([a-zA-Z_]\w*)(?:\s*\(.*?\))?\s*;' + func_pattern = rf'^\s*(?:static|volatile|const)?\s*{type_pattern}\s+([a-zA-Z_]\w*)\s*\((.*?)\)' + struct_pattern = r'^\s*struct\s+([a-zA-Z_]\w*)\s*{' + + keywords = set(['if', 'else', 'for', 'while', 'do', 'switch', 'case', 'default', + 'break', 'continue', 'return', 'goto', 'typedef', 'struct', 'enum', + 'union', 'sizeof', 'volatile', 'register', 'extern', 'inline', + 'static', 'const', 'auto', 'virtual', 'void', 'class', 'public', + 'private', 'protected', 'template', 'namespace', 'using', 'friend', + 'operator', 'try', 'catch', 'throw', 'new', 'delete']) + + control_structures = set(['if', 'else', 'for', 'while', 'do', 'switch', 'case']) + + def is_in_string(line, pos): + """Check if the given position in the line is inside a string literal.""" + in_single_quote = False + in_double_quote = False + escape = False + for i, char in enumerate(line): + if i >= pos: + return in_single_quote or in_double_quote + if escape: + escape = False + continue + if char == '\\': + escape = True + elif char == "'" and not in_double_quote: + in_single_quote = not in_single_quote + elif char == '"' and not in_single_quote: + in_double_quote = not in_double_quote + return False + + try: + with open(file_path, 'r') as f: + content = f.read() + + lines = content.split('\n') + scope_stack = [] + in_struct = False + current_struct = None + file_vars = [] + custom_types = set() + potential_func_start = False + func_parentheses_count = 0 + in_raw_string = False + raw_string_delimiter = '' + + for line_num, line in enumerate(lines, 1): + stripped_line = line.strip() + + # Skip empty lines and comments + if not stripped_line or stripped_line.startswith('//'): + continue + + # Check for raw string literal start + if not in_raw_string and 'R"' in stripped_line: + raw_start = stripped_line.index('R"') + if not is_in_string(stripped_line, raw_start): + in_raw_string = True + delimiter_end = stripped_line.index('(', raw_start) + raw_string_delimiter = stripped_line[raw_start+2:delimiter_end] + + # Check for raw string literal end + if in_raw_string: + end_delimiter = ')"' + raw_string_delimiter + if end_delimiter in stripped_line: + in_raw_string = False + raw_string_delimiter = '' + continue # Skip processing this line if we're in a raw string + + # Check for control structures + first_word = stripped_line.split()[0] if stripped_line else '' + if first_word in control_structures: + scope_stack.append('control') + + # Check for multi-line function declarations + if potential_func_start: + func_parentheses_count += stripped_line.count('(') - stripped_line.count(')') + if func_parentheses_count == 0: + if stripped_line.endswith('{'): + scope_stack.append('function') + potential_func_start = False + continue + + # Check for struct start + struct_match = re.search(struct_pattern, stripped_line) + if struct_match and not scope_stack: + in_struct = True + current_struct = struct_match.group(1) + custom_types.add(current_struct) + scope_stack.append('struct') + + # Check for function start + func_match = re.search(func_pattern, stripped_line) + if func_match and not scope_stack: + if stripped_line.endswith('{'): + scope_stack.append('function') + else: + potential_func_start = True + func_parentheses_count = stripped_line.count('(') - stripped_line.count(')') + + # Count braces + if not potential_func_start: + open_braces = stripped_line.count('{') + close_braces = stripped_line.count('}') + + for _ in range(open_braces): + if not scope_stack or scope_stack[-1] == 'brace': + scope_stack.append('brace') + + for _ in range(close_braces): + if scope_stack and scope_stack[-1] == 'brace': + scope_stack.pop() + elif scope_stack: + scope_stack.pop() + if not scope_stack: + in_struct = False + current_struct = None + + # Check for variable declarations only at global scope + if not scope_stack and not stripped_line.startswith('return'): + var_match = re.search(var_pattern, stripped_line) + class_instance_match = re.search(class_instance_pattern, stripped_line) + + if var_match and not is_in_string(line, var_match.start()): + var_type = var_match.group(1).strip() + var_declarations = re.findall(r'([a-zA-Z_]\w*(?:\[.*?\])?)(?:\s*=\s*[^,;]+)?', var_match.group(2)) + for var_name in var_declarations: + base_name = var_name.split('[')[0].strip() + if base_name.lower() not in keywords and not base_name.isdigit(): + # Check for pointer in var_type or var_name + is_pointer = var_type.endswith('*') or var_name.startswith('*') + + # Remove asterisk from var_name if it starts with one + if var_name.startswith('*'): + var_name = var_name.lstrip('*').strip() + if not var_type.endswith('*'): + var_type = var_type + '*' + + file_vars.append((var_type, var_name, None, is_pointer)) + logging.debug(f"\t[1] Global variable found: [{var_type} {var_name}]") + if is_pointer: + logging.debug(f"\t\t[1] Pointer variable detected: [{var_type} {var_name}]") + + elif class_instance_match and not is_in_string(line, class_instance_match.start()): + var_type = class_instance_match.group(1).strip() + var_name = class_instance_match.group(2).strip() + if var_name.lower() not in keywords and not var_name.isdigit(): + file_vars.append((var_type, var_name, None, False)) + logging.debug(f"\t[1] Global class instance found: [{var_type} {var_name}]") + + # Remove duplicate entries + unique_file_vars = list(set(file_vars)) + + # Add new global variables to the existing ones + if fbase in global_vars: + global_vars[fbase].extend(unique_file_vars) + else: + global_vars[fbase] = unique_file_vars + + if unique_file_vars: + logging.info(f"\t[1] Processed {os.path.basename(file_path)} successfully. Found {len(unique_file_vars)} new global variables.") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + logging.error(f"\tError processing file {file_path}: {str(e)}") + exit() + + if global_vars[fbase]: + logging.info(f"\t[1] Total global variables for {fbase}: {len(global_vars[fbase])}") + else: + logging.info("\t[1] No global variables found") + + return global_vars + +#------------------------------------------------------------------------------------------------------ +def extract_constant_pointers(file_path): + """ + Extract constant pointer array definitions with initializers from a single .ino, .cpp, or header file. + Only variables declared outside of all function blocks are considered. + """ + logging.info("") + logging.info(f"Processing: extract_constant_pointers() from : {os.path.basename(file_path)}") + + try: + global_vars = {} + + # Get the fbase (filename without extension) + file = os.path.basename(file_path) + fbase = os.path.splitext(file)[0] + + # Check if there are existing entries in dict_global_variables for this fbase + if fbase in dict_global_variables: + global_vars[fbase] = dict_global_variables[fbase] + logging.info(f"\t[2] Found {len(global_vars[fbase])} existing global variables for {fbase} in dict_global_variables") + + # Comprehensive list of object types, including String and dict_known_classes + basic_types = r'uint8_t|int8_t|uint16_t|int16_t|uint32_t|int32_t|uint64_t|int64_t|char|int|float|double|bool|boolean|long|short|unsigned|signed|size_t|void|String|time_t|struct tm' + + # Regular expression to match const char* or const int* declarations + pattern = rf'const\s+({basic_types})\s*\*\s*(\w+)\s*\[\]\s*{{' + r'\s*("[^"]*"\s*,\s*)*("[^"]*"\s*)\s*}|const\s+int\s*\*\s*(\w+)\s*{\s*\d+\s*(\s*,\s*\d+)*\s*};' + + file_vars = [] + + with open(file_path, 'r') as file: + content = file.read() + + # Remove comments + content = re.sub(r'//.*?\n|/\*.*?\*/', '', content, flags=re.DOTALL) + + # Split content by semicolon to handle each declaration separately + declarations = content.split(';') + + for declaration in declarations: + # Remove leading and trailing whitespace and check if it's not empty + declaration = declaration.strip() + if not declaration: + continue + match = re.match(pattern, declaration) + if match: + var_type = f"const {match.group(1)}*" + var_name = match.group(2) or match.group(5) # Group 2 for array, group 5 for single pointer + current_function = None + is_pointer = True + var_full_name = var_name +"[]" + file_vars.append((var_type, var_full_name, current_function, is_pointer)) + logging.info(f"\t[2] Constant pointer found: [{var_type} {var_full_name}]") + + # Remove duplicate entries + unique_file_vars = list(set(file_vars)) + + # Add new global variables to the existing ones + if fbase in global_vars: + global_vars[fbase].extend(unique_file_vars) + else: + global_vars[fbase] = unique_file_vars + + if unique_file_vars: + logging.info(f"\t[2] Processed {os.path.basename(file_path)} successfully. Found {len(unique_file_vars)} new constant pointers.") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + logging.error(f"\tError processing file {file_path}: {str(e)}") + + return global_vars + +#------------------------------------------------------------------------------------------------------ +def extract_undefined_vars_in_file(file_path): + """ + Extract all variables that are used in the file but not properly defined. + Check if the variable is in the global dict_global_variables array. + If it's in dict_global_variables with a different base-name, add it to the 'undefined_vars' array. + Variables not found in dict_global_variables or with unknown types are not added to the 'undefined_vars' array. + + Args: + file_path (str): Path to the Arduino file (.ino or .cpp) + + Returns: + dict: Dictionary of undefined variables used in the file, with their line numbers, file names, var_type, var_name, and defined file + """ + logging.info("===========================") + logging.info(f"Processing: extract_undefined_vars_in_file() for file: [{os.path.basename(file_path)}]") + + # List of C++ keywords and common Arduino types/functions to exclude + KEYWORDS_AND_TYPES = set([ + "if", "else", "for", "while", "do", "switch", "case", "default", "break", "continue", + "return", "goto", "try", "catch", "throw", "true", "false", "null", "const", "static", + "volatile", "unsigned", "signed", "void", "char", "short", "int", "long", "float", + "double", "bool", "String", "byte", "word", "boolean", "array", "sizeof", "setup", + "loop", "HIGH", "LOW", "INPUT", "OUTPUT", "INPUT_PULLUP", "LED_BUILTIN", "serial" + ]) + + try: + with open(file_path, 'r') as file: + content = file.read() + + # Remove comments + content = re.sub(r'//.*?\n|/\*.*?\*/', '', content, flags=re.DOTALL) + + undefined_vars = {} + + current_file_basename = os.path.splitext(os.path.basename(file_path))[0] + + # Find all variable declarations in the file + declarations = re.findall(r'\b(?:const\s+)?(?:unsigned\s+)?(?:static\s+)?(?:volatile\s+)?\w+\s+([a-zA-Z_]\w*)(?:\s*=|\s*;|\s*\[)', content) + declared_vars = {var for var in declarations if var not in KEYWORDS_AND_TYPES and not var.isdigit()} + logging.debug(f"Variables declared in file: {declared_vars}") + + # Find all variables used in the file + used_vars = re.findall(r'\b([a-zA-Z_]\w*)\b', content) + used_vars = [var for var in used_vars if var not in KEYWORDS_AND_TYPES and not var.isdigit()] + logging.debug(f"Variables used in file: {set(used_vars)}") + + # Identify potentially undefined variables + for var in set(used_vars) - declared_vars: + # Check if the variable is in dict_global_variables + var_found = False + var_type = 'Unknown' + defined_in = 'Undefined' + global_var_name = var + v_is_pointer = False + + for defined_file, file_vars in dict_global_variables.items(): + defined_file_basename = os.path.splitext(os.path.basename(defined_file))[0] + for v_type, v_name, is_pointer, _ in file_vars: + if v_name == var or (not '[' in var and re.match(rf'^{re.escape(var)}\[', v_name)): + var_found = True + var_type = v_type + defined_in = defined_file_basename + global_var_name = v_name # Store the name as found in dict_global_variables + v_is_pointer = is_pointer + if defined_file_basename == current_file_basename: + # Variable is defined in the same file, so it's not undefined + if args.debug: + logging.info(f"Variable {var} found in global variables of the same file") + break + if var_found: + break + + if var_found and defined_in != current_file_basename and var_type != 'Unknown': + # Find the first occurrence of the variable in the file + match = re.search(r'\b' + re.escape(var) + r'\b', content) + if match: + line_number = content[:match.start()].count('\n') + 1 + key = f"{global_var_name}+{current_file_basename}" + undefined_vars[key] = { + 'var_type': var_type, + 'var_is_pointer': v_is_pointer, + 'var_name': global_var_name, + 'used_in': current_file_basename, + 'defined_in': defined_in, + 'line': line_number + } + logging.debug(f"Added usage of {global_var_name} to undefined_vars: {undefined_vars[key]}") + + elif not var_found or var_type == 'Unknown': + logging.debug(f"Variable {var} not found in dict_global_variables or has unknown type, skipping") + + logging.debug(f"Final undefined_vars: {undefined_vars}") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"Error extract_undefined_vars_in_file at line {line_number}: {str(e)}") + exit() + + return undefined_vars + +#------------------------------------------------------------------------------------------------------ +def extract_prototypes(file_path): + """ + Extract function prototypes from a given file. + + Args: + file_path (str): Path to the file to be processed. + + Returns: + dict: Dictionary of function prototypes found in the file, with (function name, parameters) as keys and tuples (prototype, file_path, bare_function_name) as values. + """ + logging.info("") + logging.info(f"Processing: extract_prototypes() from file: [{os.path.basename(file_path)}]") + + prototypes = {} + + # Regex pattern for function header + pattern = r'^\s*(?:static\s+|inline\s+|virtual\s+|explicit\s+|constexpr\s+)*' \ + r'(?:const\s+)?' \ + r'(?:\w+(?:::\w+)*\s+)+' \ + r'[\*&]?\s*' \ + r'(\w+)\s*\(((?:[^()]|\([^()]*\))*)\)\s*{' + + try: + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + + # Remove comments and string literals + content = re.sub(r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', '', content, flags=re.DOTALL | re.MULTILINE) + + matches = re.finditer(pattern, content, re.MULTILINE) + + for match in matches: + func_name = match.group(1) + params = match.group(2) + + # Skip if the function name starts with "if", "else", "for", "while", etc. + if func_name.lower() in ['if', 'else', 'for', 'while', 'switch', 'case']: + continue + + # Skip "setup" and "loop" functions in .ino files + if file_path.lower().endswith('.ino') and func_name in ['setup', 'loop']: + continue + + # Reconstruct the prototype + prototype = match.group(0).strip()[:-1] # remove the opening brace + prototype = ' '.join(prototype.split()) # normalize whitespace + + # Use (func_name, params) as the key + key = (func_name, params.strip()) + prototypes[key] = (prototype, os.path.basename(file_path), func_name) + + logging.debug(f"\tExtracted prototype [{prototype}]") + + if not prototypes: + logging.debug(f"\tNo function prototypes found in {os.path.basename(file_path)}") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + + return prototypes + + +#------------------------------------------------------------------------------------------------------ +def extract_class_instances(file_path): + """ + Extract class instance definitions from a single .ino, .cpp, or .h file. + Includes both global and function-local class instance declarations. + """ + logging.info("") + logging.info(f"Processing: extract_class_instances() from: {short_path(file_path)}") + + file = os.path.basename(file_path) + fbase = os.path.splitext(file)[0] + + class_instances = {} + + # Read already found instances from the global dict_class_instances + if file_path in dict_class_instances: + class_instances[file_path] = dict_class_instances[file_path] + + # Pattern for class instance declarations + class_pattern = r'^\s*([A-Z]\w+(?:<.*?>)?)\s+(\w+)(?:\s*\((.*?)\))?\s*;' + # Pattern for function definitions + function_pattern = r'^\s*(?:(?:void|int|float|double|char|bool|auto)\s+)?(\w+)\s*\([^)]*\)\s*{' + + try: + with open(file_path, 'r') as f: + content = f.read() + + lines = content.split('\n') + file_instances = [] + included_headers = set() + current_function = None + + for line_num, line in enumerate(lines, 1): + stripped_line = line.strip() + + # Check for function definition + func_match = re.search(function_pattern, stripped_line) + if func_match: + current_function = func_match.group(1) + + # Check for closing brace to exit function context + if stripped_line == '}' and current_function: + current_function = None + + class_match = re.search(class_pattern, stripped_line) + if class_match: + class_type = class_match.group(1).strip() + instance_name = class_match.group(2).strip() + constructor_args = class_match.group(3).strip() if class_match.group(3) else "" + context = f"global" if not current_function else f"in function {current_function}" + logging.info(f"Match found on line {line_num} ({context}): {class_type} {instance_name}") + + # Check if it's a valid class type (starts with uppercase and is either in known_classes or matches the pattern) + if class_type[0].isupper() and (class_type in dict_known_classes or re.match(r'^[A-Z]\w+$', class_type)): + # Check if the class is in dict_singleton_classes values + singleton_header = None + for header, classes in dict_singleton_classes.items(): + if class_type in classes: + singleton_header = header + break + + if singleton_header: + if singleton_header not in included_headers: + file_instances.append((singleton_header, "", "singleton", fbase)) + included_headers.add(singleton_header) + logging.info(f"{fbase}: Added include for singleton header {singleton_header} (class {class_type})") + logging.info(f"{fbase}: Class {class_type} {instance_name} is a singleton, including header {singleton_header}") + else: + file_instances.append((class_type, instance_name, constructor_args, fbase)) + logging.info(f"{fbase}: Added {class_type} {instance_name} ({context})") + else: + logging.info(f"Skipping invalid class type: {class_type}") + + # Check global dict_singleton_classes for objects + for header, classes in dict_singleton_classes.items(): + for class_type in classes: + if class_type in content and header not in included_headers: + logging.info(f"\t\tFound singleton class [{class_type}] in [{header}]") + file_instances.append((header, "-", "=", fbase)) + included_headers.add(header) + logging.debug(f"{fbase}: Added include for singleton header {header}") + + if file_instances: + class_instances[file_path] = file_instances + else: + logging.info(f"\t>> No class instances found.") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + return class_instances + + if (len(file_instances) > 0): + logging.info(f"\tExtracted class instances from {os.path.basename(file_path)} -> found: {len(file_instances)}") + + return class_instances + +#------------------------------------------------------------------------------------------------------ +def update_arduinoglue_with_includes(dict_all_includes): + logging.info("") + logging.info("Processing: update_arduinoglue_with_includes()") + + global all_includes_added + + try: + glue_path = os.path.join(glob_pio_include, "arduinoGlue.h") + with open(glue_path, "r") as file: + content = file.read() + + insert_pos = find_marker_position(content, all_includes_marker) + + new_content = content[:insert_pos] # + "\n" + + for include in dict_all_includes: + logging.debug(f"Added:\t{include}") + new_content += f"{include}\n" + all_includes_added = True + new_content += "\n" + + new_content += content[insert_pos:] + + with open(glue_path, "w") as file: + file.write(new_content) + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def update_arduinoglue_with_global_variables(dict_global_variables): + logging.info("") + logging.info("Processing: update_arduinoglue_with_global_variables()") + + global extern_variables_added + + try: + glue_path = os.path.join(glob_pio_include, "arduinoGlue.h") + with open(glue_path, "r") as file: + content = file.read() + + insert_pos = find_marker_position(content, extern_variables_marker) + + new_content = content[:insert_pos] # + "\n" + + sorted_global_vars = sort_global_vars(dict_global_variables) + for file_path, vars_list in sorted_global_vars.items(): + if vars_list: # Only print for files that have global variables + for var_type, var_name, function, is_pointer in vars_list: + var_name += ';' + if var_type.startswith("static "): + logging.debug(f"\t\t\tFound static variable [{var_type}] (remove \'static\' part)") + var_type = var_type.replace("static ", "").strip() # Remove 'static' and any leading/trailing spaces + logging.debug(f"Added:\textern {var_type:<15} {var_name:<35}\t\t//-- from {file_path})") + new_content += (f"extern {var_type:<15} {var_name:<35}\t\t//-- from {file_path}\n") + extern_variables_added = True + new_content += "\n" + + new_content += content[insert_pos:] + + with open(glue_path, "w") as file: + file.write(new_content) + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def update_arduinoglue_with_prototypes(dict_prototypes): + logging.info("") + logging.info("Processing: update_arduinoglue_with_prototypes()") + + global prototypes_added + + try: + glue_path = os.path.join(glob_pio_include, "arduinoGlue.h") + with open(glue_path, "r") as file: + content = file.read() + + insert_pos = find_marker_position(content, prototypes_marker) + + new_content = content[:insert_pos] # + "\n" + + sav_file = "" + for key, value in dict_prototypes.items(): + func_name, params = key + prototype, file_name, bare_func_name = value + if sav_file != file_name: + sav_file = file_name + logging.debug(f"Added:\t//-- from {file_name} ----------") + new_content += (f"//-- from {file_name} -----------\n") + prototypes_added = True + prototype_sm = prototype + ';' + logging.debug(f"Added:\t{prototype_sm}") + new_content += (f"{prototype_sm:<60}\n") + new_content += "\n" + + new_content += content[insert_pos:] + + with open(glue_path, "w") as file: + file.write(new_content) + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"\tAn error occurred at line {line_number}: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def remove_unused_markers_from_arduinoGlue(): + logging.info("Processing: remove_unused_markers_from_arduinoGlue()") + glue_path = os.path.join(glob_pio_include, "arduinoGlue.h") + logging.info(f"GluePath: {glue_path}") + + try: + with open(glue_path, 'r') as file: + content = file.read() + + #debug#logging.info("File content:") + #debug#logging.info(content) + + original_content = content # Store original content for comparison + + markers = { + all_includes_marker: 'all_includes_added', + struct_union_and_enum_marker: 'struct_union_and_enum_added', + extern_variables_marker: 'extern_variables_added', + global_pointer_arrays_marker: 'global_pointer_arrays_added', + extern_classes_marker: 'extern_classes_added', + prototypes_marker: 'prototypes_added', + convertor_marker: 'convertor_added' + } + + # Create a list of all markers for the regex pattern + all_markers = '|'.join(re.escape(m) for m in markers.keys()) + + for marker, test_var in markers.items(): + logging.debug(f"\tChecking marker: {marker}") + if not globals().get(test_var, False): + logging.info(f"\tRemoving unused marker: {marker}") + # Pattern to match from this marker to the next marker or #endif + pattern = f'({re.escape(marker)}).*?(?={all_markers}|#endif)' + #debug#logging.info(f"\tSearch pattern: {pattern}") + matches = re.findall(pattern, content, flags=re.DOTALL) + if matches: + for match in matches: + logging.debug(f"\tFound match: {match}") + new_content = re.sub(pattern, '', content, flags=re.DOTALL) + if new_content != content: + logging.debug(f"\tMarker {marker} successfully removed") + content = new_content + else: + logging.info(f"\tNo change for marker {marker} despite matches") + else: + logging.info(f"\tNo matches found for marker {marker}") + + # Ensure we keep the #endif line + if '#endif' not in content: + content += '\n#endif // ARDUINOGLUE_H\n' + + if content != original_content: + with open(glue_path, 'w') as file: + file.write(content) + logging.info("File updated 'arduinoGlue.h'successfully") + else: + logging.info("No changes were necessary for 'arduinoGlue.h'") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"An error occurred at line {line_number}: {str(e)}") + exit() + +#------------------------------------------------------------------------------------------------------ +def insert_class_instances_to_header_files(file_name): + logging.info("") + logging.info(f"Processing: insert_class_instances_to_header_files() [{os.path.splitext(os.path.basename(file_name))[0]}]") + + global dict_class_instances + + try: + # Generate the corresponding .h file name + file_base = os.path.basename(file_name) + header_base = os.path.splitext(file_base)[0] + ".h" + header_file = os.path.join(glob_pio_include, header_base) + logging.info(f"\t\tprocessing [{file_base}] and [{header_base}]") + + # Check if the .h file exists, if not, create it + if not os.path.exists(header_file): + with open(header_file, 'w') as f: + file_name = os.path.splitext(header_base)[0] + f.write(f"#ifndef _{file_name.upper()}_H_\n") + f.write(f"#define _{file_name.upper()}_H_\n\n") + f.write(f"{all_includes_marker}\n\n") + f.write(f"#endif // _{file_name.upper()}_H_\n") + logging.info(f"\tCreated header file: {short_path(header_file)}") + + # Read the content of the .h file + with open(header_file, 'r') as f: + header_content = f.read() + + # Get the class instances for this file + file_instances = [] + for file_path, instances in dict_class_instances.items(): + if os.path.split(os.path.basename(file_path))[0] == os.path.split(os.path.basename(header_file))[0]: + file_instances = instances + break + + logging.info(f"\t\t>> Found {len(file_instances)} class instances for [{file_base}]") + + # Process regular class instances and singleton includes + includes_to_add = set() + for instance in file_instances: + class_name = instance[0] + logging.info(f"\t\tChecking if {class_name} ...") + # Check if it's a singleton include (ends with .h) + if class_name.endswith('.h'): + include_pattern = rf'#include\s*<{re.escape(class_name)}>\s*(//.*)?$' + if not re.search(include_pattern, header_content, re.MULTILINE): + includes_to_add.add(f'#include <{class_name}>\t\t//== singleton') + logging.info(f"\t\tAdding: #include <{class_name}>\t\t//== singleton") + else: + logging.info(f"\t\tSkipping (already exists): #include <{class_name}>") + else: + # Check if the class is in dict_singleton_classes + singleton_header = None + for header, classes in dict_singleton_classes.items(): + if class_name in classes: + singleton_header = header + break + + if singleton_header: + include_pattern = rf'#include\s*<{re.escape(singleton_header)}>\s*(//.*)?$' + if not re.search(include_pattern, header_content, re.MULTILINE): + includes_to_add.add(f'#include <{singleton_header}>\t\t//-- singleton') + logging.info(f"\t\tAdding: #include <{singleton_header}>\t\t//-- singleton") + else: + logging.info(f"\t\tSkipping (already exists): #include <{singleton_header}>") + else: + include_pattern = rf'#include\s*<{re.escape(class_name)}\.h>\s*(//.*)?$' + if not re.search(include_pattern, header_content, re.MULTILINE): + includes_to_add.add(f'#include <{class_name}.h>\t\t//-- class') + logging.info(f"\t\tAdding: #include <{class_name}.h>\t\t//-- class") + else: + logging.info(f"\t\tSkipping (already exists): #include <{class_name}.h>") + + logging.info(f"includes to add: {includes_to_add}") + # Find the position to insert the new includes and insert them + insert_pos = find_marker_position(header_content, all_includes_marker) + if insert_pos != -1: + new_includes = '\n'.join(includes_to_add) + logging.info(f"\t\tnew includes: [{new_includes}]") + updated_content = ( + header_content[:insert_pos] + + new_includes + '\n' + + header_content[insert_pos:] + ) + else: + updated_content = header_content + insert_pos = 0 + logging.warning(f"\t\tCould not find marker {all_includes_marker} in {short_path(header_file)}") + + # Write the updated content back to the .h file + with open(header_file, 'w') as f: + f.write(updated_content) + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + line_number = exc_tb.tb_lineno + logging.error(f"An error occurred at line {line_number}: {str(e)}") + logging.error(f"An error occurred during conversion: {str(e)}") + + logging.info(f"Finished inserting class instances to header file [{file_base}].") + +#------------------------------------------------------------------------------------------------------ +def update_header_with_prototypes(header_path, prototypes): + """Update header file with function prototypes.""" + logging.info() + logging.info(f"Processing: update_header_with_prototypes() file: {os.path.basename(header_path)}") + + marker_index = header_path.find(platformio_marker) + if marker_index != -1: + short_header_path = header_path[marker_index + len(platformio_marker):] + else: + short_header_path = header_path + + with open(header_path, 'r+') as f: + content = f.read() + + # Search for the prototype insertion marker + insert_start = content.find(f"{prototypes_marker}") + if insert_start != -1: + insert_pos = insert_start + len(f"{prototypes_marker}\n") + else: + insert_start = -1 + + # If marker is not found, search for the last #include statement + if insert_start == -1: + include_matches = list(re.finditer(r'#include\s*<[^>]+>', content)) + if include_matches: + insert_pos = include_matches[-1].end() + 1 # Position after the last #include + else: + # If no #include statement, search for header guard + header_guard_match = re.search(r'#ifndef\s+\w+\s+#define\s+\w+', content, re.MULTILINE) + if header_guard_match: + insert_pos = header_guard_match.end() + 1 # Position after the header guard + else: + # If no header guard, insert at the top of the file + insert_pos = 0 + + # Gather existing prototypes to avoid duplication + existing_prototypes = set(re.findall(r'^[^\S\r\n]*(?:extern\s+)?\w[\w\s\*\(\),]*\s*\([^;]*\);', content[insert_pos:], re.MULTILINE)) + prototypes_to_add = set(prototypes) - existing_prototypes + + if prototypes_to_add: + new_content = (content[:insert_pos] + + '\n'.join(sorted(prototypes_to_add)) + '\n\n' + + content[insert_pos:]) + f.seek(0) + f.write(new_content) + f.truncate() + logging.info(f"\tAdded {len(prototypes_to_add)} function prototypes to [{os.path.basename(header_path)}]") + for prototype in prototypes_to_add: + logging.info(f" - {prototype}") + else: + logging.info(f"\tNo new function prototypes added to [{os.path.basename(header_path)}]") + + +#------------------------------------------------------------------------------------------------------ +def find_undefined_functions_and_update_headers(glob_pio_src, glob_pio_include, function_reference_array): + """ + Find undefined functions in glob_pio_src files and update corresponding header files. + """ + NON_FUNCTION_KEYWORDS = {'if', 'else', 'for', 'while', 'switch', 'case', 'default', 'do', 'return', 'break', 'continue'} + + logging.info() + logging.info("Processing: find_undefined_functions_and_update_headers") + + for file in os.listdir(glob_pio_src): + if file.endswith('.cpp'): + file_path = os.path.join(glob_pio_src, file) + base_name = os.path.splitext(file)[0] + header_path = os.path.join(glob_pio_include, f"{base_name}.h") + + marker_index = header_path.find(platformio_marker) + if marker_index != -1: + short_header_path = header_path[marker_index + len(platformio_marker):] + else: + short_header_path = header_path + + with open(file_path, 'r') as f: + content = f.read() + + # Find all function calls + function_calls = set(re.findall(r'\b(\w+)\s*\(', content)) + + # Find local function definitions + local_functions = set(re.findall(r'\b\w+[\s\*]+(\w+)\s*\([^)]*\)\s*{', content)) + + # Determine which functions are undefined in this file + undefined_functions = function_calls - local_functions + + # Check which undefined functions are in the function_reference_array + functions_to_include = [func for func in undefined_functions if func in function_reference_array] + + if functions_to_include: + logging.info(f"\tFunctions to include in {file}:") + for func in functions_to_include: + logging.info(f"\t>> {func} - {function_reference_array[func]}") + + # Update the header file + with open(header_path, 'r') as f: + header_content = f.read() + + new_includes = [] + for func in functions_to_include: + include_file = function_reference_array[func] + # Ensure we don't include the file in itself + if include_file != f"{base_name}.h": + include_statement = f'#include "{include_file}\t\t//== by convertor"' + if include_statement not in header_content: + new_includes.append(include_statement) + + if new_includes: + # Find the position to insert new includes + marker = extern_classes_marker + insert_pos = header_content.find(f"{marker}") + if insert_pos == -1: + marker = all_includes_marker + insert_pos = header_content.find(f"{marker}") + if insert_pos == -1: + marker = convertor_marker + insert_pos = header_content.find(f"{marker}") + if insert_pos == -1: + marker = "" + header_guard_end = re.search(r'#define\s+\w+_H\s*\n', header_content) + if header_guard_end: + insert_pos = header_guard_end.end() + else: + logging.info("\t\tCannot find suitiblae insertion point") + return + + insert_pos += len(f"{marker}\n") + updated_content = ( + header_content[:insert_pos] + + '\n'.join(new_includes) + '\n' + + header_content[insert_pos:] + ) + + # Write the updated content back to the header file + with open(header_path, 'w') as f: + f.write(updated_content) + + logging.info(f"\tUpdated {short_header_path} with new includes:") + for include in new_includes: + logging.info(f" - {include}") + else: + logging.warning(f"\tCould not find '{all_includes_marker}' in {short_header_path}") + else: + logging.info(f"\tNo new includes needed for {short_header_path}") + else: + logging.info(f"\tNo undefined functions found in {file} that need to be included") + + logging.info("\tCompleted finding undefined functions and updating headers") + +#------------------------------------------------------------------------------------------------------ +def process_function_references(glob_pio_src, glob_pio_include): + logging.info() + logging.info("Process process_function_references()") + + function_reference_array = {} + + # Collect all function prototypes from header files + for file in os.listdir(glob_pio_include): + if file.endswith('.h'): + with open(os.path.join(glob_pio_include, file), 'r') as f: + content = f.read() + prototypes = re.findall(r'^\w+[\s\*]+(\w+)\s*\([^)]*\);', content, re.MULTILINE) + for func_name in prototypes: + function_reference_array[func_name] = file + + # Print the function reference array + logging.info("\tFunction Reference Array:") + for func, file in function_reference_array.items(): + logging.info(f"{func}: {file}") + + # Process .ino files + for file in os.listdir(glob_pio_src): + if file.endswith('.ino') or file.endswith('.cpp'): + base_name = os.path.splitext(file)[0] + source_path = os.path.join(glob_pio_src, file) + header_path = os.path.join(glob_pio_include, f"{base_name}.h") + + with open(source_path, 'r') as f: + content = f.read() + + # Find all function calls + function_calls = set(re.findall(r'\b(\w+)\s*\(', content)) + + # Find local function definitions + local_functions = set(re.findall(r'\b\w+[\s\*]+(\w+)\s*\([^)]*\)\s*{', content)) + + # Determine which functions need to be included + functions_to_include = function_calls - local_functions + + headers_to_include = set() + for func in functions_to_include: + if func in function_reference_array: + headers_to_include.add(function_reference_array[func]) + insert_include_in_header(header_path, function_reference_array[func]) + + # Update the header file with necessary includes + #aaw#if headers_to_include: + #aaw#insert_include_in_header(header_path, function_reference_array[func]) + + logging.info("\tProcessed function references and updated header files") + return function_reference_array # Return the function_reference_array + + +#------------------------------------------------------------------------------------------------------ +def add_guards_and_marker_to_header(file_path): + logging.info("") + logging.info(f"Processing: add_guards_and_marker_to_header() file: [{os.path.basename(file_path)}]") + + with open(file_path, 'r') as file: + content = file.read() + + # Replace multiple empty lines with a single empty line + content = re.sub(r'\n\s*\n', '\n\n', content) + + lines = content.splitlines() + + # Check for existing header guard + has_header_guard = False + if len(lines) >= 2 and lines[0].strip().startswith("#ifndef") and lines[1].strip().startswith("#define"): + has_header_guard = True + logging.info("\tHeader guard already present.") + + # Add header guard if not present + if not has_header_guard: + guard_name = f"{os.path.basename(file_path).upper().replace('.', '_')}_" + lines.insert(0, f"#ifndef {guard_name}") + lines.insert(1, f"#define {guard_name}") + lines.append(f"#endif // {guard_name}") + logging.info("\tAdded header guard.") + + # Find the last #include statement + last_include_index = -1 + for i, line in enumerate(lines): + if line.strip().startswith("#include"): + last_include_index = i + + # Determine where to insert the CONVERTOR marker + if last_include_index != -1: + insert_index = last_include_index + 1 + logging.info(f"\tInserting marker after last #include statement (line {insert_index + 1})") + else: + # If no #include, insert after header guard or at the beginning + insert_index = 2 if has_header_guard else 0 + logging.info(f"\tInserting marker at the beginning of the file (line {insert_index + 1})") + + # Insert the CONVERTOR marker + lines.insert(insert_index, "") + lines.insert(insert_index + 1, convertor_marker) + lines.insert(insert_index + 2, "") + + modified_content = "\n".join(lines) + + with open(file_path, 'w') as file: + file.write(modified_content) + + logging.info(f"\tFile {os.path.basename(file_path)} has been successfully modified.") + +#------------------------------------------------------------------------------------------------------ +def insert_header_include_in_cpp(file_path): + logging.info("") + logging.info(f"Processing: insert_header_include_in_cpp(): [{os.path.basename(file_path)}]") + + with open(file_path, 'r') as file: + content = file.read() + + lines = content.splitlines() + + # Generate the include statement + basename = os.path.splitext(os.path.basename(file_path))[0] + include_statement = f'#include "{basename}.h"' + + # Check if the include statement already exists + if any(line.strip() == include_statement for line in lines): + logging.info(f"\tInclude statement '{include_statement}' already exists. No changes made.") + return + + # Find the end of the first comment + in_multiline_comment = False + insert_index = 0 + + for i, line in enumerate(lines): + stripped_line = line.strip() + + if in_multiline_comment: + if "*/" in line: + insert_index = i + 1 + break + elif stripped_line.startswith("//"): + insert_index = i + 1 + break + elif stripped_line.startswith("/*"): + in_multiline_comment = True + if "*/" in line: + insert_index = i + 1 + break + elif stripped_line and not stripped_line.startswith("#"): + # If we've reached a non-empty, non-comment, non-preprocessor line, stop searching + break + + # Insert the include statement + lines.insert(insert_index, include_statement) + + modified_content = "\n".join(lines) + + with open(file_path, 'w') as file: + file.write(modified_content) + + logging.info(f"\tInserted '{include_statement}' at line {insert_index + 1}") + logging.info(f"\tFile {os.path.basename(file_path)} has been successfully modified.") + +#------------------------------------------------------------------------------------------------------ +def preserve_original_headers(): + """Read and preserve the original content of all existing header files.""" + logging.info("") + logging.info("Processing: preserve_original_headers() ..") + + original_headers = {} + for file in os.listdir(glob_pio_include): + if file.endswith('.h'): + header_path = os.path.join(glob_pio_include, file) + with open(header_path, 'r') as f: + original_headers[file] = f.read() + + return original_headers + + +#------------------------------------------------------------------------------------------------------ +def update_project_header(glob_pio_include, glob_project_name, original_content): + """Update project header file with includes for all created headers while preserving original content.""" + logging.info("") + logging.info("Processing: update_project_header() ..") + + project_header_path = os.path.join(glob_pio_include, f"{glob_project_name}.h") + + # Split the original content into sections + sections = re.split(r'(//==.*?==)', original_content, flags=re.DOTALL) + + new_content = [] + local_includes = [] + + # Process each section + for i, section in enumerate(sections): + if section.strip() == f"{all_includes_marker}": + # Add new local includes here + new_content.append(section + "\n") + for file in os.listdir(glob_pio_include): + if file.endswith('.h') and file != f"{glob_project_name}.h": + include_line = f'#include "{file}"\n' + if include_line not in original_content: + new_content.append(include_line) + new_content.append("\n") + elif i == 0: # First section (before any //== markers) + new_content.append(section) + # Add system includes if they don't exist + if "#include " not in section: + new_content.append("#include \n") + if "#include \"arduinoGlue.h\"" not in section: + new_content.append("#include \"arduinoGlue.h\"\n") + else: + new_content.append(section) + + # Write the updated content back to the file + with open(project_header_path, 'w') as f: + f.writelines(new_content) + + logging.info(f"\tUpdated project header {glob_project_name}.h while preserving original content") + + + +#------------------------------------------------------------------------------------------------------ +def main(): + global glob_ino_project_folder, glob_project_name, glob_pio_folder, glob_pio_src, glob_pio_include + global args + + args = parse_arguments() + setup_logging(args.debug) + + logging.info(f"Global project dir is [%s]", args.project_dir) + + # Backup the original project if requested + #if args.backup: + # backup_project(args.project_dir) + + try: + logging.info("") + logging.info("=======================================================================================================") + logging.info(f"[Step 1] Setup the folder structure for a PlatformIO project") + logging.info("=======================================================================================================") + + set_glob_project_info(args.project_dir) + logging.info(f" Project folder: {glob_ino_project_folder}") + + marker = "arduinoIDE2platformIO-convertor" + logging.info(f"\t PlatformIO folder: {short_path(glob_pio_folder)}") + logging.info(f"\t PlatformIO src folder: {short_path(glob_pio_src)}") + logging.info(f"\tPlatformIO include folder: {short_path(glob_pio_include)}") + + remove_pio_tree("platformio.ini") + + recreate_pio_folders() + + if not os.path.exists(glob_pio_folder): + logging.error(f"PlatformIO folder does not exist: {glob_pio_folder}") + return + + logging.info("") + logging.info("=======================================================================================================") + logging.info(f"[Step 2] Copy all .ino, .cpp, .c and .h files from the Arduino project") + logging.info( " Copy the data folder (if it excists) from the Arduino project") + logging.info( " Create the platformio.ini file") + logging.info( " Extract all #define statements from all .ino and .h files") + logging.info("=======================================================================================================") + + copy_project_files() + copy_data_folder() + create_platformio_ini() + create_arduinoglue_file() + extract_and_comment_defines() + move_struct_union_and_enum_declarations() + + + search_folders = [glob_pio_src, glob_pio_include] + + list_files_in_directory(glob_pio_src) + + logging.info("") + logging.info("=======================================================================================================") + logging.info("[Step 3] Process all '.ino' and 'h' files extracting includes, global variables and ") + logging.info(" prototypes.. and insert Header Guards in all existing header files") + logging.info("=======================================================================================================") + + for folder in search_folders: + for root, _, files in os.walk(folder): + for file in files: + if file.endswith(('.h', '.ino')): + file_path = os.path.join(root, file) + base_name = os.path.basename(file) # Get the basename without extension + logging.info("") + logging.debug("-------------------------------------------------------------------------------------------------------") + logging.debug(f"Processing file: {short_path(file_path)} basename: [{base_name}]") + + lib_includes = extract_all_includes_from_file(file_path) + if args.debug: + print_includes(lib_includes) + dict_all_includes.update({include: None for include in lib_includes}) + global_vars = extract_global_variables(file_path) + if args.debug: + print_global_vars(global_vars) + dict_global_variables.update(global_vars) + global_vars = extract_constant_pointers(file_path) + if args.debug: + print_global_vars(global_vars) + dict_global_variables.update(global_vars) + prototypes = extract_prototypes(file_path) + if args.debug: + print_prototypes(prototypes) + dict_prototypes.update(prototypes) + + if file.endswith('.h') and file != "arduinoGlue.h": + add_guards_and_marker_to_header(file_path) + + logging.info("") + logging.info("And now the complete list of #includes:") + print_includes(dict_all_includes) + logging.info("And now the complete list of global variables:") + print_global_vars(dict_global_variables) + logging.info("And now the complete list of prototypes:") + print_prototypes(dict_prototypes) + + logging.info("And now add all dict's to arduinoGlue.h:") + update_arduinoglue_with_includes(dict_all_includes) + update_arduinoglue_with_global_variables(dict_global_variables) + update_arduinoglue_with_prototypes(dict_prototypes) + + logging.info("") + logging.info("=======================================================================================================") + logging.info(f"[Step 4] Create new header files for all '.ino' files") + logging.info("=======================================================================================================") + + for filename in os.listdir(glob_pio_src): + logging.debug(f"Processing file: {os.path.basename(filename)}") + ino_name = os.path.basename(filename) + base_name = os.path.splitext(ino_name)[0] # Get the basename without extension + header_name = ino_name.replace(".ino", ".h") + if filename.endswith(".ino"): + create_new_header_file(ino_name, header_name) + + logging.info("") + logging.info("=======================================================================================================") + logging.info(f"[Step 5] add all local includes to the 'project_name.ino' file (insert '#include \"x.h\"' to x.ino)") + logging.info("=======================================================================================================") + + #-- add all local includes to {project_name}.h + add_local_includes_to_project_header() + + logging.info("") + logging.info("=======================================================================================================") + logging.info(f"[Step 6] rename all '.ino' files to '.cpp'") + logging.info("=======================================================================================================") + + for filename in os.listdir(glob_pio_src): + logging.debug(f"Found file: {os.path.basename(filename)}") + ino_name = os.path.basename(filename) + base_name = os.path.splitext(ino_name)[0] # Get the basename without extension + header_name = ino_name.replace(".ino", ".h") + if filename.endswith(".ino"): + ino_path = os.path.join(glob_pio_src, filename) + cpp_name = ino_name.replace(".ino", ".cpp") + cpp_path = os.path.join(glob_pio_src, cpp_name) + rename_file(ino_path, cpp_path) + insert_header_include_in_cpp(cpp_path) + + remove_unused_markers_from_arduinoGlue() + + logging.info("") + logging.info("*************************************************************") + logging.info("** Arduino to PlatformIO conversion completed successfully **") + logging.info("*************************************************************") + + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + #fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + line_number = exc_tb.tb_lineno + logging.error(f"An error occurred at line {line_number}: {str(e)}") + logging.error(f"An error occurred during conversion: {str(e)}") + +#====================================================================================================== +if __name__ == "__main__": + main() + + +#******************************************************************************************* +# MIT License +# +# Copyright (c) 2024 Willem Aandewiel +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#************************************************************************************************ diff --git a/arduinoIDE2platformIO-convertor/crossReference.py b/arduinoIDE2platformIO-convertor/crossReference.py new file mode 100644 index 0000000..79ee228 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/crossReference.py @@ -0,0 +1,200 @@ +#------------------------------------------------------------ +# +# X-Reference a arduinoGlue.h file +# +# file name : crossReference.py +# +# by : Willem Aandewiel +# +# Version : v0.81 (04-10-2024) +# +# Usage: python3 crossReference.py 1: + usage_str = ", ".join(usage) + print(f"{var_type} {var_name} {usage_str}") + + except Exception as e: + logging.error(f"An unexpected error occurred: {e}") + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python crossReference.py ") + sys.exit(1) + + project_path = sys.argv[1] + process_arduino_project(project_path) + + +#******************************************************************************************* +# MIT License +# +# Copyright (c) 2024 Willem Aandewiel +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#************************************************************************************************ + diff --git a/arduinoIDE2platformIO-convertor/saveNext.sh b/arduinoIDE2platformIO-convertor/saveNext.sh new file mode 100755 index 0000000..b8c8fe3 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/saveNext.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# +NOW=$(date '+%Y-%m-%d_%H.%M') +echo ${NOW} +SOURCE="${HOME}/tmp/arduinoIDE2platformIO-convertor/next.py" +#DEST="${HOME}/Documents/claudeProgress/next_${NOW}.py" +DEST="${HOME}/Documents/claudeProgress/" +echo "Copy ${SOURCE} to ${DEST}/next_${NOW}.py" +# +cp -v ${SOURCE} ${DEST}/next_${NOW}.py +cp -v ${SOURCE} ../../changes/next_${NOW}.py + +echo "Done!" + +ls -l ${DEST} + diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/Debug.h b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/Debug.h new file mode 100644 index 0000000..efdd69d --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/Debug.h @@ -0,0 +1,57 @@ +/* +*************************************************************************** +** Program : Debug.h, part of ESP_tickerExtend +** +** Copyright (c) 2021 Willem Aandewiel +** Met dank aan Erik +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +/*---- start macro's ------------------------------------------------------------------*/ + +#define Debug(...) ({ Serial.print(__VA_ARGS__); \ + }) +#define Debugln(...) ({ Serial.println(__VA_ARGS__); \ + }) +#define Debugf(...) ({ Serial.printf(__VA_ARGS__); \ + }) + +#define DebugFlush() ({ Serial.flush(); \ + }) + + +#define DebugT(...) ({ Debug(__VA_ARGS__); \ + }) +#define DebugTln(...) ({ Debugln(__VA_ARGS__); \ + }) +#define DebugTf(...) ({ Debugf(__VA_ARGS__); \ + }) + +/*---- einde macro's ------------------------------------------------------------------*/ + + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/ESP_ticker.h b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/ESP_ticker.h new file mode 100644 index 0000000..6515799 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/ESP_ticker.h @@ -0,0 +1,151 @@ +/* +*************************************************************************** +** Program : ESP_ticker.h, part of ESP_ticker +** +** Copyright (c) 2021 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +#include +#include +#include +#include + +#include "TimeSyncClass.h" + +#include "Debug.h" +//aaw#include "networkStuff.h" + +#include // @ 3.7.3 (was 3.5.5) +#include +#include "parola_Fonts_data.h" +#include + +// Define the number of devices we have in the chain and the hardware interface +// NOTE: These pin numbers are for ESP8266 hardware SPI and will probably not +// work with your hardware and may need to be adapted +//#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW +#define HARDWARE_TYPE MD_MAX72XX::FC16_HW +//#define HARDWARE_TYPE MD_MAX72XX::GENERIC_HW +#define MAX_DEVICES 8 +#define MAX_SPEED 50 + +//#define CLK_PIN 14 // or SCK +//#define DATA_PIN 13 // or MOSI +#define CS_PIN 15 // or SS + +#define SETTINGS_FILE "/settings.ini" +#define LOCAL_SIZE 255 +#define NEWS_SIZE 512 +#define JSON_BUFF_MAX 255 +#define MAX_NO_NO_WORDS 20 + + +// HARDWARE SPI +MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); + +// WiFi Server object and parameters +ESP8266WebServer httpServer(80); + +#include // part of ESP8266 Core https://github.com/esp8266/Arduino + +// Scrolling parameters + +uint8_t inFX, outFX; +textEffect_t effect[] = +{ + PA_PRINT, + //PA_SCAN_HORIZ, + PA_SCROLL_LEFT, + PA_WIPE, + PA_SCROLL_UP_LEFT, + PA_SCROLL_UP, + PA_OPENING_CURSOR, + PA_GROW_UP, + PA_MESH, + PA_SCROLL_UP_RIGHT, + //PA_BLINDS, + PA_CLOSING, + PA_RANDOM, + PA_GROW_DOWN, + PA_SCAN_VERT, + PA_SCROLL_DOWN_LEFT, + PA_WIPE_CURSOR, + //PA_DISSOLVE, + PA_OPENING, + PA_CLOSING_CURSOR, + PA_SCROLL_DOWN_RIGHT, + PA_SCROLL_RIGHT, + //PA_SLICE, + PA_SCROLL_DOWN, +}; + + +bool Verbose = false; +char cDate[15], cTime[10]; +uint32_t nrReboots; +// Global message buffers shared by Wifi and Scrolling functions +char cMsg[NEWS_SIZE]; +char tempMessage[LOCAL_SIZE] = ""; +uint8_t msgType; +char actMessage[NEWS_SIZE], timeMsg[20]; +char onTickerMessage[LOCAL_SIZE] = {}; +char fileMessage[LOCAL_SIZE]; +uint8_t newsMsgID = 0; +uint8_t localMsgID = 0; +int16_t valueLDR, valueIntensity; +char fChar[10]; +String lastReset = ""; +uint32_t timeTimer = 0; +uint32_t ntpTimer = millis() + 30000; +uint32_t weerTimer = 0; +uint32_t newsapiTimer = 0; +uint32_t revisionTimer = 0; +String noWords[MAX_NO_NO_WORDS+1]; +char settingHostname[41]; +char settingNewsNoWords[LOCAL_SIZE]; +uint8_t settingLocalMaxMsg, settingTextSpeed, settingMaxIntensity; +uint16_t settingLDRlowOffset, settingLDRhighOffset; +char settingWeerLiveAUTH[51], settingWeerLiveLocation[51]; +uint8_t settingWeerLiveInterval; +char settingNewsAUTH[51]; +uint8_t settingNewsInterval, settingNewsMaxMsg; +bool LittleFSmounted = false; +FSInfo LittleFSinfo; +time_t now; +struct tm timeinfo; +bool timeSynced = false; + + +TimeSync timeSync; + +const char *weekDayName[] { "Unknown", "Zondag", "Maandag", "Dinsdag", "Woensdag" + , "Donderdag", "Vrijdag", "Zaterdag", "Unknown" }; +const char *flashMode[] { "QIO", "QOUT", "DIO", "DOUT", "Unknown" }; + + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/ESP_ticker.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/ESP_ticker.ino new file mode 100644 index 0000000..d90706d --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/ESP_ticker.ino @@ -0,0 +1,528 @@ +/* +*************************************************************************** +** Program : ESP_ticker (lichtkrant) +*/ +const char* FWversion = "v1.7.3 (04-05-2023)"; +/* +** Copyright (c) 2021 .. 2023 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** + + Arduino-IDE settings for ESP-12E: + + - Board: "Generic ESP8266 Module" (ALLWAYS!!!!!) + - Buildin Led: "2" + - Upload Speed: "115200" + - CPU Frequency: "80 MHz" (or if you need the speed: 160MHz) + - Flash size: "4MB (FS:2M OTA~1019KB)" + - Flash mode: "DIO" / "DOUT" + - Flash Frequency: "40MHz" + - Reset Method: "nodemcu" or something else + - Debug port: "Disabled" + - Debug Level: "None" + - IwIP Variant: "v2 Lower Memory" + - VTables: "Flash" + - Exceptions: "Legacy (new can return nullptr)" + - Erase Flash: "Only Sketch" + - Espressif FW: "nonos-sdk 2.2.1+100 (190703)" + - SSL Support: "All SSL ciphers (most compatible)" + - Port: "ESPticker at <-- IP address -->" + + Arduino ESP8266 core v2.7.+ +*/ + + +// Use the Parola library to scroll text on the display +// IP address for the ESP8266 is displayed on the scrolling display +// after startup initialisation and connected to the WiFi network. +// +// Connections for ESP8266 hardware SPI are: +// Vcc 3v3 LED matrices seem to work at 3.3V +// GND GND GND +// DIN D7 HSPID or HMOSI +// CS or LD D8 HSPICS or HCS +// CLK D5 CLK or HCLK +// +// MD_MAX72XX library can be found at https://github.com/MajicDesigns/MD_MAX72XX +// + +#define USE_UPDATE_SERVER + +#define _HOSTNAME "ESPticker" +#include "ESP_ticker.h" + + +//--------------------------------------------------------------------- +int16_t calculateIntensity() +{ + int a0In = 0; + for (int l=0; l<2; l++) + { + //--- read analog A0 + a0In+= analogRead(A0); + delay(200); + } + a0In = a0In / 2; //-- smooth things up a bit + + DebugTf("analogRead[%d], ", a0In); + //---test if (a0In < settingLDRlowOffset) a0In = settingLDRlowOffset; + Debugf(" LDRlowOffset[%d] LDRhighOffset[%d] ", settingLDRlowOffset, settingLDRhighOffset); + valueLDR = (valueLDR + a0In) / 2; + if (valueLDR < settingLDRlowOffset) valueLDR = settingLDRlowOffset; + if (valueLDR > settingLDRhighOffset) valueLDR = settingLDRhighOffset; + Debugf(" ==> valueLDR[%d]\r\n", valueLDR); + + //--- map LDR to offset..1024 -> 0..settingMax + int intensity = map(valueLDR, settingLDRlowOffset, settingLDRhighOffset, 0, settingMaxIntensity); + //DebugTf("map(%d, %d, %d, 0, %d) => [%d]\r\n", valueLDR, settingLDRlowOffset, settingLDRhighOffset + // , 0 , settingMaxIntensity); + + return intensity; + +} // calculateIntensity() + + +//--------------------------------------------------------------------- +char *updateTime() +{ + time(&now); + snprintf(timeMsg, 20, "%02d : %02d", localtime(&now)->tm_hour, localtime(&now)->tm_min); + return timeMsg; + +} // updateTime() + + +//--------------------------------------------------------------------- +bool getTheLocalTime(struct tm *info, uint32_t ms) +{ + //-- getLocalTime() is not implemented in the ArduinoIDE + //-- so this is a 'work around' function + uint32_t start = millis(); + time_t now; + while((millis()-start) <= ms) + { + time(&now); + localtime_r(&now, info); + if(info->tm_year > (2016 - 1900)) + { + return true; + } + delay(10); + } + return false; + +} // getTheLocalTime() + + +//--------------------------------------------------------------------- +void splitNewsNoWords(const char *noNo) +{ + DebugTln(noNo); + int8_t wc = splitString(String(noNo), ' ', noWords, MAX_NO_NO_WORDS); + for(int8_t i=0; i 1) + { + noWords[i].toLowerCase(); + DebugTf("NoNoWord[%d] [%s]\r\n", i, noWords[i].c_str()); + } + } + +} // splitNewsNoWords() + +//--------------------------------------------------------------------- +bool hasNoNoWord(const char *cIn) +{ + for(int8_t i=0; i -1) && (noWords[i].length() > 1)) // yes! it's in there somewhere + { + DebugTf("found [%s]\r\n", noWords[i].c_str()); + return true; + } + } + //DebugTln("no NoNo words found!"); + return false; + +} // hasNoNoWord() + + +//--------------------------------------------------------------------- +void nextNieuwsBericht() +{ + bool breakOut = false; + newsMsgID++; + if (newsMsgID >= settingNewsMaxMsg) newsMsgID = 0; + while (!readFileById("NWS", newsMsgID)) + { + DebugTln("File not found!"); + newsMsgID++; + if (newsMsgID > settingNewsMaxMsg) + { + newsMsgID = 0; + breakOut = true; + break; + } + } + if (!breakOut) + { + snprintf(actMessage, NEWS_SIZE, "** %s **", fileMessage); + //DebugTf("newsMsgID[%d] %s\r\n", newsMsgID, actMessage); + utf8Ascii(actMessage); + P.displayScroll(actMessage, PA_LEFT, PA_SCROLL_LEFT, (MAX_SPEED - settingTextSpeed)); + } + +} // nextNieuwsBericht() + + +//--------------------------------------------------------------------- +void nextLocalBericht() +{ + bool nothingThere = false; + + localMsgID++; + if (localMsgID > settingLocalMaxMsg) + { + localMsgID = 0; + nothingThere = true; + } + while (!readFileById("LCL", localMsgID)) + { + DebugTf("File [/newsFiles/LCL-%03d] not found!\r\n", localMsgID); + localMsgID++; + if (localMsgID > settingLocalMaxMsg) + { + DebugTln("Back to LCL-000, exit while-loop"); + localMsgID = 0; + continue; + } + } + if (nothingThere && (localMsgID == 0)) + { + nothingThere = true; + getRevisionData(); + } + else nothingThere = false; + + snprintf(actMessage, LOCAL_SIZE, "** %s **", fileMessage); + //DebugTf("localMsgID[%d] %s\r\n", localMsgID, actMessage); + utf8Ascii(actMessage); + P.displayScroll(actMessage, PA_LEFT, PA_SCROLL_LEFT, (MAX_SPEED - settingTextSpeed)); + + if ((millis() - revisionTimer) > 900000) + { + revisionTimer = millis(); + getRevisionData(); + } + +} // nextLocalBericht() + + +//===================================================================== +void setup() +{ + Serial.begin(115200); + while(!Serial) { /* wait a bit */ } + + lastReset = ESP.getResetReason(); + + DebugTln("\r\n[MD_Parola WiFi Message Display]\r\n"); + DebugTf("Booting....[%s]\r\n\r\n", String(FWversion).c_str()); + + P.begin(); + P.displayClear(); + P.displaySuspend(false); + P.setIntensity(2); + P.displayScroll(actMessage, PA_LEFT, PA_NO_EFFECT, 20); + P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); + do + { + yield(); + } while( !P.displayAnimate() ); + + actMessage[0] = '\0'; + +//================ LittleFS =========================================== + if (LittleFS.begin()) + { + DebugTln(F("LittleFS Mount succesfull\r")); + LittleFSmounted = true; + + readSettings(true); + splitNewsNoWords(settingNewsNoWords); + + if (settingNewsInterval == 0) + { + removeNewsData(); + } + else + { + if (!LittleFS.exists("/newsFiles/LCL-000")) + { + char LCL000[100]; + sprintf(LCL000, "ESP_ticker %s by Willem Aandewiel", String(FWversion).c_str()); + writeFileById("LCL", 0, LCL000); + } + if (!LittleFS.exists("/newsFiles/LCL-001")) + { + char LCL001[100]; + sprintf(LCL001, "ESP_ticker %s by Willem Aandewiel", String(FWversion).c_str()); + writeFileById("LCL", 1, LCL001); + } + writeFileById("NWS", 1, "(c) 2021 Willem Aandewiel"); + } + } + else + { + DebugTln(F("LittleFS Mount failed\r")); // Serious problem with LittleFS + LittleFSmounted = false; + } + //==========================================================// + // writeLastStatus(); // only for firsttime initialization // + //==========================================================// + readLastStatus(); // place it in actTimestamp + + // attempt to connect to Wifi network: + int t = 0; + while ((WiFi.status() != WL_CONNECTED) && (t < 25)) + { + delay(500); + Serial.print("."); + t++; + } + if ( WiFi.status() != WL_CONNECTED) { + DebugTln("Attempting to connect to WiFi network\r"); + sprintf(actMessage, "Connect to AP '%s' and configure WiFi on 192.168.4.1 ", _HOSTNAME); + P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); + do { yield(); } while( !P.displayAnimate() ); + //P.print(" 192.168.4.1"); + } + // Connect to and initialise WiFi network + digitalWrite(LED_BUILTIN, HIGH); + startWiFi(_HOSTNAME, 240); // timeout 4 minuten + digitalWrite(LED_BUILTIN, LOW); + + startMDNS(settingHostname); + + DebugTln("Get time from NTP"); + timeSync.setup(); + timeSync.sync(300); + time(&now); + if (localtime(&now)->tm_year > 120) + { + timeSynced = true; + Serial.println("Time synchronized with NTP Service"); + } + else + { + timeSynced = false; + Serial.println("Could not synchronize time with NTP Service"); + } + + time(&now); + Serial.println("-------------------------------------------------------------------------------"); + if (!getTheLocalTime(&timeinfo, 10000)) + { + Debugln("Time : Failed to obtain time!"); + } + else + { + Debugf( "Time : %04d-%02d-%02d %02d:%02d:%02d\r\n", localtime(&now)->tm_year+1900 + , localtime(&now)->tm_mon+1 + , localtime(&now)->tm_mday + , localtime(&now)->tm_hour + , localtime(&now)->tm_min + , localtime(&now)->tm_sec); + } + + nrReboots++; + writeLastStatus(); + //writeToLog("=========REBOOT=========================="); + + snprintf(cMsg, sizeof(cMsg), "Last reset reason: [%s]", ESP.getResetReason().c_str()); + DebugTln(cMsg); + //writeToLog(cMsg); + + Serial.print("\nGebruik 'telnet "); + Serial.print (WiFi.localIP()); + Serial.println("' voor verdere debugging\r\n"); + +//================ Start HTTP Server ================================ + setupFSexplorer(); + httpServer.serveStatic("/FSexplorer.png", LittleFS, "/FSexplorer.png"); + httpServer.on("/", sendIndexPage); + httpServer.on("/index", sendIndexPage); + httpServer.on("/index.html",sendIndexPage); + httpServer.serveStatic("/index.css", LittleFS, "/index.css"); + httpServer.serveStatic("/index.js", LittleFS, "/index.js"); + // all other api calls are catched in FSexplorer onNotFounD! + httpServer.on("/api", HTTP_GET, processAPI); + + + httpServer.begin(); + DebugTln("\nServer started\r"); + + // Set up first message as the IP address + sprintf(actMessage, "%03d.%03d.%d.%d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]); + DebugTf("\nAssigned IP[%s]\r\n", actMessage); + P.displayScroll(actMessage, PA_LEFT, PA_NO_EFFECT, (MAX_SPEED - settingTextSpeed)); + P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); + + valueIntensity = calculateIntensity(); // read analog input pin 0 + + P.setIntensity(valueIntensity); + newsMsgID = 0; + do { yield(); } while( !P.displayAnimate() ); + + P.setFont(ExtASCII); + + inFX = 0; + outFX= 0; + + for (int i=0; i<=settingNewsMaxMsg; i++) + { + writeFileById("NWS", i, ""); + //DebugTf("readFileById(NWS, %d)\r\n", i); + //readFileById("NWS", i); + } + +} // setup() + + +//===================================================================== +void loop() +{ + httpServer.handleClient(); + MDNS.update(); + yield(); + + if ((millis() > weerTimer) && (strlen(settingWeerLiveAUTH) > 5)) + { + weerTimer = millis() + (settingWeerLiveInterval * (300 * 1000)); // Interval in Minutes! + if (settingWeerLiveInterval > 0) getWeerLiveData(); + } + + if ((millis() > newsapiTimer) && (strlen(settingNewsAUTH) > 5)) + { + newsapiTimer = millis() + (settingNewsInterval * (300 * 1000)); // Interval in Minutes! + if (settingNewsInterval > 0) + { + if (!getNewsapiData()) //-- first try ... + { + delay(100); + if (!getNewsapiData()) //-- second try ... + { + //-- try again in two(2) minutes ... + newsapiTimer = millis() + (2 * (60 * 1000)); // Interval in Minutes! + } + } + } + } + + if (P.displayAnimate()) // done with animation, ready for next message + { + yield(); + msgType++; + DebugTf("msgType[%d]\r\n", msgType); + time(&now); + if (localtime(&now)->tm_year > 120) timeSynced = true; + + + switch(msgType) + { + case 1: if (!(millis() > timeTimer)) { DebugTln("Not yet time to display weekday"); return; } + if (!timeSynced) { DebugTf("Time not (yet) synced!!\n"); return; } + inFX = random(0, ARRAY_SIZE(effect)); + outFX = random(0, ARRAY_SIZE(effect)); + snprintf(actMessage, LOCAL_SIZE, weekDayName[localtime(&now)->tm_wday+1]); + snprintf(onTickerMessage, 120, "%s", actMessage); + DebugT(" ["); Debug(onTickerMessage); Debugln("]"); + P.displayClear(); + P.displayText(actMessage, PA_CENTER, (MAX_SPEED - settingTextSpeed), 1000, effect[inFX], effect[outFX]); + DebugTf("Animate IN[%d], OUT[%d] %s\r\n", inFX, outFX, actMessage); + break; + case 2: if (!(millis() > timeTimer)) { DebugTln("Not yet time to display the time"); return; } + if (!timeSynced) { DebugTf("Time not (yet) synced!!\n"); return; } + timeTimer = millis() + 60000; + inFX = random(0, ARRAY_SIZE(effect)); + outFX = random(0, ARRAY_SIZE(effect)); + sprintf(actMessage, "%s", updateTime()); + snprintf(onTickerMessage, 120, "%s", actMessage); + DebugT(" ["); Debug(onTickerMessage); Debugln("]"); + P.displayText(actMessage, PA_CENTER, (MAX_SPEED - settingTextSpeed), 2000, effect[inFX], effect[outFX]); + DebugTf("Animate IN[%d], OUT[%d] %s\r\n", inFX, outFX, actMessage); + break; + case 3: nextLocalBericht(); + P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); + break; + case 6: nextLocalBericht(); + P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); + break; + case 4: + case 5: + case 7: + case 8: if (settingNewsInterval > 0) + nextNieuwsBericht(); + else nextLocalBericht(); + P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); + break; + case 9: if (settingWeerLiveInterval > 0) + { + snprintf(actMessage, LOCAL_SIZE, "** %s **", tempMessage); + Debugf("\t[%s]\r\n", actMessage); + utf8Ascii(actMessage); + } + else nextLocalBericht(); + P.setTextEffect(PA_SCROLL_LEFT, PA_NO_EFFECT); + break; + case 10: if (settingNewsInterval > 0) + nextNieuwsBericht(); + else nextLocalBericht(); + break; + default: msgType = 0; + return; + + } // switch() + + //DebugTln(actMessage); + valueIntensity = calculateIntensity(); // read analog input pin 0 + DebugTf("Intensity set to [%d]\r\n", valueIntensity); + P.setIntensity(valueIntensity); + // Tell Parola we have a new animation + P.displayReset(); + DebugTln("End of displayAnimate().."); + + } // dislayAnimate() + + +} // loop() + + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/FSexplorer.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/FSexplorer.ino new file mode 100644 index 0000000..93e0c24 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/FSexplorer.ino @@ -0,0 +1,331 @@ +/* +*************************************************************************** +** Program : FSexplorer +** Version : 2.0 10-05-202 +** +** Mostly stolen from https://www.arduinoforum.de/User-Fips +** For more information visit: https://fipsok.de +** See also https://www.arduinoforum.de/arduino-Thread-LittleFS-DOWNLOAD-UPLOAD-DELETE-Esp8266-NodeMCU +** +*************************************************************************** + Copyright (c) 2018 Jens Fleischer. All rights reserved. + + This file is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +******************************************************************* +** Usage: +** +** setup() +** { +** setupFSexplorer(); +** httpServer.serveStatic("/FSexplorer.png", LittleFS, "/FSexplorer.png"); +** httpServer.on("/", sendIndexPage); +** httpServer.on("/index", sendIndexPage); +** httpServer.on("/index.html",sendIndexPage); +** httpServer.begin(); +** } +** +** loop() +** { +** httpServer.handleClient(); +** . +** . +** } +*/ + +#define MAX_FILES_IN_LIST 25 + +const char Helper[] = R"( +
You first need to upload these two files: +
    +
  • FSexplorer.html
  • +
  • FSexplorer.css
  • +
+
+ + +
+
or you can use the Flash Utility to flash firmware or LittleFS! +
+ +
+)"; +const char Header[] = "HTTP/1.1 303 OK\r\nLocation:FSexplorer.html\r\nCache-Control: no-cache\r\n"; + +//===================================================================================== +void setupFSexplorer() // Funktionsaufruf "spiffs();" muss im Setup eingebunden werden +{ + LittleFS.begin(); + + if (LittleFS.exists("/FSexplorer.html")) + { + httpServer.serveStatic("/FSexplorer.html", LittleFS, "/FSexplorer.html"); + httpServer.serveStatic("/FSexplorer", LittleFS, "/FSexplorer.html"); + } + else + { + httpServer.send(200, "text/html", Helper); //Upload the FSexplorer.html + } + httpServer.on("/api/listfiles", APIlistFiles); + httpServer.on("/LittleFSformat", formatLittleFS); + httpServer.on("/upload", HTTP_POST, []() {}, handleFileUpload); + httpServer.on("/ReBoot", reBootESP); + httpServer.on("/update", updateFirmware); + httpServer.onNotFound([]() + { + if (Verbose) DebugTf("in 'onNotFound()'!! [%s] => \r\n", String(httpServer.uri()).c_str()); + if (httpServer.uri().indexOf("/api/") == 0) + { + if (Verbose) DebugTf("next: processAPI(%s)\r\n", String(httpServer.uri()).c_str()); + processAPI(); + } + else if (httpServer.uri() == "/") + { + DebugTln("index requested.."); + sendIndexPage(); + } + else + { + DebugTf("next: handleFile(%s)\r\n" + , String(httpServer.urlDecode(httpServer.uri())).c_str()); + if (!handleFile(httpServer.urlDecode(httpServer.uri()))) + { + httpServer.send(404, "text/plain", "FileNotFound\r\n"); + } + } + }); + +} // setupFSexplorer() + + +//===================================================================================== +void APIlistFiles() // Senden aller Daten an den Client +{ + FSInfo LittleFSinfo; + + typedef struct _fileMeta { + char Name[30]; + int32_t Size; + } fileMeta; + + _fileMeta dirMap[MAX_FILES_IN_LIST+1]; + int fileNr = 0; + + Dir dir = LittleFS.openDir("/"); // List files on LittleFS + while (dir.next() && (fileNr < MAX_FILES_IN_LIST)) + { + dirMap[fileNr].Name[0] = '\0'; + //strncat(dirMap[fileNr].Name, dir.fileName().substring(1).c_str(), 29); // remove leading '/' + strncat(dirMap[fileNr].Name, dir.fileName().c_str(), 29); + dirMap[fileNr].Size = dir.fileSize(); + fileNr++; + } + DebugTf("fileNr[%d], Max[%d]\r\n", fileNr, MAX_FILES_IN_LIST); + + // -- bubble sort dirMap op .Name-- + for (int8_t y = 0; y < fileNr; y++) { + yield(); + for (int8_t x = y + 1; x < fileNr; x++) { + //DebugTf("y[%d], x[%d] => seq[y][%s] / seq[x][%s] ", y, x, dirMap[y].Name, dirMap[x].Name); + if (compare(String(dirMap[x].Name), String(dirMap[y].Name))) + { + //Debug(" !switch!"); + fileMeta temp = dirMap[y]; + dirMap[y] = dirMap[x]; + dirMap[x] = temp; + } /* end if */ + //Debugln(); + } /* end for */ + } /* end for */ + + if (fileNr >= MAX_FILES_IN_LIST) + { + fileNr = MAX_FILES_IN_LIST; + dirMap[fileNr].Name[0] = '\0'; + //--- if you change this message you also have to + //--- change FSexplorer.html + strncat(dirMap[fileNr].Name, "More files not listed ..", 29); + dirMap[fileNr].Size = 0; + fileNr++; + } + + String temp = "["; + for (int f=0; f < fileNr; f++) + { + DebugTf("[%3d] >> [%s]\r\n", f, dirMap[f].Name); + if (temp != "[") temp += ","; + temp += R"({"name":")" + String(dirMap[f].Name) + R"(","size":")" + formatBytes(dirMap[f].Size) + R"("})"; + } + + LittleFS.info(LittleFSinfo); + temp += R"(,{"usedBytes":")" + formatBytes(LittleFSinfo.usedBytes * 1.05) + R"(",)" + // Berechnet den verwendeten Speicherplatz + 5% Sicherheitsaufschlag + R"("totalBytes":")" + formatBytes(LittleFSinfo.totalBytes) + R"(","freeBytes":")" + // Zeigt die Größe des Speichers + (LittleFSinfo.totalBytes - (LittleFSinfo.usedBytes * 1.05)) + R"("}])"; // Berechnet den freien Speicherplatz + 5% Sicherheitsaufschlag + + httpServer.send(200, "application/json", temp); + +} // APIlistFiles() + + +//===================================================================================== +bool handleFile(String&& path) +{ + if (httpServer.hasArg("delete")) + { + DebugTf("Delete -> [%s]\n\r", httpServer.arg("delete").c_str()); + LittleFS.remove(httpServer.arg("delete")); // Datei löschen + httpServer.sendContent(Header); + return true; + } + if (!LittleFS.exists("/FSexplorer.html")) httpServer.send(200, "text/html", Helper); //Upload the FSexplorer.html + if (path.endsWith("/")) path += "index.html"; + return LittleFS.exists(path) ? ({File f = LittleFS.open(path, "r"); httpServer.streamFile(f, contentType(path)); f.close(); true;}) : false; + +} // handleFile() + + +//===================================================================================== +void handleFileUpload() +{ + static File fsUploadFile; + HTTPUpload& upload = httpServer.upload(); + if (upload.status == UPLOAD_FILE_START) + { + if (upload.filename.length() > 30) + { + upload.filename = upload.filename.substring(upload.filename.length() - 30, upload.filename.length()); // Dateinamen auf 30 Zeichen kürzen + } + Debugln("FileUpload Name: " + upload.filename); + fsUploadFile = LittleFS.open("/" + httpServer.urlDecode(upload.filename), "w"); + } + else if (upload.status == UPLOAD_FILE_WRITE) + { + Debugln("FileUpload Data: " + (String)upload.currentSize); + if (fsUploadFile) + fsUploadFile.write(upload.buf, upload.currentSize); + } + else if (upload.status == UPLOAD_FILE_END) + { + if (fsUploadFile) + fsUploadFile.close(); + Debugln("FileUpload Size: " + (String)upload.totalSize); + httpServer.sendContent(Header); + } + +} // handleFileUpload() + + +//===================================================================================== +void formatLittleFS() +{ //Formatiert den Speicher + if (!LittleFS.exists("/!format")) return; + DebugTln(F("Format LittleFS")); + LittleFS.format(); + httpServer.sendContent(Header); + +} // formatLittleFS() + +//===================================================================================== +const String formatBytes(size_t const& bytes) +{ + return (bytes < 1024) ? String(bytes) + " Byte" : (bytes < (1024 * 1024)) ? String(bytes / 1024.0) + " KB" : String(bytes / 1024.0 / 1024.0) + " MB"; + +} //formatBytes() + +//===================================================================================== +const String &contentType(String& filename) +{ + if (filename.endsWith(".htm") || filename.endsWith(".html")) filename = "text/html"; + else if (filename.endsWith(".css")) filename = "text/css"; + else if (filename.endsWith(".js")) filename = "application/javascript"; + else if (filename.endsWith(".json")) filename = "application/json"; + else if (filename.endsWith(".png")) filename = "image/png"; + else if (filename.endsWith(".gif")) filename = "image/gif"; + else if (filename.endsWith(".jpg")) filename = "image/jpeg"; + else if (filename.endsWith(".ico")) filename = "image/x-icon"; + else if (filename.endsWith(".xml")) filename = "text/xml"; + else if (filename.endsWith(".pdf")) filename = "application/x-pdf"; + else if (filename.endsWith(".zip")) filename = "application/x-zip"; + else if (filename.endsWith(".gz")) filename = "application/x-gzip"; + else filename = "text/plain"; + return filename; + +} // &contentType() + +//===================================================================================== +bool freeSpace(uint16_t const& printsize) +{ + FSInfo LittleFSinfo; + LittleFS.info(LittleFSinfo); + Debugln(formatBytes(LittleFSinfo.totalBytes - (LittleFSinfo.usedBytes * 1.05)) + " im Spiffs frei"); + return (LittleFSinfo.totalBytes - (LittleFSinfo.usedBytes * 1.05) > printsize) ? true : false; + +} // freeSpace() + + +//===================================================================================== +void updateFirmware() +{ + DebugTln(F("Redirect to updateIndex ..")); + doRedirect("wait ... ", 1, "/updateIndex", false); + +} // updateFirmware() + +//===================================================================================== +void reBootESP() +{ + DebugTln(F("Redirect and ReBoot ..")); + doRedirect("Reboot ESP - lichtKrant ..", 60, "/", true); + +} // reBootESP() + +//===================================================================================== +void doRedirect(String msg, int wait, const char* URL, bool reboot) +{ + String redirectHTML = + "" + "" + "" + "" + "Redirect to Main Program" + "" + "

FSexplorer

" + "

"+msg+"

" + "
" + "
Redirect over  
" + "
"+String(wait)+"
" + "
  seconden ...
" + "
 
" + "
" + "" + "


If you are not redirected automatically, click this Main Program." + " " + "\r\n"; + + DebugTln(msg); + httpServer.send(200, "text/html", redirectHTML); + if (reboot) + { + delay(5000); + ESP.restart(); + delay(5000); + } + +} // doRedirect() diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/TimeSyncClass.cpp b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/TimeSyncClass.cpp new file mode 100644 index 0000000..2c60633 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/TimeSyncClass.cpp @@ -0,0 +1,97 @@ +/* +** With thanks to Michael +*/ + +#include "TimeSyncClass.h" + +TimeSync::TimeSync() {} + +/** + * Sets up the time synchronization with the specified timezone. + * + * @param timeZone The timezone to set. + * + * @return void + * + * @throws None + */ +void TimeSync::setup(const char *timeZone) +{ + configTime(0, 0, "pool.ntp.org", "time.nist.gov", "time.google.com"); + setenv("TZ", timeZone, 1); + tzset(); + + Serial.printf("Timezone set to: %s\r\n", timeZone); +} + +/** + * Checks if the system time is synchronized. + * + * @return True if the system time is synchronized, false otherwise. + * + * @throws None + */ +bool TimeSync::isSynced() +{ + time_t now; + time(&now); + + return (bool)(localtime(&now)->tm_year > 120); +} + +/** + * Synchronizes the system time with the NTP service. + * + * @param maxTry The maximum number of attempts to synchronize the time. + * + * @return True if the system time is synchronized with the NTP service, + * False otherwise. + * + * @throws None + */ +bool TimeSync::sync(uint16_t maxTry) +{ + time_t now; + time(&now); + uint8_t tries = 0; + + Serial.print("Get time from NTP service ."); + do + { + time(&now); + delay(20); + if ((tries%10)==0) + { + Serial.print(". "); + } + tries++; + } while ((localtime(&now)->tm_year <= 120) && (tries < maxTry)); + + if (localtime(&now)->tm_year > 120) + Serial.println(" Synchronized with NTP Service!"); + else Serial.println(" NOT (YET) SYNCED!"); + + return localtime(&now)->tm_year > 120; + +} + +/** + * Logs the current time. + * + * @return void + * + * @throws None + */ +void TimeSync::logTime() +{ + time_t now; + time(&now); + struct tm timeinfo; + + if (localtime(&now)->tm_year <= 120) + { + Serial.println("Failed to obtain time"); + return; + } + Serial.printf("%s", asctime(&timeinfo)); +} diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/TimeSyncClass.h b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/TimeSyncClass.h new file mode 100644 index 0000000..877b26c --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/TimeSyncClass.h @@ -0,0 +1,41 @@ +/* +** See MIT license at the end of this file +*/ + +#include + +class TimeSync +{ + public: + TimeSync(); + void setup(const char *timeZone = "CET-1CEST,M3.5.0,M10.5.0/3"); + bool sync(uint16_t maxTry); + bool isSynced(); + void logTime(); + +}; + +/********************************************************************************** + * MIT License + * + * Copyright (c) 2024 Willem Aandewiel (mrWheel) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ********************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/FSexplorer.css b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/FSexplorer.css new file mode 100644 index 0000000..d2345bf --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/FSexplorer.css @@ -0,0 +1,76 @@ +/* +*************************************************************************** +** Program : FSexplorer.html, part of ESP_ticker +** Version : v1.3.1 +** For more information visit: https://fipsok.de +*************************************************************************** +*/ +body { + font-family: sans-serif; + //background-color: #a9a9a9; + background-color: lightblue; + display: flex; + flex-flow: column; + align-items: left; +} +h1,h2 { + color: #e1e1e1; + text-shadow: 2px 2px 2px black; +} +a:link { + text-decoration: none; +} +input { + height: 35px; + font-size: 13px; +} +h1+main { + display: flex; +} +section { + display: flex; + flex-direction: column; + padding: 0.2em; +} +hr { + border: 0; + clear:both; + display:block; + width: 99%; + background-color:darkblue; + height: 5px; +} + +#left { + align-items: flex-end; + text-shadow: 1px 1px 2px #757474; +} +.note { + background-color: salmon; + padding: 0.5em; + margin-top: 1em; + text-align: center; + max-width: 320px; + border-radius: 0.5em; +} +.button { + width: 150px; + height: 40px; + font-size: 14px; + margin-top: 1em; + cursor: pointer; + background-color: lightgray; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.7); +} +[type=submit] { + height: 40px; + min-width: 70px; +} +[value^=Format] { + background-color: #ddd; +} +[title] { + background-color: silver; + font-size: 16px; + width: 125px; +} diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/FSexplorer.html b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/FSexplorer.html new file mode 100644 index 0000000..edb5c0f --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/FSexplorer.html @@ -0,0 +1,134 @@ + + + + + + + + + FSexplorer ESP + + + +

FSexplorer ESP

+
+
+
+
+ +
+ + +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ + diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/FSexplorer.png b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/FSexplorer.png new file mode 100644 index 0000000..74df403 Binary files /dev/null and b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/FSexplorer.png differ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/Uno_reset-en_snippet.jpg b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/Uno_reset-en_snippet.jpg new file mode 100644 index 0000000..6e8b0b4 Binary files /dev/null and b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/Uno_reset-en_snippet.jpg differ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/favicon.ico b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/favicon.ico new file mode 100644 index 0000000..9d83d2a Binary files /dev/null and b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/favicon.ico differ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/index.css b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/index.css new file mode 100644 index 0000000..307b069 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/index.css @@ -0,0 +1,273 @@ +/* +*************************************************************************** +** Program : index.css, part of ESP_ticker +** Version : v1.3.1 +** +** Copyright (c) 2020 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ +* { + font-family: Arial, Helvetica, sans-serif; +} + +html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-family: 'Dosis', sans-serif; + line-height: 1.6; + color: black; + background: #e6ffff; +} + +table { + color: black; + border-collapse: collapse; + width: 90%; + background: lightblue; +} + +th { + padding-left: 15px; + padding-right: 15px; + white-space: nowrap; + border-right: 1px solid #ddd; + border-left: 1px solid #ddd; + vertical-align: bottom; +} + +td { + padding-left: 15px; + padding-right: 15px; + white-space: nowrap; + border-bottom: 1px solid #ddd; + border-right: 1px solid #ddd; + border-left: 1px solid #ddd; + vertical-align: bottom; +} + +tfoot { + font-size: 12px; + color: darkblue; +} + +input { + padding-top: 3px; + padding-bottom: 2px; + font-size: 12pt; +} + +.settingDiv { + background: lightblue; + white-space: nowrap; +} + +.outer-div { + /*width: 90%; */ + text-align: center; + background-color: #e6ffff +} +.inner-div { + display: inline-block; + /* + margin: 0 auto; + padding: 3px; + background-color: lightblue + */ +} + +.container-card { + padding: 50; + margin: 50; + list-style: none; + flex-direction: row; /*row | row-reverse | column | column-reverse*/ + + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + + -webkit-flex-flow: row wrap; + justify-content: space-around; /* flex-start | flex-end | center | space-between | space-around | space-evenly */ + +} + +.container-box { + padding: 0; + margin: 0; + list-style: none; + flex-direction: column; /*row | row-reverse | column | column-reverse*/ + + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + + -webkit-flex-flow: column wrap; + justify-content: flex-start; + margin-top: 10px; + +} + +/*--------------------- N A V - B A R -----------------*/ +.nav-container { + list-style: none; + margin: 0; + + background: #00bffe; + + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex-start; + -webkit-flex-flow: row wrap; + min-height: 24px; + +} + +.nav-img { + top: 1px; + display: inline-block; + width: 40px; + height: 40px; +} + +.nav-item { + display: inline-block; + font-size: 16px; + padding: 10px 0; + height: 20px; + width: 110px; + border: none; + color: white; + +} + +.nav-left { + flex: 2 1 0; + text-align: center; +} + +.nav-right { + flex: 1 1 0; + text-align: right; + width: 200px; +} + +.nav-container a:hover { + color: black; + background: skyblue; /* only for FSexplorer - Rest does not work */ +} + +.nav-clock { + //top: 1px; + color: black; + float: right; + font-size: small; + font-weight:bold; + text-align: right; + background: white; + width: 200px; + padding-right: 10px; + background: #e6ffff; +} + +/*-------------------------*/ +/*** +@media all and (max-width: 600px) { + .nav-container { + justify-content: space-around; + } +} + +@media all and (max-width: 400px) { + .nav-container { + -webkit-flex-flow: column wrap; + flex-flow: column wrap; + padding: 0; + } +} +***/ + +.tabButton { + background-color: white; +} + +.tabButton a:hover { + background-color: gray; +} + + +.header h1 span { + position: relative; + top: 1px; + left: 10px; +} + +.bottom { + position: fixed; + font-size: small; + color: gray; + bottom:0; +} +.right-0 {right: 0; padding-right: 10px; } +.left-0 {left: 0; padding-left: 10px; } + +//--- next 4 are for the API doc page -------- +.div1 { + float: left; + margin-left: 20px; + margin-top: 10px; +} +.div1 a:link, a:visited { + background-color: deepSkyBlue; + color: white; + padding: 5px 15px; + width: 210px; + text-align: right; + text-decoration: none; + display: inline-block; +} +.div1 a:hover, a:active { + background-color: deepSkyBlue; + color: black; +} + +.div2 { + margin-left: 280px; + margin-top: -52px; +} + + +/* define tag selectors () -------------------------------------------------- */ + +button { width: 100px; } + +/* +*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/index.js b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/index.js new file mode 100644 index 0000000..21d1177 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/index.js @@ -0,0 +1,533 @@ +/* +*************************************************************************** +** Program : index.js, part of ESP_ticker +** Version : v1.6.0 (02-01-2021) +** +** Copyright (c) 2021 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + const APIGW='http://'+window.location.host+'/api/'; + +"use strict"; + + let needReload = true; + + window.onload=bootsTrapMain; + window.onfocus = function() { + if (needReload) { + window.location.reload(true); + } + }; + + //============================================================================ + function bootsTrapMain() { + console.log("bootsTrapMain()"); + document.getElementById('saveMsg').addEventListener('click',function() + {saveMessages();}); + document.getElementById('M_FSexplorer').addEventListener('click',function() + { console.log("newTab: goFSexplorer"); + location.href = "/FSexplorer"; + }); + document.getElementById('S_FSexplorer').addEventListener('click',function() + { console.log("newTab: goFSexplorer"); + location.href = "/FSexplorer"; + }); + document.getElementById('back').addEventListener('click',function() + { console.log("newTab: goBack"); + location.href = "/"; + }); + document.getElementById('Settings').addEventListener('click',function() + {settingsPage();}); + document.getElementById('saveSettings').addEventListener('click',function() + {saveSettings();}); + needReload = false; + refreshDevTime(); + refreshDevInfo(); + refreshMessages(); + + document.getElementById("displayMainPage").style.display = "block"; + document.getElementById("displaySettingsPage").style.display = "none"; + + } // bootsTrapMain() + + function settingsPage() + { + document.getElementById("displayMainPage").style.display = "none"; + + var settingsPage = document.getElementById("settingsPage"); + refreshSettings(); + document.getElementById("displaySettingsPage").style.display = "block"; + + } // settingsPage() + + + //============================================================================ + function refreshDevTime() + { + //console.log("Refresh api/v0/devtime .."); + fetch(APIGW+"v0/devtime") + .then(response => response.json()) + .then(json => { + console.log("parsed .., data is ["+ JSON.stringify(json)+"]"); + for( let i in json.devtime ){ + if (json.devtime[i].name == "dateTime") + { + //console.log("Got new time ["+json.devtime[i].value+"]"); + document.getElementById('theTime').innerHTML = json.devtime[i].value; + } + } + }) + .catch(function(error) { + var p = document.createElement('p'); + p.appendChild( + document.createTextNode('Error: ' + error.message) + ); + }); + + } // refreshDevTime() + + + //============================================================================ + function refreshDevInfo() + { + document.getElementById('devName').innerHTML = ""; + fetch(APIGW+"v0/devinfo") + .then(response => response.json()) + .then(json => { + //console.log("parsed .., data is ["+ JSON.stringify(json)+"]"); + data = json.devinfo; + for( let i in data ) + { + if (data[i].name == "fwversion") + { + document.getElementById('devVersion').innerHTML = json.devinfo[i].value; + + } else if (data[i].name == 'hostname') + { + document.getElementById('devName').innerHTML += data[i].value+" "; + + } else if (data[i].name == 'ipaddress') + { + document.getElementById('devName').innerHTML += " ("+data[i].value+") "; + } + } + }) + .catch(function(error) { + var p = document.createElement('p'); + p.appendChild( + document.createTextNode('Error: ' + error.message) + ); + }); + } // refreshDevInfo() + + //============================================================================ + function refreshMessages() + { + console.log("refreshMessages() .."); + data = {}; + fetch(APIGW+"v0/messages") + .then(response => response.json()) + .then(json => { + console.log("then(json => ..)"); + msg = json.messages; + for( let i in msg ) + { + msg[i].value = msg[i].value.replaceAll("@1@", ":"); + msg[i].value = msg[i].value.replaceAll("@2@", "{"); + msg[i].value = msg[i].value.replaceAll("@3@", "}"); + msg[i].value = msg[i].value.replaceAll("@4@", ","); + msg[i].value = msg[i].value.replaceAll("@5@", "\\"); + msg[i].value = msg[i].value.replaceAll("@6@", "\%"); + + console.log("["+msg[i].name+"]=>["+msg[i].value+"]"); + var messages = document.getElementById('mainPage'); + if( ( document.getElementById("msgR_"+msg[i].name)) == null ) + { + var rowDiv = document.createElement("div"); + rowDiv.setAttribute("class", "msgDiv"); + rowDiv.setAttribute("id", "msgR_"+msg[i].name); + rowDiv.setAttribute("style", "text-align: right;"); + rowDiv.style.marginLeft = "10px"; + rowDiv.style.marginRight = "10px"; + rowDiv.style.width = "850px"; + rowDiv.style.border = "thick solid lightblue"; + rowDiv.style.background = "lightblue"; + //--- field Name --- + var fldDiv = document.createElement("div"); + fldDiv.setAttribute("style", "margin-right: 10px;"); + fldDiv.style.width = "30px"; + fldDiv.style.float = 'left'; + fldDiv.textContent = msg[i].name; + rowDiv.appendChild(fldDiv); + //--- input --- + var inputDiv = document.createElement("div"); + inputDiv.setAttribute("style", "text-align: left;"); + + var sInput = document.createElement("INPUT"); + sInput.setAttribute("id", "M_"+msg[i].name); + sInput.setAttribute("style", "background: white"); + + if (msg[i].type == "s") + { + sInput.setAttribute("type", "text"); + sInput.setAttribute("maxlength", msg[i].maxlen); + sInput.setAttribute("size", 100); + + sInput.setAttribute("value", msg[i].value); + //console.log("addEventListener(M_"+msg[i].name+")"); + sInput.addEventListener('change', + function() { setBackGround("M_"+msg[i].name, "lightgray"); }, + false + ); + } + inputDiv.appendChild(sInput); + + rowDiv.appendChild(inputDiv); + messages.appendChild(rowDiv); + } + else + { + document.getElementById("M_"+msg[i].name).style.background = "white"; + document.getElementById("M_"+msg[i].name).value = data[i].value; + } + } + document.getElementById("waiting").innerHTML = ""; + }) + .catch(function(error) { + var p = document.createElement('p'); + p.appendChild( + document.createTextNode('Error: ' + error.message) + ); + }); + + } // refreshMessages() + + + //============================================================================ + function refreshSettings() + { + console.log("refreshSettings() .."); + data = {}; + fetch(APIGW+"v0/settings") + .then(response => response.json()) + .then(json => { + console.log("then(json => ..)"); + data = json.settings; + for( let i in data ) + { + console.log("["+data[i].name+"]=>["+data[i].value+"]"); + var settings = document.getElementById('settingsPage'); + if( ( document.getElementById("D_"+data[i].name)) == null ) + { + var rowDiv = document.createElement("div"); + rowDiv.setAttribute("class", "settingDiv"); + //----rowDiv.setAttribute("id", "settingR_"+data[i].name); + rowDiv.setAttribute("id", "D_"+data[i].name); + rowDiv.setAttribute("style", "text-align: right;"); + rowDiv.style.marginLeft = "10px"; + rowDiv.style.marginRight = "10px"; + rowDiv.style.width = "800px"; + rowDiv.style.border = "thick solid lightblue"; + rowDiv.style.background = "lightblue"; + //--- field Name --- + var fldDiv = document.createElement("div"); + fldDiv.setAttribute("style", "margin-right: 10px;"); + fldDiv.style.width = "270px"; + fldDiv.style.float = 'left'; + fldDiv.textContent = translateToHuman(data[i].name); + rowDiv.appendChild(fldDiv); + //--- input --- + var inputDiv = document.createElement("div"); + inputDiv.setAttribute("style", "text-align: left;"); + + var sInput = document.createElement("INPUT"); + //----sInput.setAttribute("id", "setFld_"+data[i].name); + sInput.setAttribute("id", data[i].name); + + if (data[i].type == "s") + { + sInput.setAttribute("type", "text"); + sInput.setAttribute("maxlength", data[i].maxlen); + sInput.setAttribute("size", 50); + } + else if (data[i].type == "f") + { + sInput.setAttribute("type", "number"); + sInput.max = data[i].max; + sInput.min = data[i].min; + sInput.step = (data[i].min + data[i].max) / 1000; + } + else if (data[i].type == "i") + { + sInput.setAttribute("type", "number"); + sInput.setAttribute("size", 10); + sInput.max = data[i].max; + sInput.min = data[i].min; + //sInput.step = (data[i].min + data[i].max) / 1000; + sInput.step = 1; + } + sInput.setAttribute("value", data[i].value); + sInput.addEventListener('change', + function() { setBackGround(data[i].name, "lightgray"); }, + false + ); + inputDiv.appendChild(sInput); + + rowDiv.appendChild(inputDiv); + settings.appendChild(rowDiv); + } + else + { + //----document.getElementById("setFld_"+data[i].name).style.background = "white"; + document.getElementById(data[i].name).style.background = "white"; + //----document.getElementById("setFld_"+data[i].name).value = data[i].value; + document.getElementById(data[i].name).value = data[i].value; + } + } + //console.log("-->done.."); + }) + .catch(function(error) { + var p = document.createElement('p'); + p.appendChild( + document.createTextNode('Error: ' + error.message) + ); + }); + + } // refreshSettings() + + + //============================================================================ + function saveMessages() + { + console.log("Saving messages .."); + let changes = false; + + //--- has anything changed? + var page = document.getElementById("mainPage"); + var mRow = page.getElementsByTagName("input"); + //var mRow = document.getElementById("mainPage").getElementsByTagName('div'); + for(var i = 0; i < mRow.length; i++) + { + //do something to each div like + var msgId = mRow[i].getAttribute("id"); + var field = msgId.substr(2); + //console.log("msgId["+msgId+", msgNr["+field+"]"); + value = document.getElementById(msgId).value; + //console.log("==> name["+field+"], value["+value+"]"); + + changes = false; + + if (getBackGround("M_"+field) == "lightgray") + { + setBackGround("M_"+field, "white"); + changes = true; + } + if (changes) { + console.log("Changes where made in ["+field+"]["+value+"]"); + //processWithTimeout([(data.length -1), 0], 2, data, sendPostReading); + sendPostMessages(field, value); + } + } + + } // saveMessages() + + + //============================================================================ + function saveSettings() + { + console.log("saveSettings() ..."); + let changes = false; + + //--- has anything changed? + var page = document.getElementById("settingsPage"); + var mRow = page.getElementsByTagName("input"); + //var mRow = document.getElementById("mainPage").getElementsByTagName('div'); + for(var i = 0; i < mRow.length; i++) + { + //do something to each div like + var msgId = mRow[i].getAttribute("id"); + var field = msgId; + //console.log("msgId["+msgId+", msgNr["+field+"]"); + value = document.getElementById(msgId).value; + //console.log("==> name["+field+"], value["+value+"]"); + + changes = false; + + if (getBackGround(field) == "lightgray") + { + setBackGround(field, "white"); + changes = true; + } + if (changes) { + console.log("Changes where made in ["+field+"]["+value+"]"); + var value = value.replace(/,/g, " "); + value = value.replace(/ /g, " "); + value = value.replace(/ /g, " "); + value = value.replace(/ /g, " "); + //processWithTimeout([(data.length -1), 0], 2, data, sendPostReading); + sendPostSetting(field, value); + } + } + + } // saveSettings() + + + //============================================================================ + function sendPostMessages(field, value) + { + //console.log("sendPostMessages(value): "+value+"]"); + value = value.replaceAll("\"", "'"); + value = value.replaceAll(":", "@1@"); + value = value.replaceAll("{", "@2@"); + value = value.replaceAll("}", "@3@"); + value = value.replaceAll(",", "@4@"); + value = value.replaceAll("\\", "@5@"); + value = value.replaceAll("\%", "@6@"); + console.log("sendPostMessages(value): "+value+"]"); + + const jsonString = {"name" : field, "value" : value}; + console.log("sendPostMessages(): "+JSON.stringify(jsonString)); + const other_params = { + headers : { "content-type" : "application/json; charset=UTF-8"}, + body : JSON.stringify(jsonString), + method : "POST", + mode : "cors" + }; + + fetch(APIGW+"v0/messages", other_params) + .then(function(response) { + }, function(error) { + console.log("Error["+error.message+"]"); //=> String + }); + + } // sendPostMessages() + + + //============================================================================ + function sendPostSetting(field, value) + { + const jsonString = {"name" : field, "value" : value}; + console.log("sending: "+JSON.stringify(jsonString)); + const other_params = { + headers : { "content-type" : "application/json; charset=UTF-8"}, + body : JSON.stringify(jsonString), + method : "POST", + mode : "cors" + }; + + fetch(APIGW+"v0/settings", other_params) + .then(function(response) { + //console.log(response.status ); //=> number 100–599 + //console.log(response.statusText); //=> String + //console.log(response.headers); //=> Headers + //console.log(response.url); //=> String + //console.log(response.text()); + //return response.text() + }, function(error) { + console.log("Error["+error.message+"]"); //=> String + }); + + } // sendPostSetting() + + + //============================================================================ + function translateToHuman(longName) { + //for(var index = 0; index < (translateFields.length -1); index++) + for(var index = 0; index < translateFields.length; index++) + { + if (translateFields[index][0] == longName) + { + return translateFields[index][1]; + } + }; + return longName; + + } // translateToHuman() + + + + //============================================================================ + function setBackGround(field, newColor) { + //console.log("setBackground("+field+", "+newColor+")"); + document.getElementById(field).style.background = newColor; + + } // setBackGround() + + + //============================================================================ + function getBackGround(field) { + //console.log("getBackground("+field+")"); + return document.getElementById(field).style.background; + + } // getBackGround() + + + //============================================================================ + function round(value, precision) { + var multiplier = Math.pow(10, precision || 0); + return Math.round(value * multiplier) / multiplier; + } + + + //============================================================================ + function printAllVals(obj) { + for (let k in obj) { + if (typeof obj[k] === "object") { + printAllVals(obj[k]) + } else { + // base case, stop recurring + console.log(obj[k]); + } + } + } // printAllVals() + + + var translateFields = [ + [ "author", "Auteur" ] + ,[ "localMaxMsg", "max. aantal boodschappen" ] + ,[ "textSpeed", "scroll snelheid" ] + ,[ "maxIntensity", "max. Intensiteit leds" ] + ,[ "LDRlowOffset", "LDR min. waarde" ] + ,[ "LDRhighOffset", "LDR max. waarde" ] + ,[ "weerliveAUTH", "weerlive.nl auth. token" ] + ,[ "weerliveLocation", "weerlive.nl locatie" ] + ,[ "weerliveInterval", "weerlive.nl refresh interval in minuten" ] + ,[ "newsapiAUTH", "newsapi.org auth. token" ] + ,[ "newsapiMaxMsg", "newsapi.nl max. items" ] + ,[ "newsapiInterval", "newapi.nl refresh interval in minuten" ] + ,[ "newsNoWords", "skip items met deze woorden" ] + ]; + + //============================================================================ + String.prototype.replaceAll = function(str1, str2, ignore) + { + return this.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignore?"gi":"g")),(typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2); + //return this.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignore?"gi":"g")),(typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2); + } + +/* +*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/newsFiles/LCL-001 b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/newsFiles/LCL-001 new file mode 100644 index 0000000..9f7a086 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/newsFiles/LCL-001 @@ -0,0 +1 @@ +Dit is een dummy local news file! \ No newline at end of file diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/newsFiles/NWS-001 b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/newsFiles/NWS-001 new file mode 100644 index 0000000..52e85e9 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/newsFiles/NWS-001 @@ -0,0 +1 @@ +Dit is een dummy news file! \ No newline at end of file diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/settings.ini b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/settings.ini new file mode 100644 index 0000000..c5da2e6 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/settings.ini @@ -0,0 +1,13 @@ +Hostname = ESPticker +localMaxMsg = 5 +textSpeed = 25 +maxIntensity = 6 +LDRlowOffset = 70 +LDRhighOffset = 700 +weerLiveAUTH = +weerLiveLocatie = Amsterdam +weerLiveInterval = 15 +newsAUTH = +newsNoWords = Voetbal show UEFA KNVB vannederland boulevard voetbalzone +newsMaxMsg = 4 +newsInterval = 15 diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/settings.png b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/settings.png new file mode 100644 index 0000000..2a29f4f Binary files /dev/null and b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/data/settings.png differ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/helperStuff.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/helperStuff.ino new file mode 100644 index 0000000..5483c37 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/helperStuff.ino @@ -0,0 +1,537 @@ +/* +*************************************************************************** +** Program : helperStuff, part of ESP_ticker +** +** Copyright (c) 2021 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +#include + +//=========================================================================================== +bool compare(String x, String y) +{ + for (int i = 0; i < min(x.length(), y.length()); i++) { + if (x[i] != y[i]) + { + return (bool)(x[i] < y[i]); + } + } + return x.length() < y.length(); + +} // compare() + + +//=========================================================================================== +boolean isValidIP(IPAddress ip) +{ + /* Works as follows: + * example: + * 127.0.0.1 + * 1 => 127||0||0||1 = 128>0 = true + * 2 => !(false || false) = true + * 3 => !(false || false || false || false ) = true + * 4 => !(true && true && true && true) = false + * 5 => !(false) = true + * true && true & true && false && true = false ==> correct, this is an invalid addres + * + * 0.0.0.0 + * 1 => 0||0||0||0 = 0>0 = false + * 2 => !(true || true) = false + * 3 => !(false || false || false || false) = true + * 4 => !(true && true && true && tfalse) = true + * 5 => !(false) = true + * false && false && true && true && true = false ==> correct, this is an invalid addres + * + * 192.168.0.1 + * 1 => 192||168||0||1 =233>0 = true + * 2 => !(false || false) = true + * 3 +> !(false || false || false || false) = true + * 4 => !(false && false && true && true) = true + * 5 => !(false) = true + * true & true & true && true && true = true ==> correct, this is a valid address + * + * 255.255.255.255 + * 1 => 255||255||255||255 =255>0 = true + * 2 => !(false || false ) = true + * 3 +> !(true || true || true || true) = false + * 4 => !(false && false && false && false) = true + * 5 => !(true) = false + * true && true && false && true && false = false ==> correct, this is an invalid address + * + * 0.123.12.1 => true && false && true && true && true = false ==> correct, this is an invalid address + * 10.0.0.0 => true && false && true && true && true = false ==> correct, this is an invalid address + * 10.255.0.1 => true && true && false && true && true = false ==> correct, this is an invalid address + * 150.150.255.150 => true && true && false && true && true = false ==> correct, this is an invalid address + * + * 123.21.1.99 => true && true && true && true && true = true ==> correct, this is annvalid address + * 1.1.1.1 => true && true && true && true && true = true ==> correct, this is annvalid address + * + * Some references on valid ip addresses: + * - https://www.quora.com/How-do-you-identify-an-invalid-IP-address + * + */ + boolean _isValidIP = false; + _isValidIP = ((ip[0] || ip[1] || ip[2] || ip[3])>0); // if any bits are set, then it is not 0.0.0.0 + _isValidIP &= !((ip[0]==0) || (ip[3]==0)); // if either the first or last is a 0, then it is invalid + _isValidIP &= !((ip[0]==255) || (ip[1]==255) || (ip[2]==255) || (ip[3]==255)) ; // if any of the octets is 255, then it is invalid + _isValidIP &= !(ip[0]==127 && ip[1]==0 && ip[2]==0 && ip[3]==1); // if not 127.0.0.0 then it might be valid + _isValidIP &= !(ip[0]>=224); // if ip[0] >=224 then reserved space + + DebugTf( "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + if (_isValidIP) + Debugln(F(" = Valid IP")); + else + Debugln(F(" = Invalid IP!")); + + return _isValidIP; + +} // isValidIP() + + +//=========================================================================================== +bool isNumericp(const char *timeStamp, int8_t len) +{ + for (int i=0; (i '9') + { + return false; + } + } + return true; + +} // isNumericp() + + +//=========================================================================================== +int8_t splitString(String inStrng, char delimiter, String wOut[], uint8_t maxWords) +{ + int16_t inxS = 0, inxE = 0, wordCount = 0; + + inStrng.trim(); + while(inxE < inStrng.length() && wordCount < maxWords) + { + inxE = inStrng.indexOf(delimiter, inxS); //finds location of first , + wOut[wordCount] = inStrng.substring(inxS, inxE); //captures first data String + wOut[wordCount].trim(); + //DebugTf("[%d] => [%c] @[%d] found[%s]\r\n", wordCount, delimiter, inxE, wOut[wordCount].c_str()); + inxS = inxE; + inxS++; + wordCount++; + } + // zero rest of the words + for(int i=wordCount; i< maxWords; i++) + { + wOut[wordCount][0] = 0; + } + // if not whole string processed place rest in last word + if (inxS < inStrng.length()) + { + wOut[maxWords-1] = inStrng.substring(inxS, inStrng.length()); // store rest of String + } + + return wordCount; + +} // splitString() + + + +//=========================================================================================== +void strConcat(char *dest, int maxLen, const char *src) +{ + if (strlen(dest) + strlen(src) < maxLen) + { + strcat(dest, src); + } + else + { + DebugTf("Combined string > %d chars\r\n", maxLen); + } + +} // strConcat() + + +//=========================================================================================== +void strConcat(char *dest, int maxLen, float v, int dec) +{ + static char buff[25]; + if (dec == 0) sprintf(buff,"%.0f", v); + else if (dec == 1) sprintf(buff,"%.1f", v); + else if (dec == 2) sprintf(buff,"%.2f", v); + else if (dec == 3) sprintf(buff,"%.3f", v); + else if (dec == 4) sprintf(buff,"%.4f", v); + else if (dec == 5) sprintf(buff,"%.5f", v); + else sprintf(buff,"%f", v); + + if (strlen(dest) + strlen(buff) < maxLen) + { + strcat(dest, buff); + } + else + { + DebugTf("Combined string > %d chars\r\n", maxLen); + } + +} // strConcat() + + +//=========================================================================================== +void strConcat(char *dest, int maxLen, int v) +{ + static char buff[25]; + sprintf(buff,"%d", v); + + if (strlen(dest) + strlen(buff) < maxLen) + { + strcat(dest, buff); + } + else + { + DebugTf("Combined string > %d chars\r\n", maxLen); + } + +} // strConcat() + + +//=========================================================================================== +void strToLower(char *src) +{ + for (int i = 0; i < strlen(src); i++) + { + if (src[i] == '\0') return; + if (src[i] >= 'A' && src[i] <= 'Z') + src[i] += 32; + } +} // strToLower() + +//=========================================================================================== +// a 'save' string copy +void strCopy(char *dest, int maxLen, const char *src, int frm, int to) +{ + int d=0; +//DebugTf("dest[%s], src[%s] max[%d], frm[%d], to[%d] =>\r\n", dest, src, maxLen, frm, to); + dest[0] = '\0'; + for (int i=0; i<=frm; i++) + { + if (src[i] == 0) return; + } + for (int i=frm; (src[i] != 0 && i<=to && d= maxLen! +void strCopy(char *dest, int maxLen, const char *src) +{ + dest[0] = '\0'; + strcat(dest, src); + +} // strCopy() + +//=========================================================================================== +// 'tttABCtDEtFGHttt' => 'ABCtDEtFGHttt' +void strLTrim(char *dest, int maxLen, const char tChar ) +{ + char tmp[maxLen]; + int tPos = 0; + bool done = false; + + tmp[0] = '\0'; + + for (int dPos=0; (dPos 'tttABCtDEtFGH' +void strRTrim(char *dest, int maxLen, const char tChar ) +{ + char tmp[maxLen]; + int dPos, tPos, dMax; + bool done = false; + + for(dMax=0; dMax=0 && !done); dPos--) + { + if (dest[dPos] == tChar) + { + tPos = dPos; + tmp[tPos] = '\0'; + } + else done = true; + } + dPos++; + for(dMax = 0; dMax <= dPos; dMax++) + { + tmp[dMax] = dest[dMax]; + } + tmp[dMax+1] = '\0'; + strCopy(dest, maxLen, tmp); + +} // strRTrim() + +//=========================================================================================== +// 'tttABCtDEtFGHttt' => 'ABCtDEtFGH' +void strTrim(char *dest, int maxLen, const char tChar ) +{ + char sTmp[maxLen]; + + strCopy(sTmp, maxLen, dest); + strLTrim(sTmp, maxLen, tChar); + strRTrim(sTmp, maxLen, tChar); + strCopy(dest, maxLen, sTmp); + +} // strTrim() + +//=========================================================================================== +void strRemoveAll(char *dest, int maxLen, const char tChar) +{ + char tmp[maxLen]; + int tPos = 0; + tmp[0] = '\0'; + for (int dPos=0; (dPos= ' ' && dest[dPos] <= '~') // space = 32, '~' = 127 + { + tmp[tPos++] = dest[dPos]; + } + } + tmp[tPos] = '\0'; + strCopy(dest, maxLen, tmp); + +} // strTrimCntr() + +//=========================================================================================== +int strIndex(const char *haystack, const char *needle, int start) +{ + // strindex(hay, needle) ???? + char *p = strstr(haystack+start, needle); + if (p) { + //DebugTf("found [%s] at position [%d]\r\n", needle, (p - haystack)); + return (p - haystack); + } + return -1; + +} // strIndex() + +//=========================================================================================== +int strIndex(const char *haystack, const char *needle) +{ + return strIndex(haystack, needle, 0); + +} // strIndex() + +//=========================================================================================== +int stricmp(const char *a, const char *b) +{ + for (;; a++, b++) { + int d = tolower((unsigned char)*a) - tolower((unsigned char)*b); + if (d != 0 || !*a) + return d; + } + +} // stricmp() + +//=========================================================================================== +char *intToStr(int32_t v) +{ + static char buff[25]; + sprintf(buff,"%d", v); + return buff; + +} // intToStr() + +//=========================================================================================== +char *floatToStr(float v, int dec) +{ + static char buff[25]; + if (dec == 0) sprintf(buff,"%.0f", v); + else if (dec == 1) sprintf(buff,"%.1f", v); + else if (dec == 2) sprintf(buff,"%.2f", v); + else if (dec == 3) sprintf(buff,"%.3f", v); + else if (dec == 4) sprintf(buff,"%.4f", v); + else if (dec == 5) sprintf(buff,"%.5f", v); + else sprintf(buff,"%f", v); + return buff; + +} // floattToStr() + +//=========================================================================================== +float formatFloat(float v, int dec) +{ + return (String(v, dec).toFloat()); + +} // formatFloat() + +//=========================================================================================== +float strToFloat(const char *s, int dec) +{ + float r = 0.0; + int p = 0; + int d = -1; + + r = strtof(s, NULL); + p = (int)(r*pow(10, dec)); + r = p / pow(10, dec); + //DebugTf("[%s][%d] => p[%d] -> r[%f]\r\n", s, dec, p, r); + return r; + +} // strToFloat() + +//=========================================================================================== +void parseJsonKey(const char *sIn, const char *key, char *val, int valLen) +{ + // json key-value pair looks like: + // "samenv": "Zwaar bewolkt", + // or "samenv": "Zwaar bewolkt"} + int keyStart = strIndex(sIn, key); + int sepStart = strIndex(sIn, ",", keyStart); + if (sepStart == -1) + { + sepStart = strIndex(sIn, "}", keyStart); + } + strCopy(val, valLen, sIn, keyStart+strlen(key), sepStart); + strRemoveAll(val, valLen, ':'); + strRemoveAll(val, valLen, ','); + strRemoveAll(val, valLen, '}'); + strRemoveAll(val, valLen, '"'); + strTrim(val, valLen, ' '); + +} // parseJsonKey() + +//=========================================================================================== +uint8_t utf8Ascii(uint8_t ascii) +// Convert a single Character from UTF8 to Extended ASCII according to ISO 8859-1, +// also called ISO Latin-1. Codes 128-159 contain the Microsoft Windows Latin-1 +// extended characters: +// - codes 0..127 are identical in ASCII and UTF-8 +// - codes 160..191 in ISO-8859-1 and Windows-1252 are two-byte characters in UTF-8 +// + 0xC2 then second byte identical to the extended ASCII code. +// - codes 192..255 in ISO-8859-1 and Windows-1252 are two-byte characters in UTF-8 +// + 0xC3 then second byte differs only in the first two bits to extended ASCII code. +// - codes 128..159 in Windows-1252 are different, but usually only the €-symbol will be needed from this range. +// + The euro symbol is 0x80 in Windows-1252, 0xa4 in ISO-8859-15, and 0xe2 0x82 0xac in UTF-8. +// +// Modified from original code at http://playground.arduino.cc/Main/Utf8ascii +// Extended ASCII encoding should match the characters at http://www.ascii-code.com/ +// +// Return "0" if a byte has to be ignored. +{ + static uint8_t cPrev; + uint8_t c = '\0'; + + if (ascii < 0x7f) // Standard ASCII-set 0..0x7F, no conversion + { + cPrev = '\0'; + c = ascii; + } + else + { + switch (cPrev) // Conversion depending on preceding UTF8-character + { + case 0xC2: c = ascii; break; + case 0xC3: c = ascii | 0xC0; break; + case 0x82: if (ascii==0xAC) c = 0x80; // Euro symbol special case + } + cPrev = ascii; // save last char + } + //Debugf("\nConverted 0x%02x", ascii); + //Debugf(" to 0x%02x", c); + + return(c); + +} // utf8Ascii(uint8_t) + +//=========================================================================================== +void utf8Ascii(char* s) +// In place conversion UTF-8 string to Extended ASCII +// The extended ASCII string is always shorter. +{ + uint8_t c; + char *cp = s; + + //DebugTf("\nConverting: %c", s); + + while (*s != '\0') + { + c = utf8Ascii(*s++); + if (c != '\0') + *cp++ = c; + } + *cp = '\0'; // terminate the new string + +} // utf8Ascii(char) + + +void getRevisionData() +{ + if (!LittleFS.exists("/newsFiles/LCL-000")) + { + char LCL000[100]; + sprintf(LCL000, "ESP_ticker %s (c) by Willem Aandewiel", String(FWversion).c_str()); + writeFileById("LCL", 0, LCL000); + } + +} // getRevisionData() + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/jsonStuff.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/jsonStuff.ino new file mode 100644 index 0000000..75f00da --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/jsonStuff.ino @@ -0,0 +1,211 @@ +/* +*************************************************************************** +** Program : jsonStuff, part of ESP_ticker +** +** Copyright (c) 2021 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +//aaw//WiFiServer httpServer(80); +static char objSprtr[10] = ""; + +//======================================================================= +void sendStartJsonObj(const char *objName) +{ + char sBuff[50] = ""; + objSprtr[0] = '\0'; + + snprintf(sBuff, sizeof(sBuff), "{\"%s\":[\r\n", objName); + httpServer.sendHeader("Access-Control-Allow-Origin", "*"); + httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN); + httpServer.send(200, "application/json", sBuff); + +} // sendStartJsonObj() + + +//======================================================================= +void sendEndJsonObj() +{ + httpServer.sendContent("\r\n]}\r\n"); + + //httpServer.sendHeader( "Content-Length", "0"); + //httpServer.send ( 200, "application/json", ""); + +} // sendEndJsonObj() + + +//======================================================================= +void sendNestedJsonObj(const char *cName, const char *cValue) +{ + char jsonBuff[JSON_BUFF_MAX] = ""; + + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": \"%s\"}" + , objSprtr, cName, cValue); + + httpServer.sendContent(jsonBuff); + sprintf(objSprtr, ",\r\n"); + +} // sendNestedJsonObj(*char, *char) + + +//======================================================================= +void sendNestedJsonObj(const char *cName, String sValue) +{ + char jsonBuff[JSON_BUFF_MAX] = ""; + + if (sValue.length() > (JSON_BUFF_MAX - 65) ) + { + DebugTf("[2] sValue.length() [%d]\r\n", sValue.length()); + } + + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": \"%s\"}" + , objSprtr, cName, sValue.c_str()); + + httpServer.sendContent(jsonBuff); + sprintf(objSprtr, ",\r\n"); + +} // sendNestedJsonObj(*char, String) + + +//======================================================================= +void sendNestedJsonObj(const char *cName, int32_t iValue) +{ + char jsonBuff[200] = ""; + + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %d}" + , objSprtr, cName, iValue); + + httpServer.sendContent(jsonBuff); + sprintf(objSprtr, ",\r\n"); + +} // sendNestedJsonObj(*char, int) + +//======================================================================= +void sendNestedJsonObj(const char *cName, uint32_t uValue) +{ + char jsonBuff[200] = ""; + + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %u }" + , objSprtr, cName, uValue); + + httpServer.sendContent(jsonBuff); + sprintf(objSprtr, ",\r\n"); + +} // sendNestedJsonObj(*char, uint) + + +//======================================================================= +void sendNestedJsonObj(const char *cName, float fValue) +{ + char jsonBuff[200] = ""; + + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %.3f }" + , objSprtr, cName, fValue); + + httpServer.sendContent(jsonBuff); + sprintf(objSprtr, ",\r\n"); + +} // sendNestedJsonObj(*char, float) + + +//======================================================================= +// ************ function to build Json Settings string ****************** +//======================================================================= +void sendJsonSettingObj(const char *cName, float fValue, const char *fType, int minValue, int maxValue) +{ + char jsonBuff[200] = ""; + + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %.3f, \"type\": \"%s\", \"min\": %d, \"max\": %d}" + , objSprtr, cName, fValue, fType, minValue, maxValue); + + httpServer.sendContent(jsonBuff); + sprintf(objSprtr, ",\r\n"); + +} // sendJsonSettingObj(*char, float, *char, int, int) + + +//======================================================================= +void sendJsonSettingObj(const char *cName, float fValue, const char *fType, int minValue, int maxValue, int decPlaces) +{ + char jsonBuff[200] = ""; + + switch(decPlaces) { + case 0: + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %.0f, \"type\": \"%s\", \"min\": %d, \"max\": %d}" + , objSprtr, cName, fValue, fType, minValue, maxValue); + break; + case 2: + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %.2f, \"type\": \"%s\", \"min\": %d, \"max\": %d}" + , objSprtr, cName, fValue, fType, minValue, maxValue); + break; + case 5: + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %.5f, \"type\": \"%s\", \"min\": %d, \"max\": %d}" + , objSprtr, cName, fValue, fType, minValue, maxValue); + break; + default: + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %f, \"type\": \"%s\", \"min\": %d, \"max\": %d}" + , objSprtr, cName, fValue, fType, minValue, maxValue); + + } + + httpServer.sendContent(jsonBuff); + sprintf(objSprtr, ",\r\n"); + +} // sendJsonSettingObj(*char, float, *char, int, int, int) + + +//======================================================================= +void sendJsonSettingObj(const char *cName, int iValue, const char *iType, int minValue, int maxValue) +{ + char jsonBuff[200] = ""; + + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\": %d, \"type\": \"%s\", \"min\": %d, \"max\": %d}" + , objSprtr, cName, iValue, iType, minValue, maxValue); + + httpServer.sendContent(jsonBuff); + sprintf(objSprtr, ",\r\n"); + +} // sendJsonSettingObj(*char, int, *char, int, int) + + +//======================================================================= +void sendJsonSettingObj(const char *cName, const char *cValue, const char *sType, int maxLen) +{ + char jsonBuff[200] = ""; + + snprintf(jsonBuff, sizeof(jsonBuff), "%s{\"name\": \"%s\", \"value\":\"%s\", \"type\": \"%s\", \"maxlen\": %d}" + , objSprtr, cName, cValue, sType, maxLen); + + httpServer.sendContent(jsonBuff); + sprintf(objSprtr, ",\r\n"); + +} // sendJsonSettingObj(*char, *char, *char, int, int) + + + + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/littlefsStuff.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/littlefsStuff.ino new file mode 100644 index 0000000..b659536 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/littlefsStuff.ino @@ -0,0 +1,225 @@ +/* +*************************************************************************** +** Program : littlefsStuff, part of ESP_ticker +** +** Copyright (c) 2021 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +//==================================================================== +void readLastStatus() +{ + char buffer[50] = ""; + char dummy[50] = ""; + + File _file = LittleFS.open("/sysStatus.csv", "r"); + if (!_file) + { + DebugTln("read(): No /sysStatus.csv found .."); + } + if(_file.available()) { + int l = _file.readBytesUntil('\n', buffer, sizeof(buffer)); + buffer[l] = 0; + DebugTf("read lastUpdate[%s]\r\n", buffer); + sscanf(buffer, "%[^;]; %[^;]; %u; %[^;]", cDate, cTime, &nrReboots, dummy); + DebugTf("values timestamp[%s %s], nrReboots[%u], dummy[%s]\r\n" + , cDate + , cTime + , nrReboots + , dummy); + yield(); + } + _file.close(); + +} // readLastStatus() + + +//==================================================================== +void writeLastStatus() +{ + if (ESP.getFreeHeap() < 8500) // to prevent firmware from crashing! + { + DebugTf("Bailout due to low heap (%d bytes)\r\n", ESP.getFreeHeap()); + return; + } + char buffer[50] = ""; + snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d; %02d:%02d:%02d; %010u; %s;\n" + , localtime(&now)->tm_year+1900, localtime(&now)->tm_mon+1, localtime(&now)->tm_mday + , localtime(&now)->tm_hour, localtime(&now)->tm_min, localtime(&now)->tm_sec + , nrReboots + , "meta data"); + DebugTf("writeLastStatus() => %s\r\n", buffer); + + File _file = LittleFS.open("/sysStatus.csv", "w"); + if (!_file) + { + DebugTln("write(): No /sysStatus.csv found .."); + } + _file.print(buffer); + _file.flush(); + _file.close(); + +} // writeLastStatus() + + +//------------------------------------------------------------------------ +bool readFileById(const char* fType, uint8_t mId) +{ + String percChar = "%%"; + String backSlash = "\\"; + String rTmp; + char fName[50] = ""; + + sprintf(fName, "/newsFiles/%s-%03d", fType, mId); + + DebugTf("read [%s] ", fName); + + if (!LittleFS.exists(fName)) + { + Debugln("Does not exist!"); + return false; + } + + File f = LittleFS.open(fName, "r"); + + while(f.available()) + { + rTmp = f.readStringUntil('\n'); + //Debugf("rTmp(in) [%s]\r\n", rTmp.c_str()); + rTmp.replace("\r", ""); + } + f.close(); + + rTmp.replace("@1@", ":"); + rTmp.replace("@2@", "{"); + rTmp.replace("@3@", "}"); + rTmp.replace("@4@", ","); + rTmp.replace("@5@", backSlash); + rTmp.replace("@6@", percChar); + //DebugTf("rTmp(out) [%s]\r\n", rTmp.c_str()); + + snprintf(fileMessage, LOCAL_SIZE, rTmp.c_str()); + if (strlen(fileMessage) == 0) + { + Debugln("file is zero bytes long"); + return false; + } + Debugf("OK! \r\n\t[%s]\r\n", fileMessage); + + if (mId == 0) + { + LittleFS.remove("/newsFiles/LCL-000"); + DebugTln("Remove LCL-000 .."); + } + + return true; + +} // readFileById() + +//------------------------------------------------------------------------ +bool writeFileById(const char* fType, uint8_t mId, const char *msg) +{ + String rTmp; + char fName[50] = ""; + sprintf(fName, "/newsFiles/%s-%03d", fType, mId); + + DebugTf("write [%s]-> [%s]\r\n", fName, msg); + + if (strlen(msg) < 3) + { + LittleFS.remove(fName); + Debugln("Empty message, file removed!"); + return true; + } + + DebugTln("LittleFS.open()..."); + File file = LittleFS.open(fName, "w"); + if (!file) + { + Debugf("open(%s, 'w') FAILED!!! --> Bailout\r\n", fName); + return false; + } + yield(); + + Debugln(F("Start writing data .. \r")); + DebugFlush(); + Debugln(msg); + file.println(msg); + file.close(); + + DebugTln("Exit writeFileById()!"); + return true; + +} // writeFileById() + + +//======================================================================= +void updateMessage(const char *field, const char *newValue) +{ + int8_t msgId = String(field).toInt(); + + DebugTf("-> field[%s], newValue[%s]\r\n", field, newValue); + + if (msgId < 0 || msgId > settingLocalMaxMsg) + { + DebugTf("msgId[%d] is out of scope! Bailing out!\r\n", msgId); + return; + } + + writeFileById("LCL", msgId, newValue); + +} // updateMessage() + + +//==================================================================== +void writeToLog(const char *logLine) +{ + if (ESP.getFreeHeap() < 8500) // to prevent firmware from crashing! + { + DebugTf("Bailout due to low heap (%d bytes)\r\n", ESP.getFreeHeap()); + return; + } + char buffer[150] = ""; + snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d; %02d:%02d:%02d; %s;\n" + , localtime(&now)->tm_year+1900, localtime(&now)->tm_mon+1, localtime(&now)->tm_mday + , localtime(&now)->tm_hour, localtime(&now)->tm_min, localtime(&now)->tm_sec + , logLine); + DebugTf("writeToLogs() => %s\r\n", buffer); + File _file = LittleFS.open("/sysLog.csv", "a"); + if (!_file) + { + DebugTln("write(): No /sysLog.csv found .."); + } + _file.print(buffer); + _file.flush(); + _file.close(); + +} // writeLastStatus() + + + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/networkStuff.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/networkStuff.ino new file mode 100644 index 0000000..2d81abc --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/networkStuff.ino @@ -0,0 +1,149 @@ +/* +*************************************************************************** +** Program : networkStuff.ino +** +** Copyright (c) 2021 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +** Usage: +** +** #define HOSTNAME thisProject +** +** setup() +** { +** startWiFi(_HOSTNAME, 240); // timeout 4 minuten +** startMDNS(_HOSTNAME); +** httpServer.on("/index", ); +** httpServer.on("/index.html",); +** httpServer.begin(); +** } +** +** loop() +** { +** handleWiFi(); +** MDNS.update(); +** httpServer.handleClient(); +** . +** . +** } +*/ + + +#include //ESP8266 Core WiFi Library +#include // Version 1.0.0 - part of ESP8266 Core https://github.com/esp8266/Arduino +#include // part of ESP8266 Core https://github.com/esp8266/Arduino + +#include // part of ESP8266 Core https://github.com/esp8266/Arduino +#include // https://github.com/mrWheel/ModUpdateServer +#include "updateServerHtml.h" +#include // part of ESP8266 Core + +ESP8266HTTPUpdateServer httpUpdater(true); + +bool isConnected = false; + +/** +void configModeCallback (WiFiManager *myWiFiManager) +{ + Debugln(F("Entered config mode\r")); + Debugln(WiFi.softAPIP().toString()); + Debugln(" ----> brows to 192.168.4.1"); + Debugln(myWiFiManager->getConfigPortalSSID()); + +} // configModeCallback() +**/ + +//=========================================================================================== +void startWiFi(const char* hostname, int timeOut) +{ + WiFiManager manageWiFi; + uint32_t lTime = millis(); + String thisAP = String(hostname) + "-" + WiFi.macAddress(); + + DebugT("start WiFi ..."); + + manageWiFi.setDebugOutput(true); + + //-- reset settings - wipe stored credentials for testing + //-- these are stored by the esp library + //manageWiFi.resetSettings(); + + + //-- set callback that gets called when connecting to previous + //-- WiFi fails, and enters Access Point mode + //manageWiFi.setAPCallback(configModeCallback); + + //-- sets timeout until configuration portal gets turned off + //-- useful to make it all retry or go to sleep in seconds + manageWiFi.setTimeout(timeOut); // in seconden ... + + //-- fetches ssid and pass and tries to connect + //-- if it does not connect it starts an access point with the specified name + //-- here "lichtKrant-" + //-- and goes into a blocking loop awaiting configuration + if (!manageWiFi.autoConnect(thisAP.c_str())) + { + DebugTln(F("failed to connect and hit timeout")); + + //reset and try again, or maybe put it to deep sleep + //delay(3000); + //ESP.reset(); + //delay(2000); + DebugTf(" took [%d] seconds ==> ERROR!\r\n", (millis() - lTime) / 1000); + return; + } + + Debugln(); + DebugT(F("Connected to " )); Debugln (WiFi.SSID()); + DebugT(F("IP address: " )); Debugln (WiFi.localIP()); + DebugT(F("IP gateway: " )); Debugln (WiFi.gatewayIP()); + Debugln(); + + httpUpdater.setup(&httpServer); + httpUpdater.setIndexPage(UpdateServerIndex); + httpUpdater.setSuccessPage(UpdateServerSuccess); + DebugTf(" took [%d] seconds => OK!\r\n", (millis() - lTime) / 1000); + +} // startWiFi() + + +//======================================================================= +void startMDNS(const char *Hostname) +{ + DebugTf("[1] mDNS setup as [%s.local]\r\n", Hostname); + if (MDNS.begin(Hostname)) // Start the mDNS responder for Hostname.local + { + DebugTf("[2] mDNS responder started as [%s.local]\r\n", Hostname); + } + else + { + DebugTln(F("[3] Error setting up MDNS responder!\r\n")); + } + MDNS.addService("http", "tcp", 80); + +} // startMDNS() + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/newsapi_org.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/newsapi_org.ino new file mode 100644 index 0000000..3f065fc --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/newsapi_org.ino @@ -0,0 +1,187 @@ +/* +*************************************************************************** +** Program : newsapi_org +** +** Copyright (c) 2021 .. 2023 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +//-- http://newsapi.org/v2/top-headlines?country=nl&apiKey=API_KEY + +//---------------------------------------------------------------------- +bool getNewsapiData() +{ + const char* newsapiHost = "newsapi.org"; + const int httpPort = 80; + int newsapiStatus = 0; + char newsMessage[NEWS_SIZE] = {}; + int startPos, endPos; + int32_t maxWait; + char jsonResponse[1024]; + char val[51] = ""; + + WiFiClient newsapiClient; + + Debugln(); + DebugTf("getNewsapiData(%s)\r\n", newsapiHost); + + // We now create a URI for the request + String url = "/v2/top-headlines?country=nl&apiKey="; + url += settingNewsAUTH; + + DebugTf("Requesting URL: %s/v2/top-headlines?country=nl&apiKey=secret\r\n", newsapiHost); + DebugFlush(); + //Debugln("\r\n======================================="); + //DebugFlush(); + //Debug(newsapiHost); + //Debugln(url); + //Debugln("======================================="); + + if (!newsapiClient.connect(newsapiHost, httpPort)) + { + DebugTln("connection failed"); + sprintf(tempMessage, "connection to %s failed", newsapiHost); + //-- empty newsMessage store -- + for(int i=0; i<=settingNewsMaxMsg; i++) + { + //sprintf(newsMessage, ""); + if (i==1) writeFileById("NWS", i, "There is No News ...."); + else writeFileById("NWS", i, ""); + } + + newsapiClient.flush(); + newsapiClient.stop(); + return false; + } + + // This will send the request to the server + newsapiClient.print(String("GET ") + url + " HTTP/1.1\r\n" + + "Host: " + newsapiHost + "\r\n" + + "User-Agent: ESP-ticker\r\n" + + "Connection: close\r\n\r\n"); + delay(10); + + newsapiClient.setTimeout(5000); + while (newsapiClient.connected() || newsapiClient.available()) + { + yield(); + while(newsapiClient.available()) + { + Debugln(); + //--- skip to find HTTP/1.1 + //--- then parse response code + if (newsapiClient.find("HTTP/1.1")) + { + newsapiStatus = newsapiClient.parseInt(); // parse status code + DebugTf("Statuscode: [%d] ", newsapiStatus); + if (newsapiStatus != 200) + { + Debugln(" ERROR!"); + while(newsapiClient.available()) + { + char nC = newsapiClient.read(); + Debug(nC); + } + Debugln(); + newsapiClient.flush(); + newsapiClient.stop(); + for(int i=0; i<=settingNewsMaxMsg; i++) + { + //sprintf(newsMessage, ""); + if (i==1) writeFileById("NWS", i, "There is No News ...."); + else writeFileById("NWS", i, ""); + } + newsapiClient.flush(); + newsapiClient.stop(); + return false; + } + Debugln(" OK!"); + } + else + { + DebugTln("Error reading newsapi.org.. -> bailout!"); + for(int i=0; i<=settingNewsMaxMsg; i++) + { + //sprintf(newsMessage, ""); + if (i==1) writeFileById("NWS", i, "There is No News ...."); + else writeFileById("NWS", i, ""); + } + newsapiClient.flush(); + newsapiClient.stop(); + return false; + } + //--- skip headers + uint16_t msgIdx = 0; + int msgNr = 0; + while (newsapiClient.find("\"title\":\"")) + { + for (int i=0; i 30) newsMessage[msgIdx - 16] = 0; + Debugf("\t[%2d] %s\r\n", msgNr, newsMessage); + if (!hasNoNoWord(newsMessage) && strlen(newsMessage) > 15) + { + if (msgNr <= settingNewsMaxMsg) + { + writeFileById("NWS", msgNr, newsMessage); + } + msgNr++; + } + } // while find(title) + } // while available .. + + } // connected .. + + newsapiClient.flush(); + newsapiClient.stop(); + updateMessage("0", "News brought to you by 'newsapi.org'"); + + return true; + +} // getNewsapiData() + + +//---------------------------------------------------------------------- +void removeNewsData() +{ + char nwsName[15]; + + for(int n=0; n<=settingNewsMaxMsg; n++) + { + sprintf(nwsName, "/newsFiles/NWS-%03d", n); + DebugTf("Remove [%s] from LittleFS ..\r\n", nwsName); + LittleFS.remove(nwsName); + } + +} // removeNewsData() + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/newsapi_org.txt b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/newsapi_org.txt new file mode 100644 index 0000000..3f065fc --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/newsapi_org.txt @@ -0,0 +1,187 @@ +/* +*************************************************************************** +** Program : newsapi_org +** +** Copyright (c) 2021 .. 2023 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +//-- http://newsapi.org/v2/top-headlines?country=nl&apiKey=API_KEY + +//---------------------------------------------------------------------- +bool getNewsapiData() +{ + const char* newsapiHost = "newsapi.org"; + const int httpPort = 80; + int newsapiStatus = 0; + char newsMessage[NEWS_SIZE] = {}; + int startPos, endPos; + int32_t maxWait; + char jsonResponse[1024]; + char val[51] = ""; + + WiFiClient newsapiClient; + + Debugln(); + DebugTf("getNewsapiData(%s)\r\n", newsapiHost); + + // We now create a URI for the request + String url = "/v2/top-headlines?country=nl&apiKey="; + url += settingNewsAUTH; + + DebugTf("Requesting URL: %s/v2/top-headlines?country=nl&apiKey=secret\r\n", newsapiHost); + DebugFlush(); + //Debugln("\r\n======================================="); + //DebugFlush(); + //Debug(newsapiHost); + //Debugln(url); + //Debugln("======================================="); + + if (!newsapiClient.connect(newsapiHost, httpPort)) + { + DebugTln("connection failed"); + sprintf(tempMessage, "connection to %s failed", newsapiHost); + //-- empty newsMessage store -- + for(int i=0; i<=settingNewsMaxMsg; i++) + { + //sprintf(newsMessage, ""); + if (i==1) writeFileById("NWS", i, "There is No News ...."); + else writeFileById("NWS", i, ""); + } + + newsapiClient.flush(); + newsapiClient.stop(); + return false; + } + + // This will send the request to the server + newsapiClient.print(String("GET ") + url + " HTTP/1.1\r\n" + + "Host: " + newsapiHost + "\r\n" + + "User-Agent: ESP-ticker\r\n" + + "Connection: close\r\n\r\n"); + delay(10); + + newsapiClient.setTimeout(5000); + while (newsapiClient.connected() || newsapiClient.available()) + { + yield(); + while(newsapiClient.available()) + { + Debugln(); + //--- skip to find HTTP/1.1 + //--- then parse response code + if (newsapiClient.find("HTTP/1.1")) + { + newsapiStatus = newsapiClient.parseInt(); // parse status code + DebugTf("Statuscode: [%d] ", newsapiStatus); + if (newsapiStatus != 200) + { + Debugln(" ERROR!"); + while(newsapiClient.available()) + { + char nC = newsapiClient.read(); + Debug(nC); + } + Debugln(); + newsapiClient.flush(); + newsapiClient.stop(); + for(int i=0; i<=settingNewsMaxMsg; i++) + { + //sprintf(newsMessage, ""); + if (i==1) writeFileById("NWS", i, "There is No News ...."); + else writeFileById("NWS", i, ""); + } + newsapiClient.flush(); + newsapiClient.stop(); + return false; + } + Debugln(" OK!"); + } + else + { + DebugTln("Error reading newsapi.org.. -> bailout!"); + for(int i=0; i<=settingNewsMaxMsg; i++) + { + //sprintf(newsMessage, ""); + if (i==1) writeFileById("NWS", i, "There is No News ...."); + else writeFileById("NWS", i, ""); + } + newsapiClient.flush(); + newsapiClient.stop(); + return false; + } + //--- skip headers + uint16_t msgIdx = 0; + int msgNr = 0; + while (newsapiClient.find("\"title\":\"")) + { + for (int i=0; i 30) newsMessage[msgIdx - 16] = 0; + Debugf("\t[%2d] %s\r\n", msgNr, newsMessage); + if (!hasNoNoWord(newsMessage) && strlen(newsMessage) > 15) + { + if (msgNr <= settingNewsMaxMsg) + { + writeFileById("NWS", msgNr, newsMessage); + } + msgNr++; + } + } // while find(title) + } // while available .. + + } // connected .. + + newsapiClient.flush(); + newsapiClient.stop(); + updateMessage("0", "News brought to you by 'newsapi.org'"); + + return true; + +} // getNewsapiData() + + +//---------------------------------------------------------------------- +void removeNewsData() +{ + char nwsName[15]; + + for(int n=0; n<=settingNewsMaxMsg; n++) + { + sprintf(nwsName, "/newsFiles/NWS-%03d", n); + DebugTf("Remove [%s] from LittleFS ..\r\n", nwsName); + LittleFS.remove(nwsName); + } + +} // removeNewsData() + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/parola_Fonts_data.h b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/parola_Fonts_data.h new file mode 100644 index 0000000..487c21f --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/parola_Fonts_data.h @@ -0,0 +1,262 @@ +// Data file for UTF-8 example user defined fonts +#pragma once + +MD_MAX72XX::fontType_t ExtASCII[] PROGMEM = +{ + 0, // 0 - 'Unused' + 0, // 1 - 'Unused' + 0, // 2 - 'Unused' + 0, // 3 - 'Unused' + 0, // 4 - 'Unused' + 0, // 5 - 'Unused' + 0, // 6 - 'Unused' + 0, // 7 - 'Unused' + 0, // 8 - 'Unused' + 0, // 9 - 'Unused' + 0, // 10 - 'Unused' + 0, // 11 - 'Unused' + 0, // 12 - 'Unused' + 0, // 13 - 'Unused' + 0, // 14 - 'Unused' + 0, // 15 - 'Unused' + 0, // 16 - 'Unused' + 0, // 17 - 'Unused' + 0, // 18 - 'Unused' + 0, // 19 - 'Unused' + 0, // 20 - 'Unused' + 0, // 21 - 'Unused' + 0, // 22 - 'Unused' + 0, // 23 - 'Unused' + 0, // 24 - 'Unused' + 0, // 25 - 'Unused' + 0, // 26 - 'Unused' + 0, // 27 - 'Unused' + 0, // 28 - 'Unused' + 0, // 29 - 'Unused' + 0, // 30 - 'Unused' + 0, // 31 - 'Unused' + 2, 0, 0, // 32 - 'Space' + 1, 95, // 33 - '!' + 3, 7, 0, 7, // 34 - '"' + 5, 20, 127, 20, 127, 20, // 35 - '#' + 5, 36, 42, 127, 42, 18, // 36 - '$' + 5, 35, 19, 8, 100, 98, // 37 - '%' + 5, 54, 73, 86, 32, 80, // 38 - '&' + 2, 4, 3, // 39 + 3, 28, 34, 65, // 40 - '(' + 3, 65, 34, 28, // 41 - ')' + 5, 42, 28, 127, 28, 42, // 42 - '*' + 5, 8, 8, 62, 8, 8, // 43 - '+' + 2, 128, 96, // 44 - ',' + 5, 8, 8, 8, 8, 8, // 45 - '-' + 2, 96, 96, // 46 - '.' + 5, 32, 16, 8, 4, 2, // 47 - '/' + 5, 62, 81, 73, 69, 62, // 48 - '0' + 3, 66, 127, 64, // 49 - '1' + 5, 114, 73, 73, 73, 70, // 50 - '2' + 5, 33, 65, 73, 77, 51, // 51 - '3' + 5, 24, 20, 18, 127, 16, // 52 - '4' + 5, 39, 69, 69, 69, 57, // 53 - '5' + 5, 60, 74, 73, 73, 49, // 54 - '6' + 5, 65, 33, 17, 9, 7, // 55 - '7' + 5, 54, 73, 73, 73, 54, // 56 - '8' + 5, 70, 73, 73, 41, 30, // 57 - '9' + 1, 20, // 58 - ':' + 2, 128, 104, // 59 - ';' + 4, 8, 20, 34, 65, // 60 - '<' + 5, 20, 20, 20, 20, 20, // 61 - '=' + 4, 65, 34, 20, 8, // 62 - '>' + 5, 2, 1, 89, 9, 6, // 63 - '?' + 5, 62, 65, 93, 89, 78, // 64 - '@' + 5, 124, 18, 17, 18, 124, // 65 - 'A' + 5, 127, 73, 73, 73, 54, // 66 - 'B' + 5, 62, 65, 65, 65, 34, // 67 - 'C' + 5, 127, 65, 65, 65, 62, // 68 - 'D' + 5, 127, 73, 73, 73, 65, // 69 - 'E' + 5, 127, 9, 9, 9, 1, // 70 - 'F' + 5, 62, 65, 65, 81, 115, // 71 - 'G' + 5, 127, 8, 8, 8, 127, // 72 - 'H' + 3, 65, 127, 65, // 73 - 'I' + 5, 32, 64, 65, 63, 1, // 74 - 'J' + 5, 127, 8, 20, 34, 65, // 75 - 'K' + 5, 127, 64, 64, 64, 64, // 76 - 'L' + 5, 127, 2, 28, 2, 127, // 77 - 'M' + 5, 127, 4, 8, 16, 127, // 78 - 'N' + 5, 62, 65, 65, 65, 62, // 79 - 'O' + 5, 127, 9, 9, 9, 6, // 80 - 'P' + 5, 62, 65, 81, 33, 94, // 81 - 'Q' + 5, 127, 9, 25, 41, 70, // 82 - 'R' + 5, 38, 73, 73, 73, 50, // 83 - 'S' + 5, 3, 1, 127, 1, 3, // 84 - 'T' + 5, 63, 64, 64, 64, 63, // 85 - 'U' + 5, 31, 32, 64, 32, 31, // 86 - 'V' + 5, 63, 64, 56, 64, 63, // 87 - 'W' + 5, 99, 20, 8, 20, 99, // 88 - 'X' + 5, 3, 4, 120, 4, 3, // 89 - 'Y' + 5, 97, 89, 73, 77, 67, // 90 - 'Z' + 3, 127, 65, 65, // 91 - '[' + 5, 2, 4, 8, 16, 32, // 92 - '\' + 3, 65, 65, 127, // 93 - ']' + 5, 4, 2, 1, 2, 4, // 94 - '^' + 5, 64, 64, 64, 64, 64, // 95 - '_' + 2, 3, 4, // 96 - '`' + 5, 32, 84, 84, 120, 64, // 97 - 'a' + 5, 127, 40, 68, 68, 56, // 98 - 'b' + 5, 56, 68, 68, 68, 40, // 99 - 'c' + 5, 56, 68, 68, 40, 127, // 100 - 'd' + 5, 56, 84, 84, 84, 24, // 101 - 'e' + 4, 8, 126, 9, 2, // 102 - 'f' + 5, 24, 164, 164, 156, 120, // 103 - 'g' + 5, 127, 8, 4, 4, 120, // 104 - 'h' + 3, 68, 125, 64, // 105 - 'i' + 4, 64, 128, 128, 122, // 106 - 'j' + 4, 127, 16, 40, 68, // 107 - 'k' + 3, 65, 127, 64, // 108 - 'l' + 5, 124, 4, 120, 4, 120, // 109 - 'm' + 5, 124, 8, 4, 4, 120, // 110 - 'n' + 5, 56, 68, 68, 68, 56, // 111 - 'o' + 5, 252, 24, 36, 36, 24, // 112 - 'p' + 5, 24, 36, 36, 24, 252, // 113 - 'q' + 5, 124, 8, 4, 4, 8, // 114 - 'r' + 5, 72, 84, 84, 84, 36, // 115 - 's' + 4, 4, 63, 68, 36, // 116 - 't' + 5, 60, 64, 64, 32, 124, // 117 - 'u' + 5, 28, 32, 64, 32, 28, // 118 - 'v' + 5, 60, 64, 48, 64, 60, // 119 - 'w' + 5, 68, 40, 16, 40, 68, // 120 - 'x' + 5, 76, 144, 144, 144, 124, // 121 - 'y' + 5, 68, 100, 84, 76, 68, // 122 - 'z' + 3, 8, 54, 65, // 123 - '{' + 1, 119, // 124 - '|' + 3, 65, 54, 8, // 125 - '}' + 5, 2, 1, 2, 4, 2, // 126 - '~' + 0, // 127 - 'Unused' + 6, 20, 62, 85, 85, 65, 34, // 128 - 'Euro sign' + 0, // 129 - 'Not used' + 2, 128, 96, // 130 - 'Single low 9 quotation mark' + 5, 192, 136, 126, 9, 3, // 131 - 'f with hook' + 4, 128, 96, 128, 96, // 132 - 'Single low 9 quotation mark' + 8, 96, 96, 0, 96, 96, 0, 96, 96, // 133 - 'Horizontal ellipsis' + 3, 4, 126, 4, // 134 - 'Dagger' + 3, 20, 126, 20, // 135 - 'Double dagger' + 4, 2, 1, 1, 2, // 136 - 'Modifier circumflex' + 7, 35, 19, 104, 100, 2, 97, 96, // 137 - 'Per mille sign' + 5, 72, 85, 86, 85, 36, // 138 - 'S with caron' + 3, 8, 20, 34, // 139 - '< quotation' + 6, 62, 65, 65, 127, 73, 73, // 140 - 'OE' + 0, // 141 - 'Not used' + 5, 68, 101, 86, 77, 68, // 142 - 'z with caron' + 0, // 143 - 'Not used' + 0, // 144 - 'Not used' + 2, 3, 4, // 145 - 'Left single quote mark' + 2, 4, 3, // 146 - 'Right single quote mark' + 4, 3, 4, 3, 4, // 147 - 'Left double quote marks' + 4, 4, 3, 4, 3, // 148 - 'Right double quote marks' + 4, 0, 24, 60, 24, // 149 - 'Bullet Point' + 3, 8, 8, 8, // 150 - 'En dash' + 5, 8, 8, 8, 8, 8, // 151 - 'Em dash' + 4, 2, 1, 2, 1, // 152 - 'Small ~' + 7, 1, 15, 1, 0, 15, 2, 15, // 153 - 'TM' + 5, 72, 85, 86, 85, 36, // 154 - 's with caron' + 3, 34, 20, 8, // 155 - '> quotation' + 7, 56, 68, 68, 124, 84, 84, 8, // 156 - 'oe' + 0, // 157 - 'Not used' + 5, 68, 101, 86, 77, 68, // 158 - 'z with caron' + 5, 12, 17, 96, 17, 12, // 159 - 'Y diaresis' + 2, 0, 0, // 160 - 'Non-breaking space' + 1, 125, // 161 - 'Inverted !' + 5, 60, 36, 126, 36, 36, // 162 - 'Cent sign' + 5, 72, 126, 73, 65, 102, // 163 - 'Pound sign' + 5, 34, 28, 20, 28, 34, // 164 - 'Currency sign' + 5, 43, 47, 252, 47, 43, // 165 - 'Yen' + 1, 119, // 166 - '|' + 4, 102, 137, 149, 106, // 167 - 'Section sign' + 3, 1, 0, 1, // 168 - 'Spacing diaresis' + 7, 62, 65, 93, 85, 85, 65, 62, // 169 - 'Copyright' + 3, 13, 13, 15, // 170 - 'Feminine Ordinal Ind.' + 5, 8, 20, 42, 20, 34, // 171 - '<<' + 5, 8, 8, 8, 8, 56, // 172 - 'Not sign' + 0, // 173 - 'Soft Hyphen' + 7, 62, 65, 127, 75, 117, 65, 62, // 174 - 'Registered Trademark' + 5, 1, 1, 1, 1, 1, // 175 - 'Spacing Macron Overline' + 3, 2, 5, 2, // 176 - 'Degree' + 5, 68, 68, 95, 68, 68, // 177 - '+/-' + 3, 25, 21, 19, // 178 - 'Superscript 2' + 3, 17, 21, 31, // 179 - 'Superscript 3' + 2, 2, 1, // 180 - 'Acute accent' + 4, 252, 64, 64, 60, // 181 - 'micro (mu)' + 5, 6, 9, 127, 1, 127, // 182 - 'Paragraph Mark' + 2, 24, 24, // 183 - 'Middle Dot' + 3, 128, 128, 96, // 184 - 'Spacing sedilla' + 2, 2, 31, // 185 - 'Superscript 1' + 4, 6, 9, 9, 6, // 186 - 'Masculine Ordinal Ind.' + 5, 34, 20, 42, 20, 8, // 187 - '>>' + 6, 64, 47, 16, 40, 52, 250, // 188 - '1/4' + 6, 64, 47, 16, 200, 172, 186, // 189 - '1/2' + 6, 85, 53, 31, 40, 52, 250, // 190 - '3/4' + 5, 48, 72, 77, 64, 32, // 191 - 'Inverted ?' + 5, 120, 20, 21, 22, 120, // 192 - 'A grave' + 5, 120, 22, 21, 20, 120, // 193 - 'A acute' + 5, 122, 21, 21, 21, 122, // 194 - 'A circumflex' + 5, 120, 22, 21, 22, 121, // 195 - 'A tilde' + 5, 120, 21, 20, 21, 120, // 196 - 'A diaresis' + 5, 120, 20, 21, 20, 120, // 197 - 'A ring above' + 6, 124, 10, 9, 127, 73, 73, // 198 - 'AE' + 5, 30, 161, 161, 97, 18, // 199 - 'C sedilla' + 4, 124, 85, 86, 68, // 200 - 'E grave' + 4, 124, 86, 85, 68, // 201 - 'E acute' + 4, 126, 85, 85, 70, // 202 - 'E circumflex' + 4, 124, 85, 84, 69, // 203 - 'E diaresis' + 3, 68, 125, 70, // 204 - 'I grave' + 3, 68, 126, 69, // 205 - 'I acute' + 3, 70, 125, 70, // 206 - 'I circumplex' + 3, 69, 124, 69, // 207 - 'I diaresis' + 6, 4, 127, 69, 65, 65, 62, // 208 - 'Capital Eth' + 5, 124, 10, 17, 34, 125, // 209 - 'N tilde' + 5, 56, 68, 69, 70, 56, // 210 - 'O grave' + 5, 56, 70, 69, 68, 56, // 211 - 'O acute' + 5, 58, 69, 69, 69, 58, // 212 - 'O circumflex' + 5, 56, 70, 69, 70, 57, // 213 - 'O tilde' + 5, 56, 69, 68, 69, 56, // 214 - 'O diaresis' + 5, 34, 20, 8, 20, 34, // 215 - 'Multiplication sign' + 7, 64, 62, 81, 73, 69, 62, 1, // 216 - 'O slashed' + 5, 60, 65, 66, 64, 60, // 217 - 'U grave' + 5, 60, 64, 66, 65, 60, // 218 - 'U acute' + 5, 58, 65, 65, 65, 58, // 219 - 'U circumflex' + 5, 60, 65, 64, 65, 60, // 220 - 'U diaresis' + 5, 12, 16, 98, 17, 12, // 221 - 'Y acute' + 4, 127, 18, 18, 12, // 222 - 'Capital thorn' + 4, 254, 37, 37, 26, // 223 - 'Small letter sharp S' + 5, 32, 84, 85, 122, 64, // 224 - 'a grave' + 5, 32, 84, 86, 121, 64, // 225 - 'a acute' + 5, 34, 85, 85, 121, 66, // 226 - 'a circumflex' + 5, 32, 86, 85, 122, 65, // 227 - 'a tilde' + 5, 32, 85, 84, 121, 64, // 228 - 'a diaresis' + 5, 32, 84, 85, 120, 64, // 229 - 'a ring above' + 7, 32, 84, 84, 124, 84, 84, 8, // 230 - 'ae' + 5, 24, 36, 164, 228, 40, // 231 - 'c sedilla' + 5, 56, 84, 85, 86, 88, // 232 - 'e grave' + 5, 56, 84, 86, 85, 88, // 233 - 'e acute' + 5, 58, 85, 85, 85, 90, // 234 - 'e circumflex' + 5, 56, 85, 84, 85, 88, // 235 - 'e diaresis' + 3, 68, 125, 66, // 236 - 'i grave' + 3, 68, 126, 65, // 237 - 'i acute' + 3, 70, 125, 66, // 238 - 'i circumflex' + 3, 69, 124, 65, // 239 - 'i diaresis' + 4, 48, 75, 74, 61, // 240 - 'Small eth' + 4, 122, 9, 10, 113, // 241 - 'n tilde' + 5, 56, 68, 69, 70, 56, // 242 - 'o grave' + 5, 56, 70, 69, 68, 56, // 243 - 'o acute' + 5, 58, 69, 69, 69, 58, // 244 - 'o circumflex' + 5, 56, 70, 69, 70, 57, // 245 - 'o tilde' + 5, 56, 69, 68, 69, 56, // 246 - 'o diaresis' + 5, 8, 8, 42, 8, 8, // 247 - 'Division sign' + 6, 64, 56, 84, 76, 68, 58, // 248 - 'o slashed' + 5, 60, 65, 66, 32, 124, // 249 - 'u grave' + 5, 60, 64, 66, 33, 124, // 250 - 'u acute' + 5, 58, 65, 65, 33, 122, // 251 - 'u circumflex' + 5, 60, 65, 64, 33, 124, // 252 - 'u diaresis' + 4, 156, 162, 161, 124, // 253 - 'y acute' + 4, 252, 72, 72, 48, // 254 - 'small thorn' + 4, 157, 160, 160, 125, // 255 - 'y diaresis' +}; diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/platformio.ini b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/platformio.ini new file mode 100644 index 0000000..f23df52 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/platformio.ini @@ -0,0 +1,57 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +workspace_dir = .pio.nosync +default_envs = esp12eDev + +[env:esp12eDev] +platform = espressif8266 +board = esp12e +framework = arduino +board_build.filesystem = littlefs +monitor_speed = 115200 +upload_speed = 115200 +#--- upload_port only needed for FileSys upload +upload_port = /dev/cu.usbserial-3224144 +build_flags = -DDEBUG +lib_ldf_mode = deep+ +lib_deps = + bblanchon/ArduinoJson@6.19.4 + https://github.com/PaulStoffregen/Time + jandrassy/TelnetStream@^1.3.0 + majicdesigns/MD_Parola@^3.7.3 + tzapu/WiFiManager@^0.16.0 + https://github.com/mrWheel/ModUpdateServer + +monitor_filters = + esp8266_exception_decoder + +[env:esp12eProd] +platform = espressif8266 +board = esp12e +framework = arduino +board_build.filesystem = littlefs +monitor_speed = 115200 +upload_speed = 115200 +#--- upload_port only needed for FileSys upload +upload_port = /dev/cu.usbserial-3224144 +lib_ldf_mode = deep+ +lib_deps = + bblanchon/ArduinoJson@6.19.4 + bblanchon/StreamUtils@^1.9.0 + https://github.com/PaulStoffregen/Time + jandrassy/TelnetStream@0.1.0 + majicdesigns/MD_Parola@^3.7.3 + tzapu/WiFiManager@^0.16.0 + https://github.com/mrWheel/ModUpdateServer + +monitor_filters = + esp8266_exception_decoder diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/restAPI.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/restAPI.ino new file mode 100644 index 0000000..c534d6d --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/restAPI.ino @@ -0,0 +1,375 @@ +/* +*************************************************************************** +** Program : restAPI, part of ESP_ticker +** +** Copyright (c) 2021 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +#include +#include + +//======================================================================= +void processAPI() +{ + char fName[40] = ""; + char URI[50] = ""; + String words[10]; + + strncpy( URI, httpServer.uri().c_str(), sizeof(URI) ); + + if (httpServer.method() == HTTP_GET) + DebugTf("from[%s] URI[%s] method[GET] \r\n" + , httpServer.client().remoteIP().toString().c_str() + , URI); + else DebugTf("from[%s] URI[%s] method[PUT] \r\n" + , httpServer.client().remoteIP().toString().c_str() + , URI); + + if (ESP.getFreeHeap() < 8500) // to prevent firmware from crashing! + { + DebugTf("==> Bailout due to low heap (%d bytes))\r\n", ESP.getFreeHeap() ); + httpServer.send(500, "text/plain", "500: internal server error (low heap)\r\n"); + return; + } + + int8_t wc = splitString(URI, '/', words, 10); + + if (Verbose) + { + DebugT(">>"); + for (int w=0; w [%s], ", w, words[w].c_str()); + } + Debugln(" "); + } + + if (words[1] != "api") + { + sendApiNotFound(URI); + return; + } + + if (words[2] != "v0") + { + sendApiNotFound(URI); + return; + } + + if (words[3] == "devinfo") + { + sendDeviceInfo(); + } + else if (words[3] == "devtime") + { + sendDeviceTime(); + } + else if (words[3] == "settings") + { + if (httpServer.method() == HTTP_PUT || httpServer.method() == HTTP_POST) + { + postSettings(); + } + else + { + sendDeviceSettings(); + } + } + else if (words[3] == "messages") + { + if (httpServer.method() == HTTP_PUT || httpServer.method() == HTTP_POST) + { + postMessages(); + } + else + { + sendLocalMessages(); + } + } + else if (words[3] == "news") + { + sendNewsMessages(); + } + else sendApiNotFound(URI); + +} // processAPI() + + +//======================================================================= +void sendDeviceInfo() +{ + FSInfo LittleFSinfo; + + sendStartJsonObj("devinfo"); + + sendNestedJsonObj("author", "Willem Aandewiel (www.aandewiel.nl)"); + sendNestedJsonObj("fwversion", FWversion); + + snprintf(cMsg, sizeof(cMsg), "%s %s", __DATE__, __TIME__); + sendNestedJsonObj("compiled", cMsg); + sendNestedJsonObj("hostname", settingHostname); + sendNestedJsonObj("ipaddress", WiFi.localIP().toString().c_str()); + sendNestedJsonObj("macaddress", WiFi.macAddress().c_str()); + sendNestedJsonObj("freeheap", ESP.getFreeHeap()); + sendNestedJsonObj("maxfreeblock", ESP.getMaxFreeBlockSize()); + sendNestedJsonObj("chipid", String( ESP.getChipId(), HEX ).c_str()); + sendNestedJsonObj("coreversion", String( ESP.getCoreVersion() ).c_str() ); + sendNestedJsonObj("sdkversion", String( ESP.getSdkVersion() ).c_str()); + sendNestedJsonObj("cpufreq", ESP.getCpuFreqMHz()); + sendNestedJsonObj("sketchsize", formatFloat( (ESP.getSketchSize() / 1024.0), 3)); + sendNestedJsonObj("freesketchspace", formatFloat( (ESP.getFreeSketchSpace() / 1024.0), 3)); + + snprintf(cMsg, sizeof(cMsg), "%08X", ESP.getFlashChipId()); + sendNestedJsonObj("flashchipid", cMsg); // flashChipId + sendNestedJsonObj("flashchipsize", formatFloat((ESP.getFlashChipSize() / 1024.0 / 1024.0), 3)); + sendNestedJsonObj("flashchiprealsize", formatFloat((ESP.getFlashChipRealSize() / 1024.0 / 1024.0), 3)); + + LittleFS.info(LittleFSinfo); + sendNestedJsonObj("spiffssize", formatFloat( (LittleFSinfo.totalBytes / (1024.0 * 1024.0)), 0)); + + sendNestedJsonObj("flashchipspeed", formatFloat((ESP.getFlashChipSpeed() / 1000.0 / 1000.0), 0)); + + FlashMode_t ideMode = ESP.getFlashChipMode(); + sendNestedJsonObj("flashchipmode", flashMode[ideMode]); + sendNestedJsonObj("boardtype", +#ifdef ARDUINO_ESP8266_NODEMCU + "ESP8266_NODEMCU" +#endif +#ifdef ARDUINO_ESP8266_GENERIC + "ESP8266_GENERIC" +#endif +#ifdef ESP8266_ESP01 + "ESP8266_ESP01" +#endif +#ifdef ESP8266_ESP12 + "ESP8266_ESP12" +#endif + ); + sendNestedJsonObj("ssid", WiFi.SSID().c_str()); + sendNestedJsonObj("wifirssi", WiFi.RSSI()); + + sendNestedJsonObj("lastreset", lastReset); + + httpServer.sendContent("\r\n]}\r\n"); + +} // sendDeviceInfo() + + +//======================================================================= +void sendDeviceTime() +{ + struct tm* timeinfo; + char actTime[50]; + + sendStartJsonObj("devtime"); + // Get current time + time(&now); + timeinfo = localtime(&now); // This is now correct + + snprintf(actTime, 49, "%02d-%02d-%04d %02d:%02d" + , timeinfo->tm_mday, timeinfo->tm_mon+1, timeinfo->tm_year+1900 + , timeinfo->tm_hour, timeinfo->tm_min); + sendNestedJsonObj("dateTime", actTime); + sendNestedJsonObj("epoch", (uint32_t)now); + + sendEndJsonObj(); + +} // sendDeviceTime() + + +//======================================================================= +void sendDeviceSettings() +{ + DebugTln("sending device settings ...\r"); + + sendStartJsonObj("settings"); + + sendJsonSettingObj("hostname", settingHostname, "s", sizeof(settingHostname) -1); + sendJsonSettingObj("localMaxMsg", settingLocalMaxMsg, "i", 1, 20); + sendJsonSettingObj("textSpeed", settingTextSpeed, "i", 10, MAX_SPEED); + sendJsonSettingObj("LDRlowOffset", settingLDRlowOffset, "i", 0, 500); + sendJsonSettingObj("LDRhighOffset", settingLDRhighOffset, "i", 500, 1024); + sendJsonSettingObj("maxIntensity", settingMaxIntensity, "i", 0, 15); + sendJsonSettingObj("weerliveAUTH", settingWeerLiveAUTH, "s", sizeof(settingWeerLiveAUTH) -1); + sendJsonSettingObj("weerliveLocation", settingWeerLiveLocation, "s", sizeof(settingWeerLiveLocation) -1); + sendJsonSettingObj("weerliveInterval", settingWeerLiveInterval, "i", 15, 120); + sendJsonSettingObj("newsapiAUTH", settingNewsAUTH, "s", sizeof(settingNewsAUTH) -1); + sendJsonSettingObj("newsapiMaxMsg", settingNewsMaxMsg, "i", 1, 20); + sendJsonSettingObj("newsapiInterval", settingNewsInterval, "i", 15, 120); + sendJsonSettingObj("newsNoWords", settingNewsNoWords, "s", sizeof(settingNewsNoWords) -1); + + sendEndJsonObj(); + +} // sendDeviceSettings() + + +//======================================================================= +void sendLocalMessages() +{ + int mID; + + DebugTln("sending local Messages ...\r"); + + sendStartJsonObj("messages"); + + for (mID=1; mID <= settingLocalMaxMsg; mID++) + { + if (readFileById("LCL", mID)) + { + //--- next 5 lines are realy dirty ... + char newMsg[LOCAL_SIZE] = ""; + String tmp = String(fileMessage); + tmp.replace("\\", "\\\\"); + sprintf(newMsg, "%s", tmp.c_str()); + //sendJsonSettingObj(intToStr(mID), fileMessage, "s", sizeof(fileMessage) -1); + sendJsonSettingObj(intToStr(mID), newMsg, "s", sizeof(newMsg) -1); + } + else + { + sendJsonSettingObj(intToStr(mID), "", "s", sizeof(fileMessage) -1); + } + } + + sendEndJsonObj(); + +} // sendlocalMessages() + + +//======================================================================= +void sendNewsMessages() +{ + int nID; + + DebugTln("sending news Messages ...\r"); + + sendStartJsonObj("newsapi"); + + for (nID=0; nID <= settingNewsMaxMsg; nID++) + { + if (readFileById("NWS", nID)) + { + sendJsonSettingObj(intToStr(nID), fileMessage, "s", sizeof(fileMessage) -1); + } + } + + sendEndJsonObj(); + +} // sendNewsMessages() + + +//======================================================================= +void postMessages() +{ + //------------------------------------------------------------ + // json string: {"name":"4","value":"Bericht tekst"} + //------------------------------------------------------------ + // so, why not use ArduinoJSON library? + // I say: try it yourself ;-) It won't be easy + String wOut[5]; + String wPair[5]; + String jsonIn = httpServer.arg(0).c_str(); + char field[25] = ""; + char newValue[101]=""; + jsonIn.replace("{", ""); + jsonIn.replace("}", ""); + jsonIn.replace("\"", ""); + int8_t wp = splitString(jsonIn.c_str(), ',', wPair, 5) ; + for (int i=0; i pair[%s]\r\n", i, wPair[i].c_str()); + int8_t wc = splitString(wPair[i].c_str(), ':', wOut, 5) ; + //DebugTf("==> [%s] -> field[%s]->val[%s]\r\n", wPair[i].c_str(), wOut[0].c_str(), wOut[1].c_str()); + if (wOut[0].equalsIgnoreCase("name")) strCopy(field, sizeof(field), wOut[1].c_str()); + if (wOut[0].equalsIgnoreCase("value")) strCopy(newValue, sizeof(newValue), wOut[1].c_str()); + } + DebugTf("--> field[%s] => newValue[%s]\r\n", field, newValue); + updateMessage(field, newValue); + httpServer.send(200, "application/json", httpServer.arg(0)); + +} // postMessages() + + +//======================================================================= +void postSettings() +{ + //------------------------------------------------------------ + // json string: {"name":"settingInterval","value":9} + // json string: {"name":"settingHostname","value":"abc"} + //------------------------------------------------------------ + // so, why not use ArduinoJSON library? + // I say: try it yourself ;-) It won't be easy + String wOut[5]; + String wPair[5]; + String jsonIn = httpServer.arg(0).c_str(); + char field[25] = ""; + char newValue[101]=""; + jsonIn.replace("{", ""); + jsonIn.replace("}", ""); + jsonIn.replace("\"", ""); + int8_t wp = splitString(jsonIn.c_str(), ',', wPair, 5) ; + for (int i=0; i pair[%s]\r\n", i, wPair[i].c_str()); + int8_t wc = splitString(wPair[i].c_str(), ':', wOut, 5) ; + //DebugTf("==> [%s] -> field[%s]->val[%s]\r\n", wPair[i].c_str(), wOut[0].c_str(), wOut[1].c_str()); + if (wOut[0].equalsIgnoreCase("name")) strCopy(field, sizeof(field), wOut[1].c_str()); + if (wOut[0].equalsIgnoreCase("value")) strCopy(newValue, sizeof(newValue), wOut[1].c_str()); + } + DebugTf("--> field[%s] => newValue[%s]\r\n", field, newValue); + updateSetting(field, newValue); + httpServer.send(200, "application/json", httpServer.arg(0)); + +} // postSettings() + + +//==================================================== +void sendApiNotFound(const char *URI) +{ + httpServer.sendHeader("Access-Control-Allow-Origin", "*"); + httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN); + httpServer.send ( 404, "text/html", ""); + + strCopy(cMsg, sizeof(cMsg), ""); + httpServer.sendContent(cMsg); + + strCopy(cMsg, sizeof(cMsg), "

ESP - lichtKrant

"); + httpServer.sendContent(cMsg); + + strCopy(cMsg, sizeof(cMsg), "
["); + strConcat(cMsg, sizeof(cMsg), URI); + strConcat(cMsg, sizeof(cMsg), "] is not a valid "); + httpServer.sendContent(cMsg); + + strCopy(cMsg, sizeof(cMsg), "\r\n"); + httpServer.sendContent(cMsg); + +} // sendApiNotFound() + + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/sendIndexPage.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/sendIndexPage.ino new file mode 100644 index 0000000..acbb94b --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/sendIndexPage.ino @@ -0,0 +1,110 @@ +/* +*************************************************************************** +** Program : sendIndexPage +** +** Copyright (c) 2021 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +static const char indexPage[] = +R"( + + + + + + + + ESP Lichtkrant + + + + +
+

+ ESP - lichtKrant       + -   + [version] + 00:00 +

+
+
+ + + + + + + +
2021 © Willem Aandewiel
+ + +
-
+ + + + + + +)"; + +void sendIndexPage() +{ + httpServer.send(200, "text/html", indexPage); + +} // sendIndexPage() + + + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/settingStuff.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/settingStuff.ino new file mode 100644 index 0000000..9173462 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/settingStuff.ino @@ -0,0 +1,267 @@ +/* +*************************************************************************** +** Program : settingsStuff, part of ESP_ticker +** +** Copyright (c) 2021 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +//======================================================================= +void writeSettings(bool show) +{ + DebugTf("Writing to [%s] ..\r\n", SETTINGS_FILE); + File file = LittleFS.open(SETTINGS_FILE, "w"); // open for reading and writing + if (!file) + { + DebugTf("open(%s, 'w') FAILED!!! --> Bailout\r\n", SETTINGS_FILE); + return; + } + yield(); + + DebugT(F("Start writing setting data ")); + + file.print("Hostname = "); file.println(settingHostname); Debug(F(".")); + file.print("localMaxMsg = "); file.println(settingLocalMaxMsg); Debug(F(".")); + file.print("textSpeed = "); file.println(settingTextSpeed); Debug(F(".")); + file.print("maxIntensity = "); file.println(settingMaxIntensity); Debug(F(".")); + file.print("LDRlowOffset = "); file.println(settingLDRlowOffset); Debug(F(".")); + file.print("LDRhighOffset = "); file.println(settingLDRhighOffset); Debug(F(".")); + file.print("weerLiveAUTH = "); file.println(settingWeerLiveAUTH); Debug(F(".")); + file.print("weerLiveLocatie = "); file.println(settingWeerLiveLocation); Debug(F(".")); + file.print("weerLiveInterval = "); file.println(settingWeerLiveInterval); Debug(F(".")); + file.print("newsAUTH = "); file.println(settingNewsAUTH); Debug(F(".")); + file.print("newsNoWords = "); file.println(settingNewsNoWords); Debug(F(".")); + file.print("newsMaxMsg = "); file.println(settingNewsMaxMsg); Debug(F(".")); + file.print("newsInterval = "); file.println(settingNewsInterval); Debug(F(".")); + + file.close(); + + Debugln(F(" done")); + + if (show) + { + DebugTln(F("Wrote this:")); + DebugT(F(" Hostname = ")); Debugln(settingHostname); + DebugT(F(" newsNoWords = ")); Debugln(settingNewsNoWords); + DebugT(F(" localMaxMsg = ")); Debugln(settingLocalMaxMsg); + DebugT(F(" textSpeed = ")); Debugln(settingTextSpeed); + DebugT(F(" LDRlowOffset = ")); Debugln(settingLDRlowOffset); + DebugT(F(" LDRhighOffset = ")); Debugln(settingLDRhighOffset); + DebugT(F(" maxIntensity = ")); Debugln(settingMaxIntensity); + DebugT(F(" weerLiveAUTH = ")); Debugln(settingWeerLiveAUTH); + DebugT(F(" weerLiveLocatie = ")); Debugln(settingWeerLiveLocation); + DebugT(F("weerLiveInterval = ")); Debugln(settingWeerLiveInterval); + DebugT(F(" newsAUTH = ")); Debugln(settingNewsAUTH); + DebugT(F(" newsMaxMsg = ")); Debugln(settingNewsMaxMsg); + DebugT(F(" newsInterval = ")); Debugln(settingNewsInterval); + + } // Verbose + +} // writeSettings() + + +//======================================================================= +void readSettings(bool show) +{ + String sTmp; + String words[10]; + char cTmp[LOCAL_SIZE], cVal[101], cKey[101]; + + File file; + + DebugTf(" %s ..\r\n", SETTINGS_FILE); + + snprintf(settingHostname, sizeof(settingHostname), "%s", _HOSTNAME); + snprintf(settingNewsNoWords, sizeof(settingNewsNoWords),"Voetbal, show, UEFA, KNVB"); + settingLocalMaxMsg = 5; + settingTextSpeed = 25; + settingLDRlowOffset = 70; + settingLDRhighOffset = 700; + settingMaxIntensity = 6; + snprintf(settingWeerLiveAUTH, 50, ""); + snprintf(settingWeerLiveLocation, 50, ""); + settingWeerLiveInterval = 0; + snprintf(settingNewsAUTH, 50, ""); + settingNewsMaxMsg = 4; + settingNewsInterval = 0; + + if (!LittleFS.exists(SETTINGS_FILE)) + { + DebugTln(F(" .. file not found! --> created file!")); + writeSettings(show); + } + + for (int T = 0; T < 2; T++) + { + file = LittleFS.open(SETTINGS_FILE, "r"); + if (!file) + { + if (T == 0) DebugTf(" .. something went wrong opening [%s]\r\n", SETTINGS_FILE); + else DebugT(T); + delay(100); + } + } // try T times .. + + DebugTln(F("Reading settings:\r")); + while(file.available()) + { + sTmp = file.readStringUntil('\n'); + snprintf(cTmp, sizeof(cTmp), "%s", sTmp.c_str()); + //strTrim(cTmp, sizeof(cTmp), '\r'); + strTrimCntr(cTmp, sizeof(cTmp)); + //DebugTf("cTmp[%s] (%d)\r\n", cTmp, strlen(cTmp)); + int sEq = strIndex(cTmp, "="); + strCopy(cKey, 100, cTmp, 0, sEq -1); + strCopy(cVal, 100, cTmp, sEq +1, strlen(cTmp)); + //DebugTf("cKey[%s], cVal[%s]\r\n", cKey, cVal); + strTrim(cKey, sizeof(cKey), ' '); + strTrim(cVal, sizeof(cVal), ' '); + //DebugTf("cKey[%s], cVal[%s]\r\n", cKey, cVal); + + //strToLower(cKey); + if (stricmp(cKey, "hostname") == 0) strCopy(settingHostname, sizeof(settingHostname), cVal); + if (stricmp(cKey, "localMaxMsg") == 0) settingLocalMaxMsg = atoi(cVal); + if (stricmp(cKey, "textSpeed") == 0) settingTextSpeed = atoi(cVal); + if (stricmp(cKey, "LDRlowOffset") == 0) settingLDRlowOffset = atoi(cVal); + if (stricmp(cKey, "LDRhighOffset") == 0) settingLDRhighOffset = atoi(cVal); + if (stricmp(cKey, "maxIntensity") == 0) settingMaxIntensity = atoi(cVal); + if (stricmp(cKey, "weerLiveAUTH") == 0) strCopy(settingWeerLiveAUTH, sizeof(settingWeerLiveAUTH), cVal); + if (stricmp(cKey, "weerlivelocatie") == 0) strCopy(settingWeerLiveLocation, sizeof(settingWeerLiveLocation), cVal); + if (stricmp(cKey, "weerLiveInterval") == 0) settingWeerLiveInterval = atoi(cVal); + if (stricmp(cKey, "newsAUTH") == 0) strCopy(settingNewsAUTH, sizeof(settingNewsAUTH), cVal); + if (stricmp(cKey, "newsNoWords") == 0) strCopy(settingNewsNoWords, sizeof(settingNewsNoWords), cVal); + if (stricmp(cKey, "newsMaxMsg") == 0) settingNewsMaxMsg = atoi(cVal); + if (stricmp(cKey, "newsInterval") == 0) settingNewsInterval = atoi(cVal); + + } // while available() + + file.close(); + + //--- this will take some time to settle in + //--- probably need a reboot before that to happen :-( + MDNS.setHostname(settingHostname); // start advertising with new(?) settingHostname + if (settingLocalMaxMsg > 20) settingLocalMaxMsg = 20; + if (settingLocalMaxMsg < 1) settingLocalMaxMsg = 1; + if (settingTextSpeed > MAX_SPEED) settingTextSpeed = MAX_SPEED; + if (settingTextSpeed < 10) settingTextSpeed = 10; + if (settingLDRlowOffset > 500) settingLDRlowOffset = 500; + if (settingLDRlowOffset < 1) settingLDRlowOffset = 0; + if (settingLDRhighOffset < 500) settingLDRhighOffset = 500; + if (settingLDRhighOffset > 1024) settingLDRhighOffset = 1024; + if (settingMaxIntensity > 15) settingMaxIntensity = 15; + if (settingMaxIntensity < 1) settingMaxIntensity = 1; + if (strlen(settingWeerLiveLocation) < 1) sprintf(settingWeerLiveLocation, "Amsterdam"); + if (settingWeerLiveInterval == 0) + { + settingWeerLiveInterval = 0; // geen weerberichten + } + else + { + if (settingWeerLiveInterval > 120) settingWeerLiveInterval = 120; // minuten! + if (settingWeerLiveInterval < 15) settingWeerLiveInterval = 15; + } + if (settingNewsMaxMsg > 20) settingNewsMaxMsg = 20; + if (settingNewsMaxMsg < 1) settingNewsMaxMsg = 1; + if (settingNewsInterval > 120) settingNewsInterval = 120; + if (settingNewsInterval == 0) + { + settingNewsInterval = 0; // geen nieuwsberichten + } + else + { + if (settingNewsInterval < 15) settingNewsInterval = 15; + } + + DebugTln(F(" .. done\r")); + + if (!show) return; + + Debugln(F("\r\n==== read Settings ===================================================\r")); + Debugf(" Hostname : %s\r\n", settingHostname); + Debugf(" local Max. Msg : %d\r\n", settingLocalMaxMsg); + Debugf(" text Speed : %d\r\n", settingTextSpeed); + Debugf(" LDR low offset : %d\r\n", settingLDRlowOffset); + Debugf(" LDR high offset : %d\r\n", settingLDRhighOffset); + Debugf(" max Intensity : %d\r\n", settingMaxIntensity); + Debugf(" WeerLive.nl AUTH : %s\r\n", settingWeerLiveAUTH); + Debugf(" WeerLive.nl Locatie : %s\r\n", settingWeerLiveLocation); + Debugf(" WeerLive.nl Interval : %d\r\n", settingWeerLiveInterval); + Debugf(" newsapi.org AUTH : %s\r\n", settingNewsAUTH); + Debugf(" newsapi.org NoWords : %s\r\n", settingNewsNoWords); + Debugf(" newsapi.org Max. Msg : %d\r\n", settingNewsMaxMsg); + Debugf(" newsapi.org Interval : %d\r\n", settingNewsInterval); + + Debugln(F("-\r")); + +} // readSettings() + + +//======================================================================= +void updateSetting(const char *field, const char *newValue) +{ + DebugTf("-> field[%s], newValue[%s]\r\n", field, newValue); + + if (!stricmp(field, "Hostname")) { + strCopy(settingHostname, sizeof(settingHostname), newValue); + if (strlen(settingHostname) < 1) strCopy(settingHostname, sizeof(settingHostname), _HOSTNAME); + char *dotPntr = strchr(settingHostname, '.') ; + if (dotPntr != NULL) + { + byte dotPos = (dotPntr-settingHostname); + if (dotPos > 0) settingHostname[dotPos] = '\0'; + } + Debugln(); + DebugTf("Need reboot before new %s.local will be available!\r\n\n", settingHostname); + } + if (!stricmp(field, "localMaxMsg")) settingLocalMaxMsg = String(newValue).toInt(); + if (!stricmp(field, "textSpeed")) settingTextSpeed = String(newValue).toInt(); + if (!stricmp(field, "LDRlowOffset")) settingLDRlowOffset = String(newValue).toInt(); + if (!stricmp(field, "LDRhighOffset")) settingLDRhighOffset = String(newValue).toInt(); + if (!stricmp(field, "maxIntensity")) settingMaxIntensity = String(newValue).toInt(); + + if (!stricmp(field, "weerLiveAUTH")) strCopy(settingWeerLiveAUTH, sizeof(settingWeerLiveAUTH), newValue); + if (!stricmp(field, "weerLiveLocation")) strCopy(settingWeerLiveLocation, sizeof(settingWeerLiveLocation), newValue); + if (!stricmp(field, "weerLiveInterval")) settingWeerLiveInterval = String(newValue).toInt(); + + if (!stricmp(field, "newsapiAUTH")) strCopy(settingNewsAUTH, sizeof(settingNewsAUTH), newValue); + if (!stricmp(field, "newsNoWords")) strCopy(settingNewsNoWords, sizeof(settingNewsNoWords), newValue); + if (!stricmp(field, "newsapiMaxMsg")) settingNewsMaxMsg = String(newValue).toInt(); + if (!stricmp(field, "newsapiInterval")) settingNewsInterval = String(newValue).toInt(); + + writeSettings(false); + + if (settingWeerLiveInterval == 0) memset(tempMessage, 0, sizeof(tempMessage)); + else if (settingWeerLiveInterval < 15) settingWeerLiveInterval = 15; + if (settingNewsInterval == 0) removeNewsData(); + else if (settingNewsInterval < 15) settingNewsInterval = 15; + //--- rebuild noWords array -- + splitNewsNoWords(settingNewsNoWords); + +} // updateSetting() + + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/updateServerHtml.h b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/updateServerHtml.h new file mode 100644 index 0000000..c540e6a --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/updateServerHtml.h @@ -0,0 +1,94 @@ +/* +*************************************************************************** +** Program : updateServerHtml.h +** +** Copyright (c) 2021 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +static const char UpdateServerIndex[] PROGMEM = + R"( + + +

ESP - lichtKrant Flash utility

+
+ Selecteer een ".ino.bin" bestand
+ + +
+
+ Selecteer een ".mklittlefs.bin" bestand
+ + +
+
+
Let op!!! +
Bij het flashen van LittleFS raakt u alle bestanden op LittleFS kwijt. +
Maak daarom eerst een kopie van deze bestanden (met de FSexplorer) + en zet deze na het flashen van LittleFS weer terug. +
+
+ Klik hier + om terug te keren naar het hoofdscherm! + + )"; + +static const char UpdateServerSuccess[] PROGMEM = + R"( + + +

lichtKrant Flash utility

+
+

Update successful!

+
+
Wait for the lichtKrant to reboot and start the HTTP server +
+
+
Wacht nog 30 seconden .. +
Als het lijkt of er niets gebeurd, wacht dan tot de teller + op 'nul' staat en klik daarna hier! + + + )"; + + + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/ diff --git a/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/weerlive_nl.ino b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/weerlive_nl.ino new file mode 100644 index 0000000..c2d6af9 --- /dev/null +++ b/arduinoIDE2platformIO-convertor/testProject/ESP_ticker/weerlive_nl.ino @@ -0,0 +1,149 @@ +/* +*************************************************************************** +** Program : weerlive_nl +** +** Copyright (c) 2021 .. 2023 Willem Aandewiel +** +** TERMS OF USE: MIT License. See bottom of file. +*************************************************************************** +*/ + +void getWeerLiveData() +{ + const char* weerliveHost = "weerlive.nl"; + const int httpPort = 80; + int weerliveStatus = 0; + String tempString; + int startPos, endPos; + int32_t maxWait; + char jsonResponse[1536]; + char val[51] = ""; + bool gotData = false; + + WiFiClient weerliveClient; + + DebugTf("getWeerLiveData(%s)\r\n", weerliveHost); + + // We now create a URI for the request + String url = "/api/json-data-10min.php?key="; + url += settingWeerLiveAUTH; + url += "&locatie="; + url += settingWeerLiveLocation; + + DebugTf("Requesting URL: %s/api/json-data-10min.php?key=secret&locatie=%s\r\n", weerliveHost, settingWeerLiveLocation); + Debugln(url); + if (!weerliveClient.connect(weerliveHost, httpPort)) + { + DebugTln("connection failed"); + sprintf(tempMessage, "connection to %s failed", weerliveHost); + weerliveClient.flush(); + weerliveClient.stop(); + return; + } + + // This will send the request to the server + weerliveClient.print(String("GET ") + url + " HTTP/1.1\r\n" + + "Host: " + weerliveHost + "\r\n" + + "Connection: close\r\n\r\n"); + delay(10); + + weerliveClient.setTimeout(5000); + + while ((weerliveClient.connected() || weerliveClient.available()) && !gotData) + { + yield(); + while(weerliveClient.available() && !gotData) + { + //--- skip to find HTTP/1.1 + //--- then parse response code + if (weerliveClient.find("HTTP/1.1")) + { + weerliveStatus = weerliveClient.parseInt(); // parse status code + DebugTf("Statuscode: [%d] ", weerliveStatus); + if (weerliveStatus != 200) + { + Debugln(" ERROR!"); + weerliveClient.flush(); + weerliveClient.stop(); + return; + } + Debugln(" OK!"); + } + else + { + DebugTln("Error reading weerLive.. -> bailout!"); + weerliveClient.flush(); + weerliveClient.stop(); + return; + } + //--- skip headers + if (weerliveClient.find("\r\n\r\n")) + { + int charsRead = 0; + charsRead = weerliveClient.readBytesUntil('\0', jsonResponse, sizeof(jsonResponse)); + jsonResponse[(charsRead -1)] = '\0'; + gotData = true; + DebugTln("Got weer data!"); + } + } // while available .. + + } // connected .. + + weerliveClient.flush(); + weerliveClient.stop(); + //-- jsonResponse looks like: + //-- { "liveweer": [{"plaats": "Baarn", "timestamp": "1683105785", "time": "03-05-2023 11:23", "temp": "10.4", "gtemp": "8.8", "samenv": "Licht bewolkt", "lv": "56", "windr": "NO", "windrgr": "44", "windms": "3", "winds": "2", "windk": "5.8", "windkmh": "10.8", "luchtd": "1029.4", "ldmmhg": "772", "dauwp": "2", "zicht": "35", "verw": "Zonnig en droog, donderdag warmer", "sup": "06:03", "sunder": "21:08", "image": "lichtbewolkt", "d0weer": "halfbewolkt", "d0tmax": "15", "d0tmin": "3", "d0windk": "2", "d0windknp": "6", "d0windms": "3", "d0windkmh": "11", "d0windr": "NO", "d0windrgr": "44", "d0neerslag": "0", "d0zon": "35", "d1weer": "halfbewolkt", "d1tmax": "20", "d1tmin": "5", "d1windk": "2", "d1windknp": "6", "d1windms": "3", "d1windkmh": "11", "d1windr": "O", "d1windrgr": "90", "d1neerslag": "20", "d1zon": "60", "d2weer": "regen", "d2tmax": "19", "d2tmin": "12", "d2windk": "2", "d2windknp": "6", "d2windms": "3", "d2windkmh": "11", "d2windr": "ZW", "d2windrgr": "225", "d2neerslag": "80", "d2zon": "30", "alarm": "0", "alarmtxt": ""}]} + + int prevLength = strlen(jsonResponse); + strTrimCntr(jsonResponse, 1534); + DebugTf("jsonResponse now [%d]chars (before trim [%d]chars)\r\n", strlen(jsonResponse), prevLength); + DebugTf("jsonResponse is [%s]\r\n\n", jsonResponse); + + parseJsonKey(jsonResponse, "plaats", val, 50); + snprintf(tempMessage, LOCAL_SIZE, val); + parseJsonKey(jsonResponse, "samenv", val, 50); + snprintf(cMsg, LOCAL_SIZE, " %s %s ", tempMessage, val); + parseJsonKey(jsonResponse, "d0tmin", val, 50); + snprintf(tempMessage, LOCAL_SIZE, "%s min %s°C ", cMsg, val); + parseJsonKey(jsonResponse, "d0tmax", val, 50); + snprintf(cMsg, LOCAL_SIZE, "%s max %s°C", tempMessage, val); + parseJsonKey(jsonResponse, "luchtd", val, 50); + snprintf(tempMessage, LOCAL_SIZE, "%s - luchtdruk %s hPa ", cMsg, val); + + parseJsonKey(jsonResponse, "d1weer", val, 50); + snprintf(cMsg, LOCAL_SIZE, "%s - morgen %s ", tempMessage, val); + parseJsonKey(jsonResponse, "d1tmin", val, 50); + snprintf(tempMessage, LOCAL_SIZE, "%s min %s°C ", cMsg, val); + parseJsonKey(jsonResponse, "d1tmax", val, 50); + snprintf(cMsg, LOCAL_SIZE, "%s max %s°C", tempMessage, val); + + snprintf(tempMessage, LOCAL_SIZE, "%s", cMsg); + Debugln("\r\n"); + Debugf("\tWeer[%s]\r\n", tempMessage); + +} // getWeerLiveData() + + +/*************************************************************************** +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to permit +* persons to whom the Software is furnished to do so, subject to the +* following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +**************************************************************************** +*/