diff --git a/Doxyfile b/Doxyfile index c5084fb0..4a1f61a9 100644 --- a/Doxyfile +++ b/Doxyfile @@ -470,7 +470,7 @@ EXTRACT_ALL = YES # be included in the documentation. # The default value is: NO. -EXTRACT_PRIVATE = NO +EXTRACT_PRIVATE = YES # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. @@ -488,7 +488,7 @@ EXTRACT_PACKAGE = NO # included in the documentation. # The default value is: NO. -EXTRACT_STATIC = NO +EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, @@ -2177,6 +2177,7 @@ INCLUDE_FILE_PATTERNS = # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. PREDEFINED = __cdecl= \ + __DOXYGEN__ \ TEMP_SENSOR \ OFF_PEAK_TARIFF \ RF_PRESENT \ @@ -2369,7 +2370,7 @@ UML_LIMIT_NUM_FIELDS = 10 # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -TEMPLATE_RELATIONS = NO +TEMPLATE_RELATIONS = YES # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the @@ -2511,7 +2512,7 @@ PLANTUML_INCLUDE_PATH = # Minimum value: 0, maximum value: 10000, default value: 50. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_GRAPH_MAX_NODES = 50 +DOT_GRAPH_MAX_NODES = 200 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs # generated by dot. A depth value of 3 means that only nodes reachable from the diff --git a/Mk2_3phase_RFdatalog_temp/Doxyfile b/Mk2_3phase_RFdatalog_temp/Doxyfile index 753aa0fc..a453aa00 100644 --- a/Mk2_3phase_RFdatalog_temp/Doxyfile +++ b/Mk2_3phase_RFdatalog_temp/Doxyfile @@ -467,7 +467,7 @@ EXTRACT_ALL = YES # be included in the documentation. # The default value is: NO. -EXTRACT_PRIVATE = NO +EXTRACT_PRIVATE = YES # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. @@ -485,7 +485,7 @@ EXTRACT_PACKAGE = NO # included in the documentation. # The default value is: NO. -EXTRACT_STATIC = NO +EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, diff --git a/Mk2_3phase_RFdatalog_temp/Mk2_3phase_RFdatalog_temp.ino b/Mk2_3phase_RFdatalog_temp/Mk2_3phase_RFdatalog_temp.ino index 16a621ab..c9398a71 100644 --- a/Mk2_3phase_RFdatalog_temp/Mk2_3phase_RFdatalog_temp.ino +++ b/Mk2_3phase_RFdatalog_temp/Mk2_3phase_RFdatalog_temp.ino @@ -140,11 +140,49 @@ * - heavy refactoring (again) * - stl add-ons * - add relay-output feature + * + * __February 2024: changes:__ + * - refactoring of 'temperature feature' + * - refactoring of 'relay feature' + * - new sliding average (EWMA) + * - more documentation + * + * __March 2024: changes:__ + * - multi-relay feature + * - add DEMA and TEMA sliding average + * - some tiny fixes + * - even more documentation * * @author Fred Metrich - * @copyright Copyright (c) 2023 + * @copyright Copyright (c) 2024 * */ + +/*! + * @defgroup TimeCritical Time critical functions + * Functions used by the ISR + */ + +/*! + * @defgroup RelayDiversion Relay diversion feature + * Functions used for the relay diversion + */ + +/*! + * @defgroup TemperatureSensing Temperature sensing feature + * Functions used for the temperature sensing + */ + +/*! + * @defgroup DualTariff Dual tariff feature + * Functions used for the dual tariff feature + */ + +/*! + * @defgroup RF RF feature + * Functions used for the RF feature + */ + static_assert(__cplusplus >= 201703L, "**** Please define 'gnu++17' in 'platform.txt' ! ****"); static_assert(__cplusplus >= 201703L, "See also : https://github.com/FredM67/PVRouter-3-phase/blob/main/Mk2_3phase_RFdatalog_temp/Readme.md"); diff --git a/Mk2_3phase_RFdatalog_temp/Readme.en.md b/Mk2_3phase_RFdatalog_temp/Readme.en.md new file mode 100644 index 00000000..3c745a54 --- /dev/null +++ b/Mk2_3phase_RFdatalog_temp/Readme.en.md @@ -0,0 +1,59 @@ +[![en](https://img.shields.io/badge/lang-en-red.svg)](Readme.en.md) +[![fr](https://img.shields.io/badge/lang-fr-blue.svg)](Readme.md) + +This program is to be used with the Arduino IDE and/or other development IDE like VSCode + PlatformIO. + +# Use with Arduino IDE + +You'll need to download and install the **latest** [Arduino IDE](https://www.arduino.cc/en/software). + +Download the "standalone" version, NOT the version from the Microsoft Store. +Pick-up the "Win 10 and newer, 64 bits" or the "MSI installer" version. + +Since the code is optimized with the latest standard of C++, you'll need to edit a config file to activate C++17. + +Please search the file '**platform.txt**' located in the installation path of the Arduino IDE. + +For **Windows**, typically, you'll find the file in '**C:\Program Files (x86)\Arduino\hardware\arduino\avr**' and/or in '**%LOCALAPPDATA%\Arduino15\packages\arduino\hardware\avr\x.y.z**' where 'x.y.z' is the version of the **Arduino AVR Boards** package. + +You can type this command in a Powershell : `Get-Childitem –Path C:\ -Include platform.txt -Recurse -ErrorAction SilentlyContinue`. It could take a couple of seconds/minutes until the file is found. + +For **Linux**, if using the AppImage package, you'll find this file in '**~/.arduino15/packages/arduino/hardware/avr/1.8.6**'. +You can run `find / -name platform.txt 2>/dev/null` in case the location has been changed. + +Edit the file in any Text Editor (you'll need **Admin rights**) and replace the parameter '**-std=gnu++11**' with '**-std=gnu++17**'. That's it! + +If your Arduino IDE was opened, please close all the instances and open it again. + +# Use with Visual Studio Code + +You'll need to install additional extension(s). The most popular and used extensions for this job are '*Arduino*' and '*Platform IO*'. + +# Quick overview of the files + +- **Mk2_3phase_RFdatalog_temp.ino** : This file is needed for Arduino IDE +- **calibration.h** : contains the calibration parameters +- **config.h** : the user's preferences are stored here (pin assignments, features, ...) +- **config_system.h** : rarely modified system constants +- **constants.h** : some constants - *do not edit* +- **debug.h** : some macros for serial output and debugging +- **dualtariff.h** : definitions for the dual tariff feature +- **main.cpp** : source code +- **main.h** : functions prototypes +- **movingAvg.h** : source code for sliding-window average +- **processing.cpp** : source code for the processing engine +- **processing.h** : functions prototype of the processing engine +- **Readme.md** : this file +- **types.h** : definitions of types, ... +- **type_traits.h** : some STL stuff not yet available in the avr-package +- **type_traits** : folder containing some missing STL helpers +- **utils_relay.h** : source code for the *relay-diversion* feature +- **utils_rf.h** : source code for the *RF* feature +- **utils_temp.h** : source code for the *temperature* feature +- **utils.h** : helper functions and misc stuff +- **validation.h** : config validation, this code is executed during compile-time only ! +- **platformio.ini** : PlatformIO configuration +- **inject_sketch_name.py** : helper script for PlatformIO +- **Doxyfile** : config for Doxygen (code documentation) + +The end-user should ONLY edit both files **calibration.h** and **config.h**. diff --git a/Mk2_3phase_RFdatalog_temp/Readme.md b/Mk2_3phase_RFdatalog_temp/Readme.md index ab98ba13..1ae1c03c 100644 --- a/Mk2_3phase_RFdatalog_temp/Readme.md +++ b/Mk2_3phase_RFdatalog_temp/Readme.md @@ -1,59 +1,329 @@ -[![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/FredM67/PVRouter-3-phase/blob/main/Mk2_3phase_RFdatalog_temp/Readme.md) -[![fr](https://img.shields.io/badge/lang-fr-blue.svg)](https://github.com/FredM67/PVRouter-3-phase/blob/main/Mk2_3phase_RFdatalog_temp/Readme.fr.md) +[![en](https://img.shields.io/badge/lang-en-red.svg)](Readme.en.md) -This program is to be used with the Arduino IDE and/or other development IDE like VSCode + PlatformIO. +Ce programme est conçu pour être utilisé avec l'IDE Arduino et/ou d'autres IDE de développement comme VSCode + PlatformIO. -# Use with Arduino IDE +- [Utilisation avec Arduino IDE](#utilisation-avec-arduino-ide) +- [Utilisation avec Visual Studio Code](#utilisation-avec-visual-studio-code) +- [Aperçu rapide des fichiers](#aperçu-rapide-des-fichiers) +- [Étalonnage du routeur](#étalonnage-du-routeur) +- [Configuration du programme](#configuration-du-programme) + - [Configuration des sorties TRIAC](#configuration-des-sorties-triac) + - [Configuration des sorties relais tout-ou-rien](#configuration-des-sorties-relais-tout-ou-rien) + - [Principe de fonctionnement](#principe-de-fonctionnement) + - [Configuration du Watchdog](#configuration-du-watchdog) + - [Configuration du ou des capteurs de température](#configuration-du-ou-des-capteurs-de-température) + - [Activation de la fonctionnalité](#activation-de-la-fonctionnalité) + - [Avec l'Arduino IDE](#avec-larduino-ide) + - [Avec Visual Studio Code et PlatformIO](#avec-visual-studio-code-et-platformio) + - [Configuration du ou des capteurs (commun aux 2 cas précédents)](#configuration-du-ou-des-capteurs-commun-aux-2-cas-précédents) + - [Configuration de la gestion des Heures Creuses (dual tariff)](#configuration-de-la-gestion-des-heures-creuses-dual-tariff) + - [Configuration matérielle](#configuration-matérielle) + - [Configuration logicielle](#configuration-logicielle) + - [Rotation des priorités](#rotation-des-priorités) + - [Configuration de la marche forcée](#configuration-de-la-marche-forcée) + - [Arrêt du routage](#arrêt-du-routage) -You'll need to download and install the **latest** [Arduino IDE](https://www.arduino.cc/en/software). +# Utilisation avec Arduino IDE -Download the "standalone" version, NOT the version from the Microsoft Store. -Pick-up the "Win 10 and newer, 64 bits" or the "MSI installer" version. +Pour utiliser ce programme avec l'IDE Arduino, vous devez télécharger et installer la dernière version de l'IDE Arduino. Choisissez la version "standard", PAS la version du Microsoft Store. Optez pour la version "Win 10 et plus récent, 64 bits" ou la version "MSI installer". -Since the code is optimized with the latest standard of C++, you'll need to edit a config file to activate C++17. +Comme le code est optimisé avec l'une des dernières normes C++, vous devez modifier un fichier de configuration pour activer C++17. Vous trouverez le fichier '**platform.txt**' dans le chemin d'installation de l'IDE Arduino. -Please search the file '**platform.txt**' located in the installation path of the Arduino IDE. +Pour **Windows**, vous trouverez généralement le fichier dans '**C:\Program Files (x86)\Arduino\hardware\arduino\avr**' et/ou dans '**%LOCALAPPDATA%\Arduino15\packages\arduino\hardware\avr\x.y.z**' où **'x.y.z**' est la version du package Arduino AVR Boards. -For **Windows**, typically, you'll find the file in '**C:\Program Files (x86)\Arduino\hardware\arduino\avr**' and/or in '**%LOCALAPPDATA%\Arduino15\packages\arduino\hardware\avr\x.y.z**' where 'x.y.z' is the version of the **Arduino AVR Boards** package. +Vous pouvez également exécuter cette commande dans Powershell : `Get-Childitem –Path C:\ -Include platform.txt -Recurse -ErrorAction SilentlyContinue`. Cela peut prendre quelques secondes/minutes jusqu'à ce que le fichier soit trouvé. -You can type this command in a Powershell : `Get-Childitem –Path C:\ -Include platform.txt -Recurse -ErrorAction SilentlyContinue`. It could take a couple of seconds/minutes until the file is found. +Pour **Linux**, si vous utilisez le package AppImage, vous trouverez ce fichier dans '~/.arduino15/packages/arduino/hardware/avr/1.8.6'. Vous pouvez exécuter `find / -name platform.txt 2>/dev/null` au cas où l'emplacement aurait changé. -For **Linux**, if using the AppImage package, you'll find this file in '**~/.arduino15/packages/arduino/hardware/avr/1.8.6**'. -You can run `find / -name platform.txt 2>/dev/null` in case the location has been changed. +Pour **MacOSX**, ce fichier se trouve dans '/Users/[user]/Library/Arduino15/packages/arduino/hardware/avr/1.8.6'. -Edit the file in any Text Editor (you'll need **Admin rights**) and replace the parameter '**-std=gnu++11**' with '**-std=gnu++17**'. That's it! +Ouvrez le fichier dans n'importe quel éditeur de texte (vous aurez besoin des droits d'administrateur) et remplacez le paramètre '**-std=gnu++11**' par '**-std=gnu++17**'. C'est tout ! -If your Arduino IDE was opened, please close all the instances and open it again. +Si votre IDE Arduino était ouvert, veuillez fermer toutes les instances et le rouvrir. -# Use with Visual Studio Code +# Utilisation avec Visual Studio Code -You'll need to install additional extension(s). The most popular and used extensions for this job are '*Arduino*' and '*Platform IO*'. +Vous devrez installer des extensions supplémentaires. Les extensions les plus populaires et les plus utilisées pour ce travail sont '*Arduino*' et '*Platform IO*'. +L'ensemble du projet a été conçu pour être utilisé de façon optimale avec *Platform IO*. -# Quick overview of the files +# Aperçu rapide des fichiers -- **Mk2_3phase_RFdatalog_temp.ino** : This file is needed for Arduino IDE -- **calibration.h** : contains the calibration parameters -- **config.h** : the user's preferences are stored here (pin assignments, features, ...) -- **config_system.h** : rarely modified system constants -- **constants.h** : some constants - *do not edit* -- **debug.h** : some macros for serial output and debugging -- **dualtariff.h** : definitions for the dual tariff feature -- **main.cpp** : source code -- **main.h** : functions prototypes -- **movingAvg.h** : source code for sliding-window average -- **processing.cpp** : source code for the processing engine -- **processing.h** : functions prototype of the processing engine -- **Readme.md** : this file -- **types.h** : definitions of types, ... -- **type_traits.h** : some STL stuff not yet available in the avr-package -- **type_traits** : folder containing some missing STL helpers -- **utils_relay.h** : source code for the *relay-diversion* feature -- **utils_rf.h** : source code for the *RF* feature -- **utils_temp.h** : source code for the *temperature* feature -- **utils.h** : helper functions and misc stuff -- **validation.h** : config validation, this code is executed during compile-time only ! -- **platformio.ini** : PlatformIO configuration -- **inject_sketch_name.py** : helper script for PlatformIO -- **Doxyfile** : config for Doxygen (code documentation) +- **Mk2_3phase_RFdatalog_temp.ino** : Ce fichier est nécessaire pour l’IDE Arduino +- **calibration.h** : contient les paramètres d’étalonnage +- **config.h** : les préférences de l’utilisateur sont stockées ici (affectation des broches, fonctionnalités …) +- **config_system.h** : constantes système rarement modifiées +- **constants.h** : quelques constantes — *ne pas modifier* +- **debug.h** : Quelques macros pour la sortie série et le débogage +- **dualtariff.h** : définitions de la fonction double tarif +- **main.cpp** : code source principal +- **main.h** : prototypes de fonctions +- **movingAvg.h** : code source pour la moyenne glissante +- **processing.cpp** : code source du moteur de traitement +- **processing.h** : prototypes de fonctions du moteur de traitement +- **Readme.fr.md** : ce fichier +- **types.h** : définitions des types … +- **type_traits.h** : quelques trucs STL qui ne sont pas encore disponibles dans le paquet avr +- **type_traits** : contient des patrons STL manquants +- **utils_relay.h** : code source de la fonctionnalité *diversion par relais* +- **utils_rf.h** : code source de la fonction *RF* +- **utils_temp.h** : code source de la fonctionnalité *Température* +- **utils.h** : fonctions d’aide et trucs divers +- **validation.h** : validation des paramètres, ce code n’est exécuté qu’au moment de la compilation ! +- **platformio.ini** : paramètres PlatformIO +- **inject_sketch_name.py** : script d'aide pour PlatformIO +- **Doxyfile** : paramètre pour Doxygen (documentation du code) -The end-user should ONLY edit both files **calibration.h** and **config.h**. +L’utilisateur final ne doit éditer QUE les fichiers **calibration.h** et **config.h**. + +# Étalonnage du routeur +Les valeurs d'étalonnage se trouvent dans le fichier **calibration.h**. +Il s'agit de la ligne : +```cpp +inline constexpr float f_powerCal[NO_OF_PHASES]{ 0.05000F, 0.05000F, 0.05000F }; +``` + +Ces valeurs par défaut doivent être déterminées pour assurer un fonctionnement optimal du routeur. + +# Configuration du programme + +La configuration d'une fonctionnalité suit généralement deux étapes : +- Activation de la fonctionnalité +- Configuration des paramètres de la fonctionnalité + +La cohérence de la configuration est vérifiée lors de la compilation. Par exemple, si une *pin* est allouée deux fois par erreur, le compilateur générera une erreur. + +## Configuration des sorties TRIAC + +La première étape consiste à définir le nombre de sorties TRIAC : + +```cpp +inline constexpr uint8_t NO_OF_DUMPLOADS{ 2 }; +``` + +Ensuite, il faudra assigner les *pins* correspondantes ainsi que l'ordre des priorités au démarrage. +```cpp +inline constexpr uint8_t physicalLoadPin[NO_OF_DUMPLOADS]{ 5, 7 }; +inline constexpr uint8_t loadPrioritiesAtStartup[NO_OF_DUMPLOADS]{ 0, 1 }; +``` + +## Configuration des sorties relais tout-ou-rien +Les sorties relais tout-ou-rien permettent d'alimenter des appareils qui contiennent de l'électronique (pompe à chaleur …). + +Il faudra activer la fonctionnalité comme ceci : +```cpp +inline constexpr bool RELAY_DIVERSION{ true }; +``` + +Chaque relais nécessite la définition de cinq paramètres : +- le numéro de **pin** sur laquelle est branché le relais +- le **seuil de surplus** avant mise en route (par défaut **1000 W**) +- le **seuil d'import** avant arrêt (par défaut **200 W**) +- la **durée de fonctionnement minimale** en minutes (par défaut **5 min**) +- la **durée d'arrêt minimale** en minutes (par défaut **5 min**). + +Exemple de configuration d'un relais : +```cpp +inline constexpr RelayEngine relays{ { { 4, 1000, 200, 10, 10 } } }; +``` +Dans cet exemple, le relais est connecté sur la *pin* **4**, il se déclenchera à partir de **1000 W** de surplus, s'arrêtera à partir de **200 W** d'import, et a une durée minimale de fonctionnement et d'arrêt de **10 min**. + +Pour configurer plusieurs relais, listez simplement les configurations de chaque relais : +```cpp +inline constexpr RelayEngine relays{ { { 4, 1000, 200, 10, 10 }, + { 3, 1500, 250, 5, 15 } } }; +``` +Les relais sont activés dans l'ordre de la liste, et désactivés dans l'ordre inverse. +Dans tous les cas, les durées minimales de fonctionnement et d'arrêt sont toujours respectées. + +### Principe de fonctionnement +Les seuils de surplus et d'import sont calculés en utilisant une moyenne mobile pondérée exponentiellement (EWMA), dans notre cas précis, il s'agit d'une modification d'une moyenne mobile triple exponentiellement pondérée (TEMA). +Par défaut, cette moyenne est calculée sur une fenêtre d'environ **10 min**. Vous pouvez ajuster cette durée pour l'adapter à vos besoins. +Il est possible de la rallonger mais aussi de la raccourcir. +Pour des raisons de performances de l'Arduino, la durée choisie sera arrondie à une durée proche qui permettra de faire les calculs sans impacter les performances du routeur. + +Si l'utilisateur souhaite plutôt une fenêtre de 15 min, il suffira d'écrire : +```cpp +inline constexpr RelayEngine relays{ 15_i, { { 3, 1000, 200, 1, 1 } } }; +``` +___ +**_Note_** +Attention au suffixe '**_i**' après le nombre *15* ! +___ + +Les relais configurés dans le système sont gérés par un système similaire à une machine à états. +Chaque seconde, le système augmente la durée de l'état actuel de chaque relais et procède avec tous les relais en fonction de la puissance moyenne actuelle : +- si la puissance moyenne actuelle est supérieure au seuil d'import, elle essaie d'éteindre certains relais. +- si la puissance moyenne actuelle est supérieure au seuil de surplus, elle essaie d'allumer plus de relais. + +Les relais sont traités dans l'ordre croissant pour le surplus et dans l'ordre décroissant pour l'importation. + +Pour chaque relais, la transition ou le changement d'état est géré de la manière suivante : +- si le relais est *OFF* et que la puissance moyenne actuelle est inférieure au seuil de surplus, le relais essaie de passer à l'état *ON*. Cette transition est soumise à la condition que le relais ait été *OFF* pendant au moins la durée *minOFF*. +- si le relais est *ON* et que la puissance moyenne actuelle est supérieure au seuil d'importation, le relais essaie de passer à l'état *OFF*. Cette transition est soumise à la condition que le relais ait été *ON* pendant au moins la durée *minON*. + +## Configuration du Watchdog +Un chien de garde, en anglais *watchdog*, est un circuit électronique ou un logiciel utilisé en électronique numérique pour s'assurer qu'un automate ou un ordinateur ne reste pas bloqué à une étape particulière du traitement qu'il effectue. + +Ceci est réalisé à l'aide d'une LED qui clignote à la fréquence de 1 Hz, soit toutes les secondes. +Ainsi, l'utilisateur sait d'une part si son routeur est allumé, et si jamais cette LED ne clignote plus, c'est que l'Arduino s'est bloqué (cas encore jamais rencontré !). +Un simple appui sur le bouton *Reset* permettra de redémarrage le système sans rien débrancher. + +Il faudra activer la fonctionnalité comme ceci : +```cpp +inline constexpr bool WATCHDOG_PIN_PRESENT{ true }; +``` +et définir la *pin* utilisée, dans l'exemple la *9* : +```cpp +inline constexpr uint8_t watchDogPin{ 9 }; +``` + +## Configuration du ou des capteurs de température +Il est possible de brancher un ou plusieurs capteurs de température Dallas DS18B20. +Ces capteurs peuvent servir à des fins informatives ou pour contrôler le mode de fonctionnement forcé. + +Pour activer cette fonctionnalité, il faudra procéder différemment selon que l'on utilise l'Arduino IDE ou Visual Studio Code avec l'extension PlatformIO. + +### Activation de la fonctionnalité + +Pour activer cette fonctionnalité, la procédure diffère selon que vous utilisez l'Arduino IDE ou Visual Studio Code avec l'extension PlatformIO. + +#### Avec l'Arduino IDE +Activez la ligne suivante en supprimant le commentaire : +```cpp +#define TEMP_ENABLED +``` + +Si la bibliothèque *OneWire* n'est pas installée, installez-la via le menu **Outils** => **Gérer les bibliothèques…**. +Recherchez "Onewire" et installez "**OneWire** par Jim Studt, …" en version **2.3.7** ou plus récente. + +#### Avec Visual Studio Code et PlatformIO +Sélectionnez la configuration "**env:temperature (Mk2_3phase_RFdatalog_temp)**". + +### Configuration du ou des capteurs (commun aux 2 cas précédents) +Pour configurer les capteurs, vous devez entrer leurs adresses. +Utilisez un programme pour scanner les capteurs connectés. +Vous pouvez trouver de tels programmes sur Internet ou parmi les exemples fournis avec l'Arduino IDE. +Il est recommandé de coller une étiquette avec l'adresse de chaque capteur sur son câble. + +Entrez les adresses comme suit : +```cpp +inline constexpr TemperatureSensing temperatureSensing{ 4, + { { 0x28, 0xBE, 0x41, 0x6B, 0x09, 0x00, 0x00, 0xA4 }, + { 0x28, 0x1B, 0xD7, 0x6A, 0x09, 0x00, 0x00, 0xB7 } } }; +``` +Le nombre *4* en premier paramètre est la *pin* que l'utilisateur aura choisi pour le bus *OneWire*. + +___ +**_Note_** +Plusieurs capteurs peuvent être branchés sur le même câble. +Sur Internet vous trouverez tous les détails concernant la topologie utilisable avec ce genre de capteurs. +___ + +## Configuration de la gestion des Heures Creuses (dual tariff) +Il est possible de confier la gestion des Heures Creuses au routeur. +Cela permet par exemple de limiter la chauffe en marche forcée afin de ne pas trop chauffer l'eau dans l'optique d'utiliser le surplus le lendemain matin. +Cette limite peut être en durée ou en température (nécessite d'utiliser un capteur de température Dallas DS18B20). + +### Configuration matérielle +Décâblez la commande du contacteur Jour/Nuit, qui n'est plus nécessaire. +Reliez directement une *pin* choisie au contact sec du compteur (bornes *C1* et *C2*). +___ +**__ATTENTION__** +Il faut relier **directement**, une paire *pin/masse* avec les bornes *C1/C2* du compteur. +Il NE doit PAS y avoir de 230 V sur ce circuit ! +___ + +### Configuration logicielle +Activez la fonctionnalité comme suit : +```cpp +inline constexpr bool DUAL_TARIFF{ true }; +``` +Configurez la *pin* sur laquelle est relié le compteur : +```cpp +inline constexpr uint8_t dualTariffPin{ 3 }; +``` + +Configurez la durée en *heures* de la période d'Heures Creuses (pour l'instant, une seule période est supportée par jour) : +```cpp +inline constexpr uint8_t ul_OFF_PEAK_DURATION{ 8 }; +``` + +Enfin, on définira les modalités de fonctionnement pendant la période d'Heures Creuses : +```cpp +inline constexpr pairForceLoad rg_ForceLoad[NO_OF_DUMPLOADS]{ { -3, 2 } }; +``` +Il est possible de définir une configuration pour chaque charge indépendamment l'une des autres. +Le premier paramètre de *rg_ForceLoad* détermine la temporisation de démarrage par rapport au début ou à la fin des Heures Creuses : +- si le nombre est positif et inférieur à 24, il s'agit du nombre d'heures, +- si le nombre est négatif supérieur à −24, il s'agit du nombre d'heures par rapport à la fin des Heures Creuses +- si le nombre est positif et supérieur à 24, il s'agit du nombre de minutes, +- si le nombre est négatif inférieur à −24, il s'agit du nombre de minutes par rapport à la fin des Heures Creuses + +Le deuxième paramètre détermine la durée de la marche forcée : +- si le nombre est inférieur à 24, il s'agit du nombre d'heures, +- si le nombre est supérieur à 24, il s'agit du nombre de minutes. + +Exemples pour mieux comprendre (avec début d'HC à 23:00, jusqu'à 7:00 soit 8 h de durée) : +- ```{ -3, 2 }``` : démarrage **3 heures AVANT** la fin de période (à 4 h du matin), pour une durée de 2 h. +- ```{ 3, 2 }``` : démarrage **3 heures APRÈS** le début de période (à 2 h du matin), pour une durée de 2 h. +- ```{ -150, 2 }``` : démarrage **150 minutes AVANT** la fin de période (à 4:30), pour une durée de 2 h. +- ```{ 3, 180 }``` : démarrage **3 heures APRÈS** le début de période (à 2 h du matin), pour une durée de 180 min. + +Pour une durée *infinie* (donc jusqu'à la fin de la période d'HC), utilisez ```UINT16_MAX``` comme deuxième paramètre : +- ```{ -3, UINT16_MAX }``` : démarrage **3 heures AVANT** la fin de période (à 4 h du matin) avec marche forcée jusqu'à la fin de période d'HC. + +Si votre système est constitué 2 sorties (```NO_OF_DUMPLOADS``` aura alors une valeur de 2), et que vous souhaitez une marche forcée uniquement sur la 2ᵉ sortie, écrivez : +```cpp +inline constexpr pairForceLoad rg_ForceLoad[NO_OF_DUMPLOADS]{ { 0, 0 }, + { -3, 2 } }; +``` + +## Rotation des priorités +La rotation des priorités est utile lors de l'alimentation d'un chauffe-eau triphasé. +Elle permet d'équilibrer la durée de fonctionnement des différentes résistances sur une période prolongée. + +Mais elle peut aussi être intéressante si on veut permuter les priorités de deux appareils chaque jour (deux chauffe-eau, …). + +Une fois n'est pas coutume, l'activation de cette fonction possède 2 modes : +- **automatique**, on spécifiera alors +```cpp +inline constexpr RotationModes PRIORITY_ROTATION{ RotationModes::AUTO }; +``` +- **manuel**, on écrira alors +```cpp +inline constexpr RotationModes PRIORITY_ROTATION{ RotationModes::PIN }; +``` +En mode **automatique**, la rotation se fait automatiquement toutes les 24 h. +Em mode **manuel**, vous devez également définir la *pin* qui déclenchera la rotation : +```cpp +inline constexpr uint8_t rotationPin{ 10 }; +``` + +## Configuration de la marche forcée +Il est possible de déclencher la marche forcée (certains routeurs appellent cette fonction *Boost*) via une *pin*. +On peut y relier un micro-interrupteur, une minuterie (ATTENTION, PAS de 230 V sur cette ligne), ou tout autre contact sec. + +Pour activer cette fonctionnalité, utilisez le code suivant : +```cpp +inline constexpr bool OVERRIDE_PIN_PRESENT{ true }; +``` +Vous devez également spécifier la *pin* à laquelle le contact sec est connecté : +```cpp +inline constexpr uint8_t forcePin{ 11 }; +``` + +## Arrêt du routage +Il peut être pratique de désactiver le routage lors d'une absence prolongée. +Cette fonctionnalité est particulièrement utile si la *pin* de commande est connectée à un contact sec qui peut être contrôlé à distance, par exemple via une routine Alexa ou similaire. +Ainsi, vous pouvez désactiver le routage pendant votre absence et le réactiver un ou deux jours avant votre retour, afin de disposer d'eau chaude (gratuite) à votre arrivée. + +Pour activer cette fonctionnalité, utilisez le code suivant : +```cpp +inline constexpr bool DIVERSION_PIN_PRESENT{ true }; +``` +Vous devez également spécifier la *pin* à laquelle le contact sec est connecté : +```cpp +inline constexpr uint8_t diversionPin{ 12 }; +``` + +*doc non finie* diff --git a/Mk2_3phase_RFdatalog_temp/config.h b/Mk2_3phase_RFdatalog_temp/config.h index 810374ef..f3c916d1 100644 --- a/Mk2_3phase_RFdatalog_temp/config.h +++ b/Mk2_3phase_RFdatalog_temp/config.h @@ -35,7 +35,6 @@ //-------------------------------------------------------------------------------------------------- // constants which must be set individually for each system // -inline constexpr uint8_t NO_OF_PHASES{ 3 }; /**< number of phases of the main supply. */ inline constexpr uint8_t NO_OF_DUMPLOADS{ 3 }; /**< number of dump loads connected to the diverter */ #ifdef EMONESP @@ -76,14 +75,13 @@ inline constexpr uint8_t physicalLoadPin[NO_OF_DUMPLOADS]{ 5, 6, 7 }; /* inline constexpr uint8_t loadPrioritiesAtStartup[NO_OF_DUMPLOADS]{ 0, 1, 2 }; /**< load priorities and states at startup */ // Set the value to 0xff when the pin is not needed (feature deactivated) -inline constexpr uint8_t relayPin{ 0xff }; /**< for 3-phase PCB, relay trigger */ inline constexpr uint8_t dualTariffPin{ 0xff }; /**< for 3-phase PCB, off-peak trigger */ inline constexpr uint8_t diversionPin{ 0xff }; /**< if LOW, set diversion on standby */ inline constexpr uint8_t rotationPin{ 0xff }; /**< if LOW, trigger a load priority rotation */ inline constexpr uint8_t forcePin{ 0xff }; /**< for 3-phase PCB, force pin */ inline constexpr uint8_t watchDogPin{ 4 }; /**< watch dog LED */ -inline constexpr relayOutput relay_Output{ relayPin, 1000, 200, 1, 1 }; /**< config for relay diversion, see class definition for defaults and advanced options */ +inline constexpr RelayEngine relays{ { { 0xff, 1000, 200, 1, 1 } } }; /**< config for relay diversion, see class definition for defaults and advanced options */ inline constexpr uint8_t ul_OFF_PEAK_DURATION{ 8 }; /**< Duration of the off-peak period in hours */ inline constexpr pairForceLoad rg_ForceLoad[NO_OF_DUMPLOADS]{ { -3, 2 } }; /**< force config for load #1 ONLY for dual tariff */ diff --git a/Mk2_3phase_RFdatalog_temp/config_system.h b/Mk2_3phase_RFdatalog_temp/config_system.h index 3f6b842d..e08b8255 100644 --- a/Mk2_3phase_RFdatalog_temp/config_system.h +++ b/Mk2_3phase_RFdatalog_temp/config_system.h @@ -16,6 +16,8 @@ #include "type_traits.hpp" +inline constexpr uint8_t NO_OF_PHASES{ 3 }; /**< number of phases of the main supply. */ + //-------------------------------------------------------------------------------------------------- // for users with zero-export profile, this value will be negative inline constexpr int16_t REQUIRED_EXPORT_IN_WATTS{ 00 }; /**< when set to a negative value, this acts as a PV generator */ diff --git a/Mk2_3phase_RFdatalog_temp/dualtariff.h b/Mk2_3phase_RFdatalog_temp/dualtariff.h index 988995ae..baa8fce1 100644 --- a/Mk2_3phase_RFdatalog_temp/dualtariff.h +++ b/Mk2_3phase_RFdatalog_temp/dualtariff.h @@ -21,13 +21,14 @@ * * @tparam N # of loads * @tparam D + * + * @ingroup DualTariff */ template< uint8_t N, uint8_t OffPeakDuration = 8 > class _rg_OffsetForce { public: constexpr _rg_OffsetForce() - : _rg() { constexpr uint16_t uiPeakDurationInSec{ OffPeakDuration * 3600 }; // calculates offsets for force start and stop of each load @@ -49,13 +50,13 @@ class _rg_OffsetForce } } } - const uint32_t (&operator[](uint8_t i) const)[2] + const auto (&operator[](uint8_t i) const) { return _rg[i]; } private: - uint32_t _rg[N][2]; + uint32_t _rg[N][2]{}; }; inline uint32_t ul_TimeOffPeak; /**< 'timestamp' for start of off-peak period */ @@ -65,6 +66,7 @@ inline constexpr auto rg_OffsetForce{ _rg_OffsetForce< NO_OF_DUMPLOADS, ul_OFF_P /** * @brief Print the settings for off-peak period * + * @ingroup DualTariff */ inline void printDualTariffConfiguration() { diff --git a/Mk2_3phase_RFdatalog_temp/ewma_avg.hpp b/Mk2_3phase_RFdatalog_temp/ewma_avg.hpp new file mode 100644 index 00000000..eefb2603 --- /dev/null +++ b/Mk2_3phase_RFdatalog_temp/ewma_avg.hpp @@ -0,0 +1,136 @@ +/** + * @file ewma_avg.hpp + * @author Frédéric Metrich (frederic.metrich@live.fr) + * @brief This file implements an Exponentially Weighted Moving Average template class + * @version 0.1 + * @date 2024-02-27 + * + * @section description Description + * The Exponentially Weighted Moving Average (EWMA) is a quantitative or statistical measure used to model or describe a time series. + * The EWMA is widely used in finance, the main applications being technical analysis and volatility modeling. + * + * The moving average is designed as such that older observations are given lower weights. + * The weights fall exponentially as the data point gets older – hence the name exponentially weighted. + * + * The only decision a user of the EWMA must make is the parameter alpha. + * The parameter decides how important the current observation is in the calculation of the EWMA. + * The higher the value of alpha, the more closely the EWMA tracks the original time series. + * + * Computation of DEMA (Double EMA) with half-alpha has been added to get a better response of the average, + * especially when "peak inputs" are recorded. + * + * Computation of TEMA (Triple EMA) with quarter-alpha has been added to get a even better response of the average, + * especially when "peak inputs" are recorded. This seems to be the "optimal" solution. + * + * @section note Note + * This class is implemented in way to use only integer math. + * This comes with some restrictions on the alpha parameter, but the benefit of full integer math wins + * on the side-drawback. + * + * @copyright Copyright (c) 2024 + * + */ + +#ifndef EWMA_AVG_H +#define EWMA_AVG_H + +#include + +#include "type_traits.hpp" + +/** + * @brief Helper compile-time function to retrieve the previous power of 2 of the given number (120 => 64 => 6) + * + * @param v The input number + * @return constexpr uint8_t The next power of two + */ +constexpr uint8_t round_up_to_power_of_2(uint16_t v) +{ + if (__builtin_popcount(v) == 1) { return __builtin_ctz(v) - 1; } + + uint8_t next_pow_of_2{ 0 }; + + while (v) + { + v >>= 1; + ++next_pow_of_2; + } + + return --next_pow_of_2; +} + +/** + * @brief Exponentially Weighted Moving Average + * + * @details The smoothing factor is the approximate amount of values taken to calculate the average. + * Since the Arduino is very slow and does not provide any dedicated math co-processor, + * the smoothing factor will be rounded to the previous power of 2. Ie 120 will be rounded to 64. + * This allows to perform all the calculations with integer math, which is much faster ! + * + * @note Because of the 'sign extension', the sign is copied into lower bits. + * + * @tparam A Smoothing factor + * @param input Input value + * @return long Output value + */ +template< uint8_t A = 10 > +class EWMA_average +{ +public: + /** + * @brief Add a new value and actualize the EMA, DEMA and TEMA + * + * @param input The new value + */ + void addValue(int32_t input) + { + ema_raw = ema_raw - ema + input; + ema = ema_raw >> round_up_to_power_of_2(A); + + ema_ema_raw = ema_ema_raw - ema_ema + ema; + ema_ema = ema_ema_raw >> (round_up_to_power_of_2(A) - 1); + + ema_ema_ema_raw = ema_ema_ema_raw - ema_ema_ema + ema_ema; + ema_ema_ema = ema_ema_ema_raw >> (round_up_to_power_of_2(A) - 2); + } + + /** + * @brief Get the EMA + * + * @return auto The EMA value + */ + auto getAverageS() const + { + return ema; + } + + /** + * @brief Get the DEMA + * + * @return auto The DEMA value + */ + auto getAverageD() const + { + return (ema << 1) - ema_ema; + } + + /** + * @brief Get the TEMA + * + * @return auto The TEMA value + */ + auto getAverageT() const + { + return 3 * (ema - ema_ema) + ema_ema_ema; + } + +private: + int32_t ema_ema_ema_raw{ 0 }; + int32_t ema_ema_ema{ 0 }; + int32_t ema_ema_raw{ 0 }; + int32_t ema_ema{ 0 }; + int32_t ema_raw{ 0 }; + int32_t ema{ 0 }; +}; + +#endif \ No newline at end of file diff --git a/Mk2_3phase_RFdatalog_temp/main.cpp b/Mk2_3phase_RFdatalog_temp/main.cpp index 8fd0a405..ba3bdfbe 100644 --- a/Mk2_3phase_RFdatalog_temp/main.cpp +++ b/Mk2_3phase_RFdatalog_temp/main.cpp @@ -46,6 +46,7 @@ static_assert(__cplusplus >= 201703L, "See also : https://github.com/FredM67/PVR /** * @brief Interrupt Service Routine - Interrupt-Driven Analog Conversion. + * * @details An Interrupt Service Routine is now defined which instructs the ADC to perform a conversion * for each of the voltage and current sensors in turn. * @@ -71,6 +72,7 @@ static_assert(__cplusplus >= 201703L, "See also : https://github.com/FredM67/PVR * - Variables shared with main code may need to be protected by "critical sections" * - Don't try to turn interrupts off or on * + * @ingroup TimeCritical */ ISR(ADC_vect) { @@ -377,8 +379,8 @@ void loop() if constexpr (RELAY_DIVERSION) { - relay_Output.inc_duration(); - relay_Output.proceed_relay(); + relays.inc_duration(); + relays.proceed_relays(); } } } @@ -407,7 +409,7 @@ void loop() if constexpr (RELAY_DIVERSION) { - relay_Output.update_average(tx_data.power); + relays.update_average(tx_data.power); } if constexpr (TEMP_SENSOR_PRESENT) @@ -425,7 +427,7 @@ void loop() tx_data.temperature_x100[idx] = tmp; } while (idx); - + temperatureSensing.requestTemperatures(); // for use next time around } diff --git a/Mk2_3phase_RFdatalog_temp/movingAvg.h b/Mk2_3phase_RFdatalog_temp/movingAvg.h index 3af7a07d..1bc594d8 100644 --- a/Mk2_3phase_RFdatalog_temp/movingAvg.h +++ b/Mk2_3phase_RFdatalog_temp/movingAvg.h @@ -16,61 +16,6 @@ #include "type_traits.hpp" -/** - * @brief Helper compile-time function to retrieve the previous power of 2 of the given number (120 => 64 => 6) - * - * @param v The input number - * @return constexpr uint8_t The next power of two - */ -constexpr uint8_t round_up_to_power_of_2(uint16_t v) -{ - if (__builtin_popcount(v) == 1) { return __builtin_ctz(v) - 1; } - - uint8_t next_pow_of_2{ 0 }; - - while (v) - { - v >>= 1; - ++next_pow_of_2; - } - - return --next_pow_of_2; -} - -/** - * @brief Exponentially Weighted Moving Average - * - * @details The smoothing factor is the approximate amount of values taken to calculate the average. - * Since the Arduino is very slow and does not provide any dedicated math co-processor, - * the smoothing factor will be rounded to the previous power of 2. Ie 120 will be rounded to 64. - * This allows to perform all the calculations with integer math, which is much faster ! - * - * @note Because of the 'sign extension', the sign is copied into lower bits. - * - * @tparam A Smoothing factor - * @param input Input value - * @return long Output value - */ -template< uint8_t A = 10 > -class EWMA_average -{ -public: - void addValue(int32_t input) - { - w = w - x + input; - x = w >> round_up_to_power_of_2(A); - } - - auto getAverage() const - { - return x; - } - -private: - int32_t w{ 0 }; - int32_t x{ 0 }; -}; - /** * @brief Template class for implementing a sliding average * @@ -199,7 +144,7 @@ class movingAvg return _ar[idx]; } - constexpr uint8_t getSize() const + [[nodiscard]] constexpr uint8_t getSize() const { return DURATION_IN_MINUTES; } diff --git a/Mk2_3phase_RFdatalog_temp/platformio.ini b/Mk2_3phase_RFdatalog_temp/platformio.ini index b0b62b3b..a4bfec8f 100644 --- a/Mk2_3phase_RFdatalog_temp/platformio.ini +++ b/Mk2_3phase_RFdatalog_temp/platformio.ini @@ -25,9 +25,10 @@ build_unflags = extra_scripts = pre:inject_sketch_name.py check_tool = cppcheck, clangtidy check_flags = - cppcheck: --enable=all - clangtidy: --fix --checks=*,-llvmlibc-callee-namespace,-llvmlibc-implementation-in-namespace,-clang-diagnostic-c++17-extensions + cppcheck: --enable=all --std=c++17 --suppress=missingIncludeSystem + clangtidy: --fix --extra-arg=-std=c++17 --checks=*,-llvmlibc-callee-namespace,-llvmlibc-implementation-in-namespace,-clang-diagnostic-c++17-extensions,-llvm-header-guard check_skip_packages = yes +check_src_filters = +<*> monitor_filters = default ; Remove typical terminal control codes from input time ; Add timestamp with milliseconds for each new line diff --git a/Mk2_3phase_RFdatalog_temp/processing.cpp b/Mk2_3phase_RFdatalog_temp/processing.cpp index b211f37a..2b6f8cff 100644 --- a/Mk2_3phase_RFdatalog_temp/processing.cpp +++ b/Mk2_3phase_RFdatalog_temp/processing.cpp @@ -16,13 +16,6 @@ #include "processing.h" #include "utils_pins.h" -/*! - * @defgroup TimeCritical Time critical functions Group - * Functions used by the ISR - */ - -int32_t l_DCoffset_V[NO_OF_PHASES]; /**< <--- for LPF */ - // Define operating limits for the LP filters which identify DC offset in the voltage // sample streams. By limiting the output range, these filters always should start up // correctly. @@ -30,6 +23,8 @@ constexpr int32_t l_DCoffset_V_min{ (512L - 100L) * 256L }; /**< mid-point of AD constexpr int32_t l_DCoffset_V_max{ (512L + 100L) * 256L }; /**< mid-point of ADC plus a working margin */ constexpr int16_t i_DCoffset_I_nom{ 512L }; /**< nominal mid-point value of ADC @ x1 scale */ +int32_t l_DCoffset_V[NO_OF_PHASES]; /**< <--- for LPF */ + constexpr uint32_t WORKING_ZONE_IN_JOULES{ 3600UL }; /**< number of joule for 1Wh */ /**< main energy bucket for 3-phase use, with units of Joules * SUPPLY_FREQUENCY */ @@ -177,8 +172,7 @@ void initializeOptionalPins() if constexpr (RELAY_DIVERSION) { - pinMode(relayPin, OUTPUT); - delay(100); + relays.initializePins(); } if constexpr (WATCHDOG_PIN_PRESENT) @@ -188,6 +182,9 @@ void initializeOptionalPins() } } +#if !defined(__DOXYGEN__) +void updatePortsStates() __attribute__((optimize("-O3"))); +#endif /** * @brief update the control ports for each of the physical loads * @@ -238,18 +235,18 @@ void updatePortsStates() */ void updatePhysicalLoadStates() { - uint8_t i{ 0 }; - if constexpr (PRIORITY_ROTATION != RotationModes::OFF) { if (b_reOrderLoads) { const auto temp{ loadPrioritiesAndState[0] }; - for (i = 0; i < NO_OF_DUMPLOADS - 1; ++i) + uint8_t i{ NO_OF_DUMPLOADS - 1 }; + do { - loadPrioritiesAndState[i] = loadPrioritiesAndState[i + 1]; - } - loadPrioritiesAndState[i] = temp; + loadPrioritiesAndState[i] = loadPrioritiesAndState[i - 1]; + --i; + } while (i); + loadPrioritiesAndState[0] = temp; b_reOrderLoads = false; } @@ -268,11 +265,13 @@ void updatePhysicalLoadStates() } const bool bDiversionOff{ b_diversionOff }; - for (i = 0; i < NO_OF_DUMPLOADS; ++i) + uint8_t idx{ NO_OF_DUMPLOADS }; + do { - const auto iLoad{ loadPrioritiesAndState[i] & loadStateMask }; - physicalLoadState[iLoad] = !bDiversionOff && (b_overrideLoadOn[iLoad] || (loadPrioritiesAndState[i] & loadStateOnBit)) ? LoadStates::LOAD_ON : LoadStates::LOAD_OFF; - } + --idx; + const auto iLoad{ loadPrioritiesAndState[idx] & loadStateMask }; + physicalLoadState[iLoad] = !bDiversionOff && (b_overrideLoadOn[iLoad] || (loadPrioritiesAndState[idx] & loadStateOnBit)) ? LoadStates::LOAD_ON : LoadStates::LOAD_OFF; + } while (idx); } /** @@ -340,9 +339,7 @@ void confirmPolarity(const uint8_t phase) return; } - ++count[phase]; - - if (count[phase] > PERSISTENCE_FOR_POLARITY_CHANGE) + if (++count[phase] > PERSISTENCE_FOR_POLARITY_CHANGE) { count[phase] = 0; polarityConfirmed[phase] = polarityOfMostRecentSampleV[phase]; @@ -413,7 +410,7 @@ void processStartUp(const uint8_t phase) void proceedHighEnergyLevel() { bool bOK_toAddLoad{ true }; - auto tempLoad{ nextLogicalLoadToBeAdded() }; + const auto tempLoad{ nextLogicalLoadToBeAdded() }; if (tempLoad >= NO_OF_DUMPLOADS) { @@ -454,7 +451,7 @@ void proceedHighEnergyLevel() void proceedLowEnergyLevel() { bool bOK_toRemoveLoad{ true }; - auto tempLoad{ nextLogicalLoadToBeRemoved() }; + const auto tempLoad{ nextLogicalLoadToBeRemoved() }; if (tempLoad >= NO_OF_DUMPLOADS) { @@ -576,10 +573,13 @@ void processMinusHalfCycle(const uint8_t phase) } } +#if !defined(__DOXYGEN__) +uint8_t nextLogicalLoadToBeAdded() __attribute__((optimize("-O3"))); +#endif /** * @brief Retrieve the next load that could be added (be aware of the order) * - * @return The load number if successfull, NO_OF_DUMPLOADS in case of failure + * @return The load number if successful, NO_OF_DUMPLOADS in case of failure * * @ingroup TimeCritical */ @@ -596,6 +596,9 @@ uint8_t nextLogicalLoadToBeAdded() return (NO_OF_DUMPLOADS); } +#if !defined(__DOXYGEN__) +uint8_t nextLogicalLoadToBeRemoved() __attribute__((optimize("-O3"))); +#endif /** * @brief Retrieve the next load that could be removed (be aware of the reverse-order) * @@ -608,8 +611,7 @@ uint8_t nextLogicalLoadToBeRemoved() uint8_t index{ NO_OF_DUMPLOADS }; do { - --index; - if (loadPrioritiesAndState[index] & loadStateOnBit) + if (loadPrioritiesAndState[--index] & loadStateOnBit) { return (index); } @@ -619,7 +621,7 @@ uint8_t nextLogicalLoadToBeRemoved() } /** - * @brief Process the lastest contribution after each phase specific new cycle + * @brief Process the latest contribution after each phase specific new cycle * additional processing is performed after each main cycle based on phase 0. * * @param phase the phase number [0..NO_OF_PHASES[ @@ -643,6 +645,9 @@ void processLatestContribution(const uint8_t phase) // } +#if !defined(__DOXYGEN__) +void processDataLogging() __attribute__((optimize("-O3"))); +#endif /** * @brief Process with data logging. * @details At the end of each datalogging period, copies are made of the relevant variables @@ -729,10 +734,12 @@ void processPlusHalfCycle(const uint8_t phase) void processRawSamples(const uint8_t phase) { // The raw V and I samples are processed in "phase pairs" + const auto &lastPolarity{ polarityConfirmedOfLastSampleV[phase] }; + if (Polarities::POSITIVE == polarityConfirmed[phase]) { // the polarity of this sample is positive - if (Polarities::POSITIVE != polarityConfirmedOfLastSampleV[phase]) + if (Polarities::POSITIVE != lastPolarity) { // This is the start of a new +ve half cycle, for this phase, just after the zero-crossing point. if (beyondStartUpPeriod) @@ -756,13 +763,13 @@ void processRawSamples(const uint8_t phase) else { // the polarity of this sample is negative - if (Polarities::NEGATIVE != polarityConfirmedOfLastSampleV[phase]) + if (Polarities::NEGATIVE != lastPolarity) { // This is the start of a new -ve half cycle (just after the zero-crossing point) processMinusHalfCycle(phase); } } -} // end of processRawSamples() +} /** * @brief Process the current voltage raw sample for the specific phase diff --git a/Mk2_3phase_RFdatalog_temp/processing.h b/Mk2_3phase_RFdatalog_temp/processing.h index fc7d067d..0d428e37 100644 --- a/Mk2_3phase_RFdatalog_temp/processing.h +++ b/Mk2_3phase_RFdatalog_temp/processing.h @@ -62,6 +62,20 @@ void processRawSamples(uint8_t phase); void processVoltage(uint8_t phase); +#if defined(__DOXYGEN__) +inline void processStartUp(uint8_t phase); +inline void processStartNewCycle(); +inline void processPlusHalfCycle(uint8_t phase); +inline void processMinusHalfCycle(uint8_t phase); +inline void processVoltage(uint8_t phase); +inline void processPolarity(uint8_t phase, int16_t rawSample); +inline void confirmPolarity(uint8_t phase); +inline void proceedLowEnergyLevel(); +inline void proceedHighEnergyLevel(); +inline uint8_t nextLogicalLoadToBeAdded(); +inline uint8_t nextLogicalLoadToBeRemoved(); +inline void processLatestContribution(uint8_t phase); +#else inline void processStartUp(uint8_t phase) __attribute__((always_inline)); inline void processStartNewCycle() __attribute__((always_inline)); inline void processPlusHalfCycle(uint8_t phase) __attribute__((always_inline)); @@ -74,6 +88,7 @@ inline void proceedHighEnergyLevel() __attribute__((always_inline)); inline uint8_t nextLogicalLoadToBeAdded() __attribute__((always_inline)); inline uint8_t nextLogicalLoadToBeRemoved() __attribute__((always_inline)); inline void processLatestContribution(uint8_t phase) __attribute__((always_inline)); +#endif void processDataLogging(); diff --git a/Mk2_3phase_RFdatalog_temp/type_traits.hpp b/Mk2_3phase_RFdatalog_temp/type_traits.hpp index ccacb696..eb78cf69 100644 --- a/Mk2_3phase_RFdatalog_temp/type_traits.hpp +++ b/Mk2_3phase_RFdatalog_temp/type_traits.hpp @@ -10,7 +10,7 @@ */ // ArduinoJson - https://arduinojson.org -// Copyright © 2014-2023, Benoit BLANCHON +// Copyright © 2014-2024, Benoit BLANCHON // MIT License #pragma once diff --git a/Mk2_3phase_RFdatalog_temp/type_traits/integral_constant.hpp b/Mk2_3phase_RFdatalog_temp/type_traits/integral_constant.hpp index 632d3d97..bd49d2e0 100644 --- a/Mk2_3phase_RFdatalog_temp/type_traits/integral_constant.hpp +++ b/Mk2_3phase_RFdatalog_temp/type_traits/integral_constant.hpp @@ -7,8 +7,18 @@ template< typename T, T v > struct integral_constant { - static const T value = v; + static constexpr T value = v; + using value_type = T; + using type = integral_constant; // using injected-class-name + constexpr operator value_type() const noexcept + { + return value; + } + constexpr value_type operator()() const noexcept + { + return value; + } // since c++14 }; typedef integral_constant< bool, true > true_type; -typedef integral_constant< bool, false > false_type; \ No newline at end of file +typedef integral_constant< bool, false > false_type; diff --git a/Mk2_3phase_RFdatalog_temp/type_traits/is_convertible.hpp b/Mk2_3phase_RFdatalog_temp/type_traits/is_convertible.hpp index d50754b0..843039cc 100644 --- a/Mk2_3phase_RFdatalog_temp/type_traits/is_convertible.hpp +++ b/Mk2_3phase_RFdatalog_temp/type_traits/is_convertible.hpp @@ -25,10 +25,10 @@ struct is_convertible static int probe(To); static char probe(...); - static From& _from; + static From& from_; -public: - static const bool value = sizeof(probe(_from)) == sizeof(int); + public: + static const bool value = sizeof(probe(from_)) == sizeof(int); }; #ifdef _MSC_VER #pragma warning(pop) diff --git a/Mk2_3phase_RFdatalog_temp/types.h b/Mk2_3phase_RFdatalog_temp/types.h index 2787ac6b..48479946 100644 --- a/Mk2_3phase_RFdatalog_temp/types.h +++ b/Mk2_3phase_RFdatalog_temp/types.h @@ -88,4 +88,23 @@ template< typename _Tp > constexpr size_t size(const _Tp (& /*__array*/)[0]) noe return 0; } +template< class... Ts > +constexpr uint8_t ival(Ts... Vs) +{ + char vals[sizeof...(Vs)] = { Vs... }; + uint8_t result = 0; + for (uint8_t i = 0; i < sizeof...(Vs); i++) + { + result *= 10; + result += vals[i] - '0'; + } + return result; +} + +template< char... Vs > +constexpr integral_constant< uint8_t, ival(Vs...) > operator""_i() +{ + return {}; +} + #endif // _TYPES_H diff --git a/Mk2_3phase_RFdatalog_temp/utils.h b/Mk2_3phase_RFdatalog_temp/utils.h index a28d9608..552d8787 100644 --- a/Mk2_3phase_RFdatalog_temp/utils.h +++ b/Mk2_3phase_RFdatalog_temp/utils.h @@ -119,7 +119,7 @@ inline void printConfiguration() { DBUGLN(F("is present")); - relay_Output.printRelayConfiguration(); + relays.printConfiguration(); } else { @@ -264,7 +264,7 @@ inline void printForSerialText() if constexpr (RELAY_DIVERSION) { Serial.print(F("/")); - Serial.print(relay_Output.get_average()); + Serial.print(relays.get_average()); } for (phase = 0; phase < NO_OF_PHASES; ++phase) diff --git a/Mk2_3phase_RFdatalog_temp/utils_dualtariff.h b/Mk2_3phase_RFdatalog_temp/utils_dualtariff.h index 556e995b..5acef526 100644 --- a/Mk2_3phase_RFdatalog_temp/utils_dualtariff.h +++ b/Mk2_3phase_RFdatalog_temp/utils_dualtariff.h @@ -37,11 +37,11 @@ class pairForceLoad { } - constexpr int16_t getStartOffset() const + [[nodiscard]] constexpr int16_t getStartOffset() const { return iStartOffset; } - constexpr uint16_t getDuration() const + [[nodiscard]] constexpr uint16_t getDuration() const { return uiDuration; } diff --git a/Mk2_3phase_RFdatalog_temp/utils_pins.h b/Mk2_3phase_RFdatalog_temp/utils_pins.h index 06057480..40b5c2f9 100644 --- a/Mk2_3phase_RFdatalog_temp/utils_pins.h +++ b/Mk2_3phase_RFdatalog_temp/utils_pins.h @@ -14,6 +14,17 @@ #include +#if defined(__DOXYGEN__) +inline constexpr void togglePin(const uint8_t pin); + +inline constexpr void setPinON(const uint8_t pin); +inline void setPinsON(const uint16_t pins); + +inline constexpr void setPinOFF(const uint8_t pin); +inline void setPinsOFF(const uint16_t pins); + +inline bool getPinState(const uint8_t pin); +#else inline constexpr void togglePin(const uint8_t pin) __attribute__((always_inline)); inline constexpr void setPinON(const uint8_t pin) __attribute__((always_inline)); @@ -23,6 +34,7 @@ inline constexpr void setPinOFF(const uint8_t pin) __attribute__((always_inline) inline void setPinsOFF(const uint16_t pins) __attribute__((always_inline)); inline bool getPinState(const uint8_t pin) __attribute__((always_inline)); +#endif /** * @brief Set the specified bit to 1 diff --git a/Mk2_3phase_RFdatalog_temp/utils_relay.h b/Mk2_3phase_RFdatalog_temp/utils_relay.h index 636171cd..cc9e9955 100644 --- a/Mk2_3phase_RFdatalog_temp/utils_relay.h +++ b/Mk2_3phase_RFdatalog_temp/utils_relay.h @@ -13,49 +13,47 @@ #define _UTILS_RELAY_H #include "types.h" +#include "type_traits.hpp" #include "config_system.h" #include "movingAvg.h" +#include "ewma_avg.hpp" #include "utils_pins.h" /** * @brief Relay diversion config and engine - * @details By default, the sliding average is calculated over 1 minute. - * If the user wants to calculate over a longer period, - * decare the variable like this: - * relayOutput<2> relay_Output{ relayPin, 1000, 200, 1, 1 } * - * @tparam T Duration in minutes of the sliding average + * @ingroup RelayDiversion */ -template< uint8_t T = 1 > class relayOutput +class relayOutput { public: constexpr relayOutput() = delete; /** - * @brief Construct a new relay Config object + * @brief Construct a new relay Config object with default parameters * * @param _relay_pin Control pin for the relay */ explicit constexpr relayOutput(const uint8_t _relay_pin) - : relay_pin(_relay_pin) + : relay_pin{_relay_pin} { } /** - * @brief Construct a new relay Config object + * @brief Construct a new relay Config object with default/custom parameters * * @param _relay_pin Control pin for the relay * @param _surplusThreshold Surplus threshold to turn relay ON * @param _importThreshold Import threshold to turn relay OFF */ constexpr relayOutput(uint8_t _relay_pin, int16_t _surplusThreshold, int16_t _importThreshold) - : relay_pin(_relay_pin), surplusThreshold(-abs(_surplusThreshold)), importThreshold(abs(_importThreshold)) + : relay_pin{_relay_pin}, surplusThreshold{-abs(_surplusThreshold)}, importThreshold{abs(_importThreshold)} { } /** - * @brief Construct a new relay Config object + * @brief Construct a new relay Config object with custom parameters * * @param _relay_pin Control pin for the relay * @param _surplusThreshold Surplus threshold to turn relay ON @@ -64,7 +62,7 @@ template< uint8_t T = 1 > class relayOutput * @param _minOFF Minimum duration in minutes to leave relay OFF */ constexpr relayOutput(uint8_t _relay_pin, int16_t _surplusThreshold, int16_t _importThreshold, uint16_t _minON, uint16_t _minOFF) - : relay_pin(_relay_pin), surplusThreshold(-abs(_surplusThreshold)), importThreshold(abs(_importThreshold)), minON(_minON * 60), minOFF(_minOFF * 60) + : relay_pin{_relay_pin}, surplusThreshold{-abs(_surplusThreshold)}, importThreshold{abs(_importThreshold)}, minON{_minON * 60}, minOFF{_minOFF * 60} { } @@ -118,6 +116,16 @@ template< uint8_t T = 1 > class relayOutput return minOFF; } + /** + * @brief Return the state + * + * @return auto + */ + auto isRelayON() const + { + return relayIsON; + } + /** * @brief Increment the duration of the current state * @details This function must be called every second. @@ -134,39 +142,30 @@ template< uint8_t T = 1 > class relayOutput /** * @brief Proceed with the relay * + * @return bool True if state has changed */ - void proceed_relay() const + bool proceed_relay(const int32_t currentAvgPower) const { - const auto currentAvgPower{ sliding_Average.getAverage() }; - // To avoid changing sign, surplus is a negative value if (currentAvgPower < surplusThreshold) { - try_turnON(); + return try_turnON(); } - else if (currentAvgPower > importThreshold) + if (currentAvgPower > importThreshold) { - try_turnOFF(); + return try_turnOFF(); } - } - - inline static auto get_average() - { - return sliding_Average.getAverage(); - } - - inline static void update_average(int16_t currentPower) - { - sliding_Average.addValue(currentPower); + return false; } /** * @brief Print the configuration of the current relay-diversion * */ - void printRelayConfiguration() const + void printRelayConfiguration(uint8_t idx) const { - Serial.println(F("\tRelay configuration:")); + Serial.print(F("\tRelay configuration: #")); + Serial.println(idx + 1); Serial.print(F("\t\tPin is ")); Serial.println(get_pin()); @@ -188,12 +187,13 @@ template< uint8_t T = 1 > class relayOutput /** * @brief Turn ON the relay if the 'time' condition is met * + * @return bool True if state has changed */ - void try_turnON() const + bool try_turnON() const { if (relayIsON || duration < minOFF) { - return; + return false; } setPinON(relay_pin); @@ -202,17 +202,20 @@ template< uint8_t T = 1 > class relayOutput relayIsON = true; duration = 0; + + return true; } /** * @brief Turn OFF the relay if the 'time' condition is met * + * @return bool True if state has changed */ - void try_turnOFF() const + bool try_turnOFF() const { if (!relayIsON || duration < minON) { - return; + return false; } setPinOFF(relay_pin); @@ -221,6 +224,8 @@ template< uint8_t T = 1 > class relayOutput relayIsON = false; duration = 0; + + return true; } private: @@ -232,8 +237,179 @@ template< uint8_t T = 1 > class relayOutput mutable uint16_t duration{ 0 }; /**< Duration of the current state */ mutable bool relayIsON{ false }; /**< True if the relay is ON */ +}; + +/** + * @brief This class implements the relay management engine + * + * @tparam D The duration in minutes of the sliding average + * @tparam N The number of relays to be used. This parameter is deduced automatically. + * + * @ingroup RelayDiversion + */ +template< uint8_t N, uint8_t D = 10 > +class RelayEngine +{ +public: + /** + * @brief Construct a list of relays + * + */ + explicit constexpr RelayEngine(const relayOutput (&ref)[N]) + : relay(ref) + { + } + + /** + * @brief Construct a list of relays with a custom sliding average + * + */ + constexpr RelayEngine(integral_constant< uint8_t, D > ic, const relayOutput (&ref)[N]) + : relay(ref) + { + } - static inline movingAvg< int16_t, T, 60 / DATALOG_PERIOD_IN_SECONDS > sliding_Average; + /** + * @brief Get the number of relays + * + * @return constexpr auto The number of relays + */ + constexpr auto get_size() const + { + return N; + } + + /** + * @brief Get the relay object + * + * @tparam idx The index of the relay + * @return constexpr const auto& The relay object + */ + constexpr const auto& get_relay(uint8_t idx) const + { + return relay[idx]; + } + + /** + * @brief Get the current average + * + * @return auto The current average + */ + inline static auto get_average() + { + return ewma_average.getAverageS(); + } + + /** + * @brief Update the sliding average + * + * @param currentPower Current power at the grid + */ + inline static void update_average(int16_t currentPower) + { + ewma_average.addValue(currentPower); + } + +/** + * @brief Increment the duration's state of each relay + * + */ +#if defined(__DOXYGEN__) + void inc_duration() const; +#else + void inc_duration() const __attribute__((optimize("-O3"))); +#endif + + /** + * @brief Proceed all relays in increasing order (surplus) or decreasing order (import) + * + */ + void proceed_relays() const + { + if (settle_change != 0) + { + // A relay has been toggle less than a minute ago, wait until changes take effect + return; + } + + if (ewma_average.getAverageS() > 0) + { + // Currently importing, try to turn OFF some relays + uint8_t idx{ N }; + do + { + if (relay[--idx].proceed_relay(ewma_average.getAverageS())) + { + settle_change = 60; + return; + } + } while (idx); + } + else + { + // Remaining surplus, try to turn ON more relays + uint8_t idx{ 0 }; + do + { + if (relay[idx].proceed_relay(ewma_average.getAverageS())) + { + settle_change = 60; + return; + } + } while (++idx < N); + } + } + + /** + * @brief Initialize the pins used by the relays + * + */ + void initializePins() const + { + uint8_t idx{ N }; + do + { + pinMode(relay[--idx].get_pin(), OUTPUT); + delay(100); + } while (idx); + } + + /** + * @brief Print the configuration of each relay + * + */ + void printConfiguration() const + { + Serial.println(F("\t*** Relay(s) configuration ***")); + Serial.print(F("\t\tSliding average: ")); + Serial.println(D); + + for (uint8_t i = 0; i < N; ++i) + { + relay[i].printRelayConfiguration(i); + } + } + +private: + const relayOutput relay[N]; /**< Array of relays */ + + mutable uint8_t settle_change{ 60 }; /**< Delay in seconds until next change occurs */ + + static inline EWMA_average< D * 60 / DATALOG_PERIOD_IN_SECONDS > ewma_average; /**< EWMA average */ }; +template< uint8_t N, uint8_t D > void RelayEngine< N, D >::inc_duration() const +{ + uint8_t idx{ N }; + do + { + relay[--idx].inc_duration(); + } while (idx); + + if (settle_change) + { + --settle_change; + } +} + #endif // _UTILS_RELAY_H \ No newline at end of file diff --git a/Mk2_3phase_RFdatalog_temp/utils_temp.h b/Mk2_3phase_RFdatalog_temp/utils_temp.h index f096b92e..9a57a4c3 100644 --- a/Mk2_3phase_RFdatalog_temp/utils_temp.h +++ b/Mk2_3phase_RFdatalog_temp/utils_temp.h @@ -23,22 +23,33 @@ inline constexpr bool TEMP_SENSOR_PRESENT{ true }; /**< set it to 'true' if temp inline constexpr bool TEMP_SENSOR_PRESENT{ false }; /**< set it to 'true' if temperature sensing is needed */ #endif +/** + * @struct DeviceAddress + * @brief Structure representing the address of a device. + * + * This structure is used to store the unique address of a device, such as a DS18B20 temperature sensor. + * The address is an array of 8 bytes, typically represented in hexadecimal. + */ +struct DeviceAddress +{ + uint8_t addr[8]; /**< The address of the device as an array of 8 bytes. */ +}; + /** * @brief This class implements the temperature sensing feature * * @tparam N Number of sensors, automatically deduced + * + * @ingroup TemperatureSensing */ template< uint8_t N > class TemperatureSensing { using ScratchPad = uint8_t[9]; - struct DeviceAddress - { - uint8_t addr[8]; - }; - public: + constexpr TemperatureSensing() = delete; + /** * @brief Construct a new Temperature Sensing object * @@ -98,7 +109,7 @@ class TemperatureSensing /** * @brief Read temperature of a specific device * - * @param deviceAddress The address of the device + * @param idx The index of the device * @return int16_t Temperature * 100 */ int16_t readTemperature(const uint8_t idx) diff --git a/Mk2_3phase_RFdatalog_temp/validation.h b/Mk2_3phase_RFdatalog_temp/validation.h index 0a476ee1..9329531d 100644 --- a/Mk2_3phase_RFdatalog_temp/validation.h +++ b/Mk2_3phase_RFdatalog_temp/validation.h @@ -34,8 +34,6 @@ static_assert((PRIORITY_ROTATION == RotationModes::PIN) ^ (rotationPin == 0xff), static_assert(OVERRIDE_PIN_PRESENT ^ (forcePin == 0xff), "******** Wrong pin value for override command. Please check your config.h ! ********"); static_assert(WATCHDOG_PIN_PRESENT ^ (watchDogPin == 0xff), "******** Wrong pin value for watchdog. Please check your config.h ! ********"); -static_assert(RELAY_DIVERSION ^ (relayPin == 0xff), "******** Wrong pin value for relay diversion. Please check your config.h ! ********"); - static_assert(DUAL_TARIFF ^ (dualTariffPin == 0xff), "******** Wrong pin value for dual tariff. Please check your config.h ! ********"); static_assert(!DUAL_TARIFF | (ul_OFF_PEAK_DURATION == 0), "******** Off-peak duration cannot be zero. Please check your config.h ! ********"); static_assert(!(DUAL_TARIFF & (ul_OFF_PEAK_DURATION > 12)), "******** Off-peak duration cannot last more than 12 hours. Please check your config.h ! ********"); @@ -87,7 +85,7 @@ check_pins() bit_set(used_pins, watchDogPin); } - //physicalLoadPin + //physicalLoadPin for the TRIACS for (const auto &loadPin : physicalLoadPin) { if (loadPin == 0xff) @@ -99,9 +97,46 @@ check_pins() bit_set(used_pins, loadPin); } + if constexpr (RELAY_DIVERSION) + { + for (uint8_t idx = 0; idx < relays.get_size(); ++idx) + { + const auto relayPin = relays.get_relay(idx).get_pin(); + + if (relayPin != 0xff) + { + if (bitRead(used_pins, relayPin)) + return 0; + + bit_set(used_pins, relayPin); + } + } + } + return used_pins; } +constexpr uint16_t check_relay_pins() +{ + bool pins_ok{ true }; + + for (uint8_t idx = 0; idx < relays.get_size(); ++idx) + { + const auto relayPin = relays.get_relay(idx).get_pin(); + + if constexpr (RELAY_DIVERSION) + { + pins_ok &= (relayPin != 0xff); + } + else + { + pins_ok &= (relayPin == 0xff); + } + } + + return pins_ok; +} + constexpr bool check_load_priorities() { uint8_t _sum{ 0 }; @@ -127,5 +162,6 @@ static_assert(check_pins(), "******** Duplicate pin definition ! Please check yo static_assert((check_pins() & B00000011) == 0, "******** Pins 0 & 1 are reserved for RX/TX ! Please check your config ! ********"); static_assert((check_pins() & 0xC000) == 0, "******** Pins 14 and/or 15 do not exist ! Please check your config ! ********"); static_assert(!(RF_CHIP_PRESENT && ((check_pins() & 0x3C04) != 0)), "******** Pins from RF chip are reserved ! Please check your config ! ********"); +static_assert(check_relay_pins(), "******** Wrong pin(s) configuration for relay(s) ********"); #endif diff --git a/PVRouter-3-phase.code-workspace b/PVRouter-3-phase.code-workspace index 7a76bec6..2329b137 100644 --- a/PVRouter-3-phase.code-workspace +++ b/PVRouter-3-phase.code-workspace @@ -19,10 +19,15 @@ "name": "6-channel raw sampler", "path": "dev/RawSamplesTool_6chan" }, + { + "name": "MathPerfTests", + "path": "dev/MathPerfTests" + } ], "settings": { "files.associations": { - "xtr1common": "cpp" + "xtr1common": "cpp", + "new": "cpp" } } } \ No newline at end of file diff --git a/Readme.en.md b/Readme.en.md new file mode 100644 index 00000000..5bdde103 --- /dev/null +++ b/Readme.en.md @@ -0,0 +1,265 @@ +
+ +[![GitHub issues](https://img.shields.io/github/issues/FredM67/PVRouter-3-phase)](https://github.com/FredM67/PVRouter-3-phase/issues) +[![GitHub forks](https://img.shields.io/github/forks/FredM67/PVRouter-3-phase)](https://github.com/FredM67/PVRouter-3-phase/network) +[![GitHub stars](https://img.shields.io/github/stars/FredM67/PVRouter-3-phase)](https://github.com/FredM67/PVRouter-3-phase/stargazers) +[![CodeQL](https://github.com/FredM67/PVRouter-3-phase/actions/workflows/codeql.yml/badge.svg)](https://github.com/FredM67/PVRouter-3-phase/actions/workflows/codeql.yml) +[![Doxygen](https://github.com/FredM67/PVRouter-3-phase/actions/workflows/doxygen-gh-pages.yml/badge.svg)](https://github.com/FredM67/PVRouter-3-phase/actions/workflows/doxygen-gh-pages.yml) +
+[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) +
+
+ [![en](https://img.shields.io/badge/lang-en-red.svg)](Readme.en.md) + [![fr](https://img.shields.io/badge/lang-fr-blue.svg)](Readme.md) +
+ +# PVRouter (3-phase version) + +My version of the 3-phase Mk2PVRouter firmware (see ). + +Robin Emley already proposes a 3 phase PV-router (). +It supports up to 12 resistive output loads, which are completely independent. + +--- +**_NOTE:_** For a single phase version, please see [PVRouter-Single](https://github.com/FredM67/PVRouter-Single). + +--- + +- [PVRouter (3-phase version)](#pvrouter-3-phase-version) + - [Photo Gallery](#photo-gallery) + - [Schematic of the mainboard](#schematic-of-the-mainboard) + - [Implementation documentation](#implementation-documentation) + - [End-user documentation](#end-user-documentation) + - [Overview](#overview) + - [Load priorities management](#load-priorities-management) + - [Off-peak period detection](#off-peak-period-detection) + - [Force full power](#force-full-power) + - [Temperature sensor](#temperature-sensor) + - [Enphase zero-export profile](#enphase-zero-export-profile) +- [How to wire the router](#how-to-wire-the-router) +- [Use cases](#use-cases) + - [Requirements](#requirements) + - [Heater with mechanical thermostat](#heater-with-mechanical-thermostat) + - [Migrate from single-phase to 3-phase (with neutral wire)](#migrate-from-single-phase-to-3-phase-with-neutral-wire) + - [Wiring](#wiring) + - [Heater with ACI single phase thermostat](#heater-with-aci-single-phase-thermostat) + - [Heater with ACI 3-phase thermostat (without neutral wire)](#heater-with-aci-3-phase-thermostat-without-neutral-wire) + - [Alternatives WITHOUT neutral wire](#alternatives-without-neutral-wire) + - [Heater with mechanical thermostat](#heater-with-mechanical-thermostat-1) + - [Support](#support) + - [Roadmap](#roadmap) + - [Contributing](#contributing) + - [Authors and acknowledgment](#authors-and-acknowledgment) + +## Photo Gallery + +[Here](Gallery.md) a couple of pictures of assembled routers. + +## Schematic of the mainboard + +[Here](../../schematics/3phase_Mainboard.pdf) the schematic of the mainboard. + +## Implementation documentation + +You can start reading the documentation here [3-phase diverter](https://fredm67.github.io/PVRouter-3-phase/html/index.html). + +## End-user documentation + +### Overview + +Goal was to modify/optimize the sketch for the "special" case of a 3-phase water heater. A 3-phase water heater is composed in fact of 3 independent heating elements. Most of the time, such a heater can be connected in mono, or 3-phase WYE or 3-phase Delta. +When connected in WYE (without varistor), there's no need of a neutral wire because the system is equally distributed, so at any time, there's no current flowing to the neutral. + +If a diverter is used, the neutral wire must be connected. + +Added functionalities: + +- load priorities management (configurable) +- off-peak period detection (configurable) +- force full power +- temperature sensor (just reading for the moment) +- optimized (RF) data logging +- serial output in JSON or TXT + +The original sketch had to be completely re-worked and re-structured to support temperature reading. In the original sketch, the ISR "just" reads and converts the analog data, and the processing is done in the loop. This won't work with a temperature sensor because of its slow performance. It would break the whole system, current/voltage data will be lost, ... + +Now, all the time-critical processing is done inside the ISR, other stuff like (RF) data logging, Serial printing, temperature reading is made inside the loop(). The ISR and main processor communicate with each other through "events". + +### Load priorities management + +In my variant of Robin's sketch, the 3 loads are still physically independent, so it means, the router will divert surplus of energy to the first load (highest priority) from 0% to 100%, then to the second (0% to 100%) and finally to the third. + +To avoid that the priorities stay all the time unchanged, which would mean that load 1 will run much more than load 2, which again will run much more than 3, I've added a priority management. +Each day, the load priorities are rotated, so over many days, all the heating elements will run somehow the same amount of time. + +### Off-peak period detection + +Depending on the country, some energy meters provide a switch/relay which toggles on at the beginning of the off-peak period. It is intended to control a relay. If you wire it to a free digital pin of the router (in my case D3), you can detect off-peak/peak period. + +### Force full power + +Support has been added to force full power on specific loads. Each load can be forced independently from each other, start time and duration can be set individually. + +In my variant, that's used to switch the heater one during off-peak period if not enough surplus has been routed during the day. Here, to optimize the behavior, a temp-sensor will be used to check the temperature of the water and decide to switch on or not during night. + +### Temperature sensor + +For the moment, just reading. It'll be used to optimize force full power, to make the right decision during night. + +### Enphase zero-export profile + +When zero-export settings is enabled, the PV system curtails power production if the production of the system exceeds the consumption needs of the site. This ensures zero feed into the grid. + +As a side effect, the diverter won't see at any time surplus of energy. +So the idea is to apply a certain offset to the energy measured by the diverter. +As it is already commented in the code, after setting a negative value to *REQUIRED_EXPORT_IN_WATTS*, the diverter will act as a PV generator. +If you set a value of -20, each time the diverter measures the energy flowing, it'll add *-20* to the measurements. + +So, now let see what happen in a couple of cases: + +- measured value is **positive** (energy import = no surplus), after adding *-20*, it stays positive, the diverter doesn't do anything. By a value between -20 and 0, the diverter won't do anything either. +- measured value is **around zero**. In this situation, the "zero export profile" limitation is active. +After adding *-20*, we get a negative value that will make the diverter start diverting energy to the water heater. +Now, there's a sort of chain reaction. The Envoy detects more consumption, decides to raise production. +On the next measurement, the diverter measures again a value around zero, add again *-20*, and diverts even more energy. +When production (and surplus) gets to the maximum possible, the measured value will stay around zero+ and the system is stable. + +This has been tested in real by Amorim. Depending of each situation, it might be necessary to tweak this value of *-20* to a bigger or smaller value. + +# How to wire the router +[Here](../../docs/HowToInstall.pdf) you'll find a quick how-to for installing/wiring your router. + +# Use cases + +I want to: + +- change my (mechanical) single-phase water heater to 3-phase, see [Heater with mechanical thermostat](#heater-with-mechanical-thermostat) +- connect my (mechanical) 3-phase water heater, see [Heater with mechanical thermostat](#heater-with-mechanical-thermostat) +- change my ACI single-phase water heater to 3-phase w/o buying a 3-phase kit, see [Heater with ACI single phase thermostat](#heater-with-aci-single-phase-thermostat) +- connect my ACI 3-phase water heater, see [Heater with ACI 3-phase thermostat (without neutral wire)](#heater-with-aci-3-phase-thermostat-without-neutral-wire) +- connect multiple pure resistive charges, simply wire them, one on each output, and do not forget to disable [Load priorities management](#load-priorities-management). + +## Requirements + +To change your single-phase water heater to 3-phase, it MUST support 3-phase wiring (i.e. it must have 3 heating elements). + +--- +**_Safety Warning_** + +To modify the existing wiring, access to 240V mains voltage is required. +Please take great care, and do not undertake this stage unless you feel confident to do so. + +--- + +## Heater with mechanical thermostat + +#### Migrate from single-phase to 3-phase (with neutral wire) + +--- +**_A router with 3 outputs is needed_** + +With this solution, you'll control each heating element separately. + +--- + +You'll have to separate all 3 heating elements, and probably add a new wire for each of them. Sometime, the elements are connected together with a sort of metallic "star". There's one for the (single) phase, and one for the neutral wire. You only need to remove one of them, the one for neutral must stay wired. + +#### Wiring + +Since on all (3-phase) water heaters I've seen, the thermostat switches only 2 phases in normal mode (all 3 phases in security mode), it must be wired in another way to achieve a full switch on all 3 phases. In a fully balanced 3-phase situation, you don't need any neutral wire. To switch off the device, you only need to switch off 2 phases. + +--- +**_Note_** + +In a balanced situation, you don't need any neutral wire. To switch off the device, you just need to switch off 2 phases out of 3. That's why most thermostats are build like this. + +--- + +For that, I've "recycled" a peak/off peak 3-phase relay but you can use any 3-phase relay. It doesn't matter on which phase the command coil is connected, but it must be permanent (not through the router). + +![Heater with mechanical thermostat](img/Heater_mechanical.png) +*Figure: Wiring diagram* + +## Heater with ACI single phase thermostat + +In this case, it's somehow the same situation as before. +You don't need to buy a 3-phase kit to convert your single phase heater. +The ACI pcb must be connected to a permanent phase. It will then control any 3-phase relay. + +![Heater with ACI single phase thermostat](img/Heater_ACI_Mono.png) +*Figure: Wiring diagram* + +## Heater with ACI 3-phase thermostat (without neutral wire) + +--- +**_A router with 2 outputs is needed_** + +With this solution, you'll control each heating element separately. + +--- + +The ACI board does not cut all 3 phases when the temperature is reached. Only 2 phases are disconnected. + +The remaining connected phase is the one in the middle of the power connector. +***It is very IMPORTANT that this phase, which remains permanent, does not pass through a triac***. + +The ACI pcb must be connected to 3 permanent phases. + +![Heater with ACI 3-phase thermostat](img/Heater_ACI_Tri.png) +*Figure: Wiring diagram* + +## Alternatives WITHOUT neutral wire + +--- +**_A router with 2 outputs is needed_** + +With this solution, you won't need to add an additional neutral wire nor add a relay. + +--- + +### Heater with mechanical thermostat + +This configuration allows to simplify the wiring and specially does not require any 3-4 poles relay. + +--- +**_Zoom on the thermostat_** + +You need to take care of which wires are switched off. + +In **red**, security switch (see the 'S' on each pole) : all 3 phases are switched off. + +In **green**, only 2 phases are switched off, L2 et L3. ***It is IMPORTANT that the phase L1, not switched by the thermostat, DOES NOT pass through the triac***. + +![Mechanical thermostat](img/Thermostat.png) +*Figure: An example of a thermostat* + +--- + +![Heater with mechanical thermostat](img/Heater_mechanical-No_neutral.png) +*Figure: Wiring diagram* + +## Support + +This project is maintained by [@FredM67](https://github.com/FredM67). Please understand that we won't be able to provide individual support via email. We also believe that help is much more valuable if it's shared publicly, so that more people can benefit from it. + +| Type | Platforms | +| -------------------------------------- | ----------------------------------------------------------------------------- | +| ?? **Bug Reports** | [GitHub Issue Tracker](https://github.com/FredM67/PVRouter-3-phase/issues) | +| ?? **Docs Issue** | [GitHub Issue Tracker](https://github.com/FredM67/PVRouter-3-phase/issues) | +| ?? **Feature Requests** | [GitHub Issue Tracker](https://github.com/FredM67/PVRouter-3-phase/issues) | +| ?? **Report a security vulnerability** | See [SECURITY.md](SECURITY.md) | +| ?? **General Questions** | [GitHub Discussions](https://github.com/FredM67/PVRouter-3-phase/discussions) | + +## Roadmap + +No changes are currently planned. + +## Contributing + +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. + +## Authors and acknowledgment + +- **Fr�d�ric Metrich** - _Initial work_ - [FredM67](https://github.com/FredM67) + +See also the list of [contributors](https://github.com/FredM67/PVRouter-3-phase/graphs/contributors) who participated in this project. diff --git a/Readme.md b/Readme.md index ea390108..c565a2fe 100644 --- a/Readme.md +++ b/Readme.md @@ -9,16 +9,16 @@ [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)

- [![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/FredM67/PVRouter-3-phase/blob/main/Readme.md) - [![fr](https://img.shields.io/badge/lang-fr-blue.svg)](https://github.com/FredM67/PVRouter-3-phase/blob/main/Readme.fr.md) + [![en](https://img.shields.io/badge/lang-en-red.svg)](Readme.en.md) + [![fr](https://img.shields.io/badge/lang-fr-blue.svg)](Readme.md) -# PVRouter (3-phase version) +# PVRouter (version triphasée) My version of the 3-phase Mk2PVRouter firmware (see ). -Robin Emley already proposes a 3 phase PV-router (). -It supports up to 12 resistive output loads, which are completely independent. +Robin Emley propose déjà un routeur PV triphasé (https://www.mk2pvrouter.co.uk/3-phase-version.html). +Il prend en charge jusqu'à 12 sorties pour charges résistives, qui sont complètement indépendantes. --- **_NOTE:_** For a single phase version, please see [PVRouter-Single](https://github.com/FredM67/PVRouter-Single). @@ -59,20 +59,17 @@ It supports up to 12 resistive output loads, which are completely independent. [Here](../../schematics/3phase_Mainboard.pdf) the schematic of the mainboard. -## Implementation documentation +## Documentation de développement -You can start reading the documentation here [3-phase diverter](https://fredm67.github.io/PVRouter-3-phase/html/index.html). +Vous pouvez commencer à lire la documentation ici [3-phase routeur](https://fredm67.github.io/PVRouter-3-phase/) (en anglais). -## End-user documentation +## Documentation de l’utilisateur final -### Overview +### Aperçu -Goal was to modify/optimize the sketch for the "special" case of a 3-phase water heater. A 3-phase water heater is composed in fact of 3 independent heating elements. Most of the time, such a heater can be connected in mono, or 3-phase WYE or 3-phase Delta. -When connected in WYE (without varistor), there's no need of a neutral wire because the system is equally distributed, so at any time, there's no current flowing to the neutral. +L’objectif était de modifier/optimiser le programme pour le cas « spécial » d’un chauffe-eau triphasé. Un chauffe-eau triphasé est composé en fait de 3 éléments de chauffage indépendants. La plupart du temps, un tel chauffe-eau peut être connecté en monophasé, en triphasé étoile (WYE) ou triphasé triangle (Delta). Lorsqu’il est connecté en étoile, il n’y a pas besoin de fil de neutre parce que le système est équilibré, donc à tout moment, il n’y a pas de courant qui circule vers le neutre. -If a diverter is used, the neutral wire must be connected. - -Added functionalities: +Fonctionnalités ajoutées : - load priorities management (configurable) - off-peak period detection (configurable) @@ -102,27 +99,27 @@ Support has been added to force full power on specific loads. Each load can be f In my variant, that's used to switch the heater one during off-peak period if not enough surplus has been routed during the day. Here, to optimize the behavior, a temp-sensor will be used to check the temperature of the water and decide to switch on or not during night. -### Temperature sensor +### Capteur de température -For the moment, just reading. It'll be used to optimize force full power, to make the right decision during night. +Il peut être utilisé pour optimiser le fonctionnement de la marche forcée, pour prendre la bonne décision pendant la nuit. ### Enphase zero-export profile When zero-export settings is enabled, the PV system curtails power production if the production of the system exceeds the consumption needs of the site. This ensures zero feed into the grid. -As a side effect, the diverter won't see at any time surplus of energy. -So the idea is to apply a certain offset to the energy measured by the diverter. -As it is already commented in the code, after setting a negative value to *REQUIRED_EXPORT_IN_WATTS*, the diverter will act as a PV generator. -If you set a value of -20, each time the diverter measures the energy flowing, it'll add *-20* to the measurements. +Comme effet secondaire, le routeur ne verra pas à aucun moment un surplus d’énergie. +L’idée est donc d’appliquer un certain décalage à l’énergie mesurée par le routeur. +Comme il est déjà commenté dans le code, après l'assignation d’une valeur négative à *REQUIRED_EXPORT_IN_WATTS*, le routeur agira comme un générateur PV. +Si vous définissez une valeur de *-20*, chaque fois que le routeur mesure le flux d’énergie, il ajoutera *-20* aux mesures. So, now let see what happen in a couple of cases: -- measured value is **positive** (energy import = no surplus), after adding *-20*, it stays positive, the diverter doesn't do anything. By a value between -20 and 0, the diverter won't do anything either. -- measured value is **around zero**. In this situation, the "zero export profile" limitation is active. -After adding *-20*, we get a negative value that will make the diverter start diverting energy to the water heater. -Now, there's a sort of chain reaction. The Envoy detects more consumption, decides to raise production. -On the next measurement, the diverter measures again a value around zero, add again *-20*, and diverts even more energy. -When production (and surplus) gets to the maximum possible, the measured value will stay around zero+ and the system is stable. +- la valeur mesurée est **positive** (importation d’énergie = pas d’excédent), après avoir ajouté *-20*, cela reste positif, le routeur ne fait rien. Pour une valeur comprise entre -20 et 0, le déviateur ne fera rien non plus. +- la valeur mesurée est **autour de zéro**. Dans cette situation, la limitation du "profil zéro exportation" est active. +Après l’ajout de *-20*, nous obtenons une valeur négative, ce qui déclenchera le détournement d’énergie vers le chauffe-eau. +Ensuite, il y a une sorte de réaction en chaîne. L’Envoy détecte plus de consommation, décide d’augmenter la production. +À la mesure suivante, le routeur mesure à nouveau une valeur autour de zéro, ajoute à nouveau -20, et détourne encore plus d’énergie. +Lorsque la production (et l’excédent) arrive au maximum possible, la valeur mesurée restera autour de zéro+ et le système deviendra stable. This has been tested in real by Amorim. Depending of each situation, it might be necessary to tweak this value of *-20* to a bigger or smaller value. @@ -242,13 +239,13 @@ In **green**, only 2 phases are switched off, L2 et L3. ***It is IMPORTANT that This project is maintained by [@FredM67](https://github.com/FredM67). Please understand that we won't be able to provide individual support via email. We also believe that help is much more valuable if it's shared publicly, so that more people can benefit from it. -| Type | Platforms | -| -------------------------------------- | ----------------------------------------------------------------------------- | -| ?? **Bug Reports** | [GitHub Issue Tracker](https://github.com/FredM67/PVRouter-3-phase/issues) | -| ?? **Docs Issue** | [GitHub Issue Tracker](https://github.com/FredM67/PVRouter-3-phase/issues) | -| ?? **Feature Requests** | [GitHub Issue Tracker](https://github.com/FredM67/PVRouter-3-phase/issues) | -| ?? **Report a security vulnerability** | See [SECURITY.md](SECURITY.md) | -| ?? **General Questions** | [GitHub Discussions](https://github.com/FredM67/PVRouter-3-phase/discussions) | +| Type | Platforms | +| ------------------------------------- | ----------------------------------------------------------------------------- | +| 🚨 **Bug Reports** | [GitHub Issue Tracker](https://github.com/FredM67/PVRouter-3-phase/issues) | +| 📚 **Docs Issue** | [GitHub Issue Tracker](https://github.com/FredM67/PVRouter-3-phase/issues) | +| 🎁 **Feature Requests** | [GitHub Issue Tracker](https://github.com/FredM67/PVRouter-3-phase/issues) | +| 🛡 **Report a security vulnerability** | See [SECURITY.md](SECURITY.md) | +| 💬 **General Questions** | [GitHub Discussions](https://github.com/FredM67/PVRouter-3-phase/discussions) | ## Roadmap diff --git a/dev/MathPerfTests/.gitignore b/dev/MathPerfTests/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/dev/MathPerfTests/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/dev/MathPerfTests/.vscode/extensions.json b/dev/MathPerfTests/.vscode/extensions.json new file mode 100644 index 00000000..080e70d0 --- /dev/null +++ b/dev/MathPerfTests/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/dev/MathPerfTests/MathPerfTests.ino b/dev/MathPerfTests/MathPerfTests.ino index 87abab3f..1c1a40c8 100644 --- a/dev/MathPerfTests/MathPerfTests.ino +++ b/dev/MathPerfTests/MathPerfTests.ino @@ -3,8 +3,8 @@ #include "movingAvg.h" -const int nb_of_interation_per_pass = 500; -const int nb_of_pass = 10; +const int nb_of_interation_per_pass = 30000; +const int nb_of_pass = 100; unsigned long initial_time = 0; unsigned long final_time = 0; @@ -13,7 +13,6 @@ float duration_dummy_loop = 0; float duration = 0; float duration_sum = 0; - // All variables used for calculations are declared globally and volatile to minimize // any possible compiler optimisation when performing the same operation multiple times. @@ -23,7 +22,7 @@ volatile float float_1 = 0; volatile float float_2 = 0; volatile float float_3 = 0; -long long_1 = 0; +volatile long long_1 = 0; volatile long long_2 = 0; volatile long long_3 = 0; @@ -55,31 +54,104 @@ constexpr uint8_t round_up_to_power_of_2(uint16_t v) { template< uint8_t A = 10 > class EWMA_average { public: - void addValue(int32_t input) { - w = w - x + input; - x = w >> round_up_to_power_of_2(A); + void addValue(int32_t input) __attribute__((optimize("-Os"))); + + auto getAverageS() const { + return ema; } - auto getAverage() const { - return x; + auto getAverageD() const { + return (ema << 1) - ema_ema; + } + + auto getAverageT() const { + return 3 * (ema - ema_ema) + ema_ema_ema; } private: - int32_t w{ 0 }; - int32_t x{ 0 }; + int32_t ema_ema_ema_raw{ 0 }; + int32_t ema_ema_ema{ 0 }; + int32_t ema_ema_raw{ 0 }; + int32_t ema_ema{ 0 }; + int32_t ema_raw{ 0 }; + int32_t ema{ 0 }; }; +template< uint8_t A> void EWMA_average::addValue(int32_t input) { + ema_raw = ema_raw - ema + input; + ema = ema_raw >> (round_up_to_power_of_2(A)); + + ema_ema_raw = ema_ema_raw - ema_ema + ema; + ema_ema = ema_ema_raw >> (round_up_to_power_of_2(A) - 1); + + ema_ema_ema_raw = ema_ema_ema_raw - ema_ema_ema + ema_ema; + ema_ema_ema = ema_ema_ema_raw >> (round_up_to_power_of_2(A) - 2); +} + movingAvg< int32_t, 10, 12 > sliding_Average; -EWMA_average<120> ewma_average; +EWMA_average< 120 > ewma_average; +constexpr uint8_t alpha{ round_up_to_power_of_2(120) }; void setup() { - Serial.begin(115200); Serial.println("Setup ***"); } -void loop() { +void pause() { + byte done = false; + byte dummyByte; + while (done != true) { + if (Serial.available() > 0) { + dummyByte = Serial.read(); // to 'consume' the incoming byte + if (dummyByte == 'g') done++; + } + } +} + +// void loop() { +// int16_t input_data; + +// for (int i = 0; i < nb_of_interation_per_pass; i++) { +// if (i < 5) +// input_data = 0; +// else if (i < (nb_of_interation_per_pass >> 1)) +// input_data = -5000 + random(-100, 100); +// else +// input_data = 0 + random(-20, 20); + +// const auto temp{ nb_of_interation_per_pass >> 2 }; +// if (i > (temp - 2) && i < (temp + 2)) +// input_data = 2000; + +// if (i > (3 * temp - 15) && i < (3 * temp + 25)) +// input_data = 200; + +// ewma_average.addValue(input_data); +// sliding_Average.addValue(input_data); + +// Serial.print("Input:"); +// Serial.print(input_data); +// Serial.print(","); +// Serial.print("AVG:"); +// Serial.print(sliding_Average.getAverage()); +// Serial.print(","); +// Serial.print("EMA:"); +// Serial.print(ewma_average.getAverageS()); +// Serial.print(","); +// Serial.print("DEMA:"); +// Serial.println(ewma_average.getAverageD()); +// Serial.print(","); +// Serial.print("TEMA:"); +// Serial.println(ewma_average.getAverageT()); + +// // **************** PUT YOUR COMMAND TO TEST HERE ******************** +// } + +// pause(); +// } + +void loop() { for (int j = 0; j < nb_of_pass; j++) { // STEP 1: We first calculate the time taken to run a dummy FOR loop to measure the overhead cause by the execution of the loop. @@ -93,19 +165,17 @@ void loop() { // STEP 2 (optional): Pick some relevant random numbers to test the command under random conditions. Make sure to pick numbers appropriate for your command (e.g. no negative number for the command "sqrt()") randomSeed(micros() * analogRead(0)); - long_1 = random(-36000, 36000); + byte_1 = random(0, 256); + byte_2 = random(1, 256); // STEP 3: Calculation of the time taken to run the dummy FOR loop and the command to test. initial_time = micros(); for (int i = 0; i < nb_of_interation_per_pass; i++) { dummy++; // The dummy instruction is also performed here so that we can remove the effect of the dummy FOR loop accurately. - // **************** PUT YOUR COMMAND TO TEST HERE ******************** - long_1 = sin(i) * 1000; - - sliding_Average.addValue(long_1); - ewma_average.addValue(long_1); - // **************** PUT YOUR COMMAND TO TEST HERE ******************** + ewma_average.addValue(i); // Target command example + long_2 = ewma_average.getAverageD(); + // **************** PUT YOUR COMMAND TO TEST HERE ******************** } final_time = micros(); @@ -114,15 +184,9 @@ void loop() { duration_sum += duration; dummy = 0; - Serial.print(sliding_Average.getAverage()); - Serial.print(" - "); - Serial.print(ewma_average.getAverage()); - Serial.print(" - "); Serial.print(j); Serial.print(". "); print_result(duration); - - sliding_Average.clear(); } Serial.println(); @@ -130,10 +194,6 @@ void loop() { print_result(duration_sum / nb_of_pass); Serial.println("****************************************** "); Serial.println(); - //for (uint8_t idx = 0; idx < sliding_Average.getSize(); ++idx) { - // Serial.println(sliding_Average.getElement(idx)); - //} - //Serial.println(); duration_sum = 0; delay(2000); } diff --git a/dev/MathPerfTests/movingAvg.h b/dev/MathPerfTests/movingAvg.h index 304e5e9d..d18f6f16 100644 --- a/dev/MathPerfTests/movingAvg.h +++ b/dev/MathPerfTests/movingAvg.h @@ -195,8 +195,8 @@ class movingAvg T _sub_ar[VALUES_PER_MINUTE]{}; T _ar[DURATION_IN_MINUTES]{}; - static constexpr float invD{ 1.0F / VALUES_PER_MINUTE }; - static constexpr float invN{ 1.0F / DURATION_IN_MINUTES }; + static constexpr float invD{ 1.0 / VALUES_PER_MINUTE }; + static constexpr float invN{ 1.0 / DURATION_IN_MINUTES }; }; #endif diff --git a/dev/MathPerfTests/platformio.ini b/dev/MathPerfTests/platformio.ini new file mode 100644 index 00000000..383a1bf6 --- /dev/null +++ b/dev/MathPerfTests/platformio.ini @@ -0,0 +1,24 @@ +; 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] +src_dir = ./ + +[env:uno] +platform = atmelavr +board = uno +framework = arduino +build_flags = + -std=c++1z + -std=gnu++1z +build_unflags = + -std=c++11 + -std=gnu++11 +monitor_speed = 115200 diff --git a/dev/RawSamplesTool_6chan/RawSamplesTool_6chan.ino b/dev/RawSamplesTool_6chan/RawSamplesTool_6chan.ino index b5b5a249..cfc8f56a 100644 --- a/dev/RawSamplesTool_6chan/RawSamplesTool_6chan.ino +++ b/dev/RawSamplesTool_6chan/RawSamplesTool_6chan.ino @@ -9,7 +9,7 @@ * Robin Emley (calypso_rae on Open Energy Monitor Forum) * December 2012 */ - + #define POSITIVE 1 #define NEGATIVE 0 #define ON 0 // the external trigger device is active low @@ -33,16 +33,16 @@ byte sensorPin_I3 = 5; long cycleCount = 0; int samplesRecorded = 0; -float cyclesPerSecond = 50; // use float to ensure accurate maths +float cyclesPerSecond = 50; // use float to ensure accurate maths -byte polarityNow; +byte polarityNow; boolean beyondStartUpPhase = false; byte currentStateOfTriac; -int lastSample_V1; // stored value from the previous loop (HP filter is for voltage samples only) -float lastFiltered_V1; // voltage values after HP-filtering to remove the DC offset -byte polarityOfLastSample_V1; // for zero-crossing detection - +int lastSample_V1; // stored value from the previous loop (HP filter is for voltage samples only) +float lastFiltered_V1; // voltage values after HP-filtering to remove the DC offset +byte polarityOfLastSample_V1; // for zero-crossing detection + boolean recordingNow; boolean recordingComplete; byte cycleNumberBeingRecorded; @@ -50,36 +50,35 @@ byte noOfCyclesToBeRecorded; unsigned long recordingMayStartAt; boolean firstLoop = true; -int settlingDelay = 5; // <<--- settling time (seconds) for HPF +int settlingDelay = 5; // <<--- settling time (seconds) for HPF char blankLine[82]; char newLine[82]; -int storedSample_V1[50]; -int storedSample_V2[50]; +int storedSample_V1[50]; +int storedSample_V2[50]; int storedSample_V3[50]; int storedSample_I1[50]; int storedSample_I2[50]; int storedSample_I3[50]; void setup() -{ +{ delay(3000); Serial.begin(9600); - + // initialise each character of the display line blankLine[0] = '|'; blankLine[80] = '|'; - + for (int i = 1; i < 80; i++) { blankLine[i] = ' '; } - + blankLine[40] = '.'; - + Serial.print(">>free RAM = "); Serial.println(freeRam()); // a useful value to keep an eye on - } @@ -96,74 +95,85 @@ void setup() * At the start of the following cycle, the data collected during the * previous cycle data is sent to the Serial window. */ -void loop() // each iteration of loop is for one pair of measurements only +void loop() // each iteration of loop is for one pair of measurements only { - if(firstLoop) + if (firstLoop) { unsigned long timeNow = millis(); - Serial.print ("millis() now = "); - Serial.println (timeNow); - + Serial.print("millis() now = "); + Serial.println(timeNow); + recordingMayStartAt = timeNow + (settlingDelay * 1000); - Serial.print ("recordingMayStartAt "); - Serial.println (recordingMayStartAt); - + Serial.print("recordingMayStartAt "); + Serial.println(recordingMayStartAt); + recordingNow = false; firstLoop = false; recordingComplete = false; noOfCyclesToBeRecorded = 1; cycleNumberBeingRecorded = 0; - samplesRecorded = 0; + samplesRecorded = 0; } - - int sample_V1 = analogRead(sensorPin_V1); //Read in raw voltage signal - int sample_I1 = analogRead(sensorPin_I1); //Read in raw current signal - int sample_V2 = analogRead(sensorPin_V2); //Read in raw voltage signal - int sample_I2 = analogRead(sensorPin_I2); //Read in raw current signal - int sample_V3 = analogRead(sensorPin_V3); //Read in raw current signal - int sample_I3 = analogRead(sensorPin_I3); //Read in raw current signal - float filtered_V1 = 0.996*(lastFiltered_V1 + sample_V1 - lastSample_V1); + int sample_V1 = analogRead(sensorPin_V1); //Read in raw voltage signal + int sample_I1 = analogRead(sensorPin_I1); //Read in raw current signal + int sample_V2 = analogRead(sensorPin_V2); //Read in raw voltage signal + int sample_I2 = analogRead(sensorPin_I2); //Read in raw current signal + int sample_V3 = analogRead(sensorPin_V3); //Read in raw current signal + int sample_I3 = analogRead(sensorPin_I3); //Read in raw current signal + + float filtered_V1 = 0.996 * (lastFiltered_V1 + sample_V1 - lastSample_V1); byte polarityOfThisSample_V1; - if(filtered_V1 > 0) + if (filtered_V1 > 0) { polarityOfThisSample_V1 = POSITIVE; - - if (polarityOfLastSample_V1 != POSITIVE) + + if (polarityOfLastSample_V1 != POSITIVE) { // This is the start of a new mains cycle - cycleCount++; - - if (recordingNow == true) { - if (cycleNumberBeingRecorded >= noOfCyclesToBeRecorded) { - Serial.print ("No of cycles recorded = "); - Serial.println (cycleNumberBeingRecorded); - dispatch_recorded_data(); } - else { - cycleNumberBeingRecorded++; } } - - else - if((cycleCount % 50) == 1) { - unsigned long timeNow = millis(); - if (timeNow > recordingMayStartAt) { - recordingNow = true; - cycleNumberBeingRecorded++; } - else { - Serial.println((int)(recordingMayStartAt - timeNow) / 1000); } } - } // end of specific processing for first +ve reading in each mains cycle - - } // end of specific processing of +ve cycles + cycleCount++; + + if (recordingNow == true) + { + if (cycleNumberBeingRecorded >= noOfCyclesToBeRecorded) + { + Serial.print("No of cycles recorded = "); + Serial.println(cycleNumberBeingRecorded); + dispatch_recorded_data(); + } + else + { + cycleNumberBeingRecorded++; + } + } + + else if ((cycleCount % 50) == 1) + { + unsigned long timeNow = millis(); + if (timeNow > recordingMayStartAt) + { + recordingNow = true; + cycleNumberBeingRecorded++; + } + else + { + Serial.println((int)(recordingMayStartAt - timeNow) / 1000); + } + } + } // end of specific processing for first +ve reading in each mains cycle + + } // end of specific processing of +ve cycles else { - polarityOfThisSample_V1 = NEGATIVE; - - if (polarityOfLastSample_V1 != NEGATIVE) + polarityOfThisSample_V1 = NEGATIVE; + + if (polarityOfLastSample_V1 != NEGATIVE) { // at the start of a new negative half cycle } } - + if (recordingNow == true) { storedSample_V1[samplesRecorded] = sample_V1; @@ -172,19 +182,19 @@ void loop() // each iteration of loop is for one pair of measurements only storedSample_I1[samplesRecorded] = sample_I1; storedSample_I2[samplesRecorded] = sample_I2; storedSample_I3[samplesRecorded] = sample_I3; - samplesRecorded++; + ++samplesRecorded; } - - polarityOfLastSample_V1 = polarityOfThisSample_V1; - lastSample_V1 = sample_V1; - lastFiltered_V1 = filtered_V1; -} // end of loop() + + polarityOfLastSample_V1 = polarityOfThisSample_V1; + lastSample_V1 = sample_V1; + lastFiltered_V1 = filtered_V1; +} // end of loop() void dispatch_recorded_data() -{ +{ // display raw samples via the Serial Monitor - // ------------------------------------------ + // ------------------------------------------ Serial.print("cycleCount "); Serial.print(cycleCount); @@ -200,102 +210,113 @@ void dispatch_recorded_data() int V3, I3; int min_V3 = 1023, min_I3 = 1023; int max_V3 = 0, max_I3 = 0; - - for (int index = 0; index < samplesRecorded; index++) + + for (int index = 0; index < samplesRecorded; index++) { strcpy(newLine, blankLine); - V1 = storedSample_V1[index]; - I1 = storedSample_I1[index]; - V2 = storedSample_V2[index]; - I2 = storedSample_I2[index]; - V3 = storedSample_V3[index]; - I3 = storedSample_I3[index]; - - if (V1 < min_V1){min_V1 = V1;} - if (V1 > max_V1){max_V1 = V1;} - if (I1 < min_I1){min_I1 = I1;} - if (I1 > max_I1){max_I1 = I1;} - - if (V2 < min_V2){min_V2 = V2;} - if (V2 > max_V2){max_V2 = V2;} - if (I2 < min_I2){min_I2 = I2;} - if (I2 > max_I2){max_I2 = I2;} - - if (V3 < min_V3){min_V3 = V3;} - if (V3 > max_V3){max_V3 = V3;} - if (I3 < min_I3){min_I3 = I3;} - if (I3 > max_I3){max_I3 = I3;} - - newLine[map(V1, 0, 1023, 0, 80)] = '0'; - newLine[map(I1, 0, 1023, 0, 80)] = '1'; - newLine[map(V2, 0, 1023, 0, 80)] = '2'; - newLine[map(I2, 0, 1023, 0, 80)] = '3'; - newLine[map(V3, 0, 1023, 0, 80)] = '4'; - newLine[map(I3, 0, 1023, 0, 80)] = '5'; - - if ((index % 1) == 0) // change this to "% 1" for full resolution + V1 = storedSample_V1[index]; + I1 = storedSample_I1[index]; + V2 = storedSample_V2[index]; + I2 = storedSample_I2[index]; + V3 = storedSample_V3[index]; + I3 = storedSample_I3[index]; + + if (V1 < min_V1) { min_V1 = V1; } + if (V1 > max_V1) { max_V1 = V1; } + if (I1 < min_I1) { min_I1 = I1; } + if (I1 > max_I1) { max_I1 = I1; } + + if (V2 < min_V2) { min_V2 = V2; } + if (V2 > max_V2) { max_V2 = V2; } + if (I2 < min_I2) { min_I2 = I2; } + if (I2 > max_I2) { max_I2 = I2; } + + if (V3 < min_V3) { min_V3 = V3; } + if (V3 > max_V3) { max_V3 = V3; } + if (I3 < min_I3) { min_I3 = I3; } + if (I3 > max_I3) { max_I3 = I3; } + + newLine[map(V1, 0, 1023, 0, 80)] = '0'; + newLine[map(I1, 0, 1023, 0, 80)] = '1'; + newLine[map(V2, 0, 1023, 0, 80)] = '2'; + newLine[map(I2, 0, 1023, 0, 80)] = '3'; + newLine[map(V3, 0, 1023, 0, 80)] = '4'; + newLine[map(I3, 0, 1023, 0, 80)] = '5'; + + if ((index % 1) == 0) // change this to "% 1" for full resolution { Serial.println(newLine); } } - - Serial.print("min_V1 "); Serial.print(min_V1); - Serial.print(", max_V1 "); Serial.print(max_V1); - Serial.print(", min_I1 "); Serial.print(min_I1); - Serial.print(", max_I1 "); Serial.println(max_I1); - - Serial.print("min_V2 "); Serial.print(min_V2); - Serial.print(", max_V2 "); Serial.print(max_V2); - Serial.print(", min_I2 "); Serial.print(min_I2); - Serial.print(", max_I2 "); Serial.println(max_I2); - - Serial.print("min_V3 "); Serial.print(min_V3); - Serial.print(", max_V3 "); Serial.print(max_V3); - Serial.print(", min_I3 "); Serial.print(min_I3); - Serial.print(", max_I3 "); Serial.println(max_I3); - - + + Serial.print("min_V1 "); + Serial.print(min_V1); + Serial.print(", max_V1 "); + Serial.print(max_V1); + Serial.print(", min_I1 "); + Serial.print(min_I1); + Serial.print(", max_I1 "); + Serial.println(max_I1); + + Serial.print("min_V2 "); + Serial.print(min_V2); + Serial.print(", max_V2 "); + Serial.print(max_V2); + Serial.print(", min_I2 "); + Serial.print(min_I2); + Serial.print(", max_I2 "); + Serial.println(max_I2); + + Serial.print("min_V3 "); + Serial.print(min_V3); + Serial.print(", max_V3 "); + Serial.print(max_V3); + Serial.print(", min_I3 "); + Serial.print(min_I3); + Serial.print(", max_I3 "); + Serial.println(max_I3); + + Serial.println(); Serial.println(); - - // despatch raw samples via the Serial Monitor - // ------------------------------------------- - + + // despatch raw samples via the Serial Monitor + // ------------------------------------------- + Serial.println("Raw data from stored cycle: ,[cr]"); Serial.print(samplesRecorded); Serial.println(", <<< No of sample pairs"); - for (int index = 0; index < samplesRecorded; index++) + for (int index = 0; index < samplesRecorded; index++) { - Serial.print (storedSample_V1[index]); - Serial.print(','); - Serial.println (storedSample_I1[index]); + Serial.print(storedSample_V1[index]); + Serial.print(','); + Serial.println(storedSample_I1[index]); } recordingNow = false; firstLoop = true; pause(); -} +} void pause() { byte done = false; byte dummyByte; - + while (done != true) { if (Serial.available() > 0) { - dummyByte = Serial.read(); // to 'consume' the incoming byte + dummyByte = Serial.read(); // to 'consume' the incoming byte if (dummyByte == 'g') done++; } - } + } } -int freeRam () { - extern int __heap_start, *__brkval; - int v; - return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); +int freeRam() +{ + extern int __heap_start, *__brkval; + int v; + return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval); } - - diff --git a/dev/cal_CTx_v_meter/platformio.ini b/dev/cal_CTx_v_meter/platformio.ini index 50d8f819..8f12e9e4 100644 --- a/dev/cal_CTx_v_meter/platformio.ini +++ b/dev/cal_CTx_v_meter/platformio.ini @@ -28,6 +28,7 @@ check_flags = cppcheck: --enable=all clangtidy: --fix --checks=*,-llvmlibc-callee-namespace,-llvmlibc-implementation-in-namespace,-clang-diagnostic-c++17-extensions check_skip_packages = yes +check_src_filters = +<*> monitor_filters = default ; Remove typical terminal control codes from input time ; Add timestamp with milliseconds for each new line diff --git "a/docs/Carte-m\303\250re.drawio" "b/docs/Carte-m\303\250re.drawio" index 42a98cd6..e3842026 100644 --- "a/docs/Carte-m\303\250re.drawio" +++ "b/docs/Carte-m\303\250re.drawio" @@ -1,133 +1,241 @@ - + - + - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - - + + - + - - + + - + - - + + - - + + - + - - + + - - + + - - + + - - + + - - + + - - + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + diff --git "a/docs/Carte-m\303\250re.pdf" "b/docs/Carte-m\303\250re.pdf" index ebe2c0d0..eb299270 100644 Binary files "a/docs/Carte-m\303\250re.pdf" and "b/docs/Carte-m\303\250re.pdf" differ diff --git a/docs/CarteACI_Mono_Autre_Routeur.drawio b/docs/CarteACI_Mono_Autre_Routeur.drawio index 3804fff9..9626e0a9 100644 --- a/docs/CarteACI_Mono_Autre_Routeur.drawio +++ b/docs/CarteACI_Mono_Autre_Routeur.drawio @@ -1,16 +1,13 @@ - + - + - - - - + - + @@ -92,13 +89,13 @@ - + - - + + @@ -184,7 +181,7 @@ - + @@ -194,43 +191,21 @@ - + - - - - - - - - - - - - + - - - - - - - - - - - @@ -249,32 +224,33 @@ - + - + - - + + - + - + - + + - + - + - + @@ -283,23 +259,11 @@ - - - - - - - - - - - - - + @@ -352,7 +316,7 @@ - + @@ -441,7 +405,7 @@ - + @@ -451,28 +415,17 @@ - + - - - - - - - - - - - - + @@ -495,12 +448,12 @@ - + - + @@ -510,9 +463,9 @@ - + - + @@ -563,20 +516,14 @@ - - - - - - - - + + - - + + @@ -654,17 +601,6 @@ - - - - - - - - - - - @@ -696,15 +632,15 @@ - + - - - - + + + + @@ -793,17 +729,6 @@ - - - - - - - - - - - @@ -837,9 +762,9 @@ - + - + @@ -891,55 +816,44 @@ - + - + - + - + - + - + - - - - - - - - - - - - + - + @@ -948,7 +862,7 @@ - + @@ -967,7 +881,7 @@ - + @@ -981,18 +895,7 @@ - - - - - - - - - - - - + @@ -1015,46 +918,23 @@ - - - - - - - - - - - - - + - + - + - - - - - - - - - - - - - - - + + + + @@ -1063,27 +943,292 @@ - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/CarteACI_Mono_Autre_Routeur.pdf b/docs/CarteACI_Mono_Autre_Routeur.pdf index f4a92988..8d3fb811 100644 Binary files a/docs/CarteACI_Mono_Autre_Routeur.pdf and b/docs/CarteACI_Mono_Autre_Routeur.pdf differ diff --git a/docs/SortieRelais.drawio b/docs/SortieRelais.drawio index 9a1d18b8..5717681b 100644 --- a/docs/SortieRelais.drawio +++ b/docs/SortieRelais.drawio @@ -1,6 +1,6 @@ - + - + @@ -10,16 +10,16 @@ - + - + - + - + @@ -35,7 +35,7 @@ - + @@ -44,7 +44,7 @@ - + @@ -55,7 +55,7 @@ - + @@ -67,12 +67,15 @@ - + - + + + + diff --git a/docs/SortieRelais.pdf b/docs/SortieRelais.pdf index e40352ae..898b8179 100644 Binary files a/docs/SortieRelais.pdf and b/docs/SortieRelais.pdf differ