diff --git a/docs/assets/img/hardware/shelly_solar_diverter_poc.jpeg b/docs/assets/img/hardware/shelly_solar_diverter_poc.jpeg index 31b0b05..727da41 100644 Binary files a/docs/assets/img/hardware/shelly_solar_diverter_poc.jpeg and b/docs/assets/img/hardware/shelly_solar_diverter_poc.jpeg differ diff --git a/docs/assets/img/schemas/Solar_Router_Diverter.jpg b/docs/assets/img/schemas/Solar_Router_Diverter.jpg index 497e67c..911df91 100644 Binary files a/docs/assets/img/schemas/Solar_Router_Diverter.jpg and b/docs/assets/img/schemas/Solar_Router_Diverter.jpg differ diff --git a/docs/blog/2024-07-01_shelly_solar_diverter.md b/docs/blog/2024-07-01_shelly_solar_diverter.md index 49cdf74..b2907e8 100644 --- a/docs/blog/2024-07-01_shelly_solar_diverter.md +++ b/docs/blog/2024-07-01_shelly_solar_diverter.md @@ -28,7 +28,10 @@ _I've put the YaSolR project in pause for a few days to work on this very cool a - [Excess sharing amongst dimmers](#excess-sharing-amongst-dimmers) - [Start / Stop Automatic Divert](#start--stop-automatic-divert) - [Solar Diverter Status](#solar-diverter-status) + - [PID Control and Tuning](#pid-control-and-tuning) +- [Future Improvements](#future-improvements) - [Demo](#demo) +- [Help and Support](#help-and-support) ## What is a Solar Router / Diverter ? @@ -46,6 +49,7 @@ A router can also schedule some forced heating of the water tank to ensure the w - **Unlimited dimmers (output)** - **PID Controller** - **Excess sharing amongst dimmers with percentages** +- **Bypass (force heating)** and automatically turn the dimmer off - **Plus all the power of the Shelly ecosystem (rules, schedules, automations, etc)** This solar diverter based on Shelly devices and a Shelly script can control remotely dimmers and could even be enhanced with relays. @@ -91,8 +95,7 @@ First the easy part: the temperature sensor and the Shelly Add-On, which has to For example, the Shelly EM can be inside the main electric box, and the Shelly Dimmer + Add-On can be in the water tank electric panel, while the contactor and dimmer can be placed neat the water tank. They communicate through the network. - The dimmer will control the voltage regulator through the `COM` and `0-10V` ports -- The dimmer will also control the relay or contactor through the `A2` ports -- The wire from `Dimmer Output` to `Dimmer S1` is to set the switch mode to invert and make the dimmer detect when the contactor is OFF or ON and respectively disable or enable the dimming. +- The Shelly EM will control the relay or contactor through the `A2` ports. - The B clamp around the wire going from the voltage regulator to the water tank is to measure the current going through the water tank resistance is optional and for information purposes only. - The A clamp should be put around the main phase entering the house - The relay / contactor is optional and is used to schedule some forced heating of the water tank to ensure the water reaches a safe temperature, and consequently bypass the dimmed voltage. @@ -122,6 +125,8 @@ Also, this central place allows to control the 1, 2 or more dimmers remotely. - Make sure the switch (input) are enabled and inverted. S1 should replicate the inverse state of the relay / contactor, so that when you activate the contactor through the Dimmer output remotely, the dimmer will deactivate itself. - Set static IP addresses +- Use the min/max settings to remap the 0-100% to match the voltage regulator. + I noticed that the LSA allows a wider range but the one from LCTC has to be re-mapped from 10-80% ### Shelly Pro EM 50 Setup @@ -129,7 +134,7 @@ Also, this central place allows to control the 1, 2 or more dimmers remotely. - Make sure to place the A clamp around the main phase entering the house in the right direction - Add the `Shelly Solar Diverter` to the Shelly Pro EM - Configure the settings in the `CONFIG` object -- Start teh script +- Start the script ## How to use @@ -144,31 +149,51 @@ const CONFIG = { // Grid Power Read Interval (s) READ_INTERVAL_S: 1, PID: { + // Reverse + REVERSE: false, + // Proportional Mode: + // - "error" (proportional on error), + // - "input" (proportional on measurement), + // - "both" (proportional on 50% error and 50% measurement) + P_MODE: "input", + // Derivative Mode: + // - "error" (derivative on error), + // - "input" (derivative on measurement) + D_MODE: "error", + // Integral Correction + // - "off" (integral sum not clamped), + // - "clamp" (integral sum not clamped to OUT_MIN and OUT_MAX), + // - "advanced" (advanced anti-windup algorithm) + IC_MODE: "advanced", // Target Grid Power (W) - SET_POINT: 0, - // Number of Watts allowed to be above or below the set point (W) - SET_POINT_DELTA: 2, + SETPOINT: 0, // PID Proportional Gain - KP: 0.8, + KP: 0.3, // PID Integral Gain - KI: 0, + KI: 0.3, // PID Derivative Gain - KD: 0.8, - // PID Output Minimum Clamp (W) - OUTPUT_MIN: 0, + KD: 0.1, + // Output Minimum (W) + OUT_MIN: -10000, + // Output Maximum (W) + OUT_MAX: 10000, }, DIMMERS: { - "192.168.125.93": { + "192.168.125.98": { // Resistance (in Ohm) of the load connecter to the dimmer + voltage regulator // 0 will disable the dimmer RESISTANCE: 24, // Percentage of the remaining excess power that will be assigned to this dimmer // The remaining percentage will be given to the next dimmers RESERVED_EXCESS_PERCENT: 100, + // Set whether the Shelly EM with this script will be used to control the bypass relay to force a heating + // When set to true, if you activate remotely the bypass to force a heating, then the script will tur the dimmer off + BYPASS_CONTROLLED_BY_EM: true, }, "192.168.125.97": { RESISTANCE: 0, RESERVED_EXCESS_PERCENT: 100, + BYPASS_CONTROLLED_BY_EM: false, }, }, }; @@ -189,19 +214,20 @@ It is possible to share the excess power amongst the dimmers. Let's say you have 3 dimmers with this configuration: ```javascript - DIMMERS: { - "192.168.125.93": { - RESISTANCE: 53, - RESERVED_EXCESS_PERCENT: 50, - }, - "192.168.125.94": { - RESISTANCE: 53, - RESERVED_EXCESS_PERCENT: 25, - }, - "192.168.125.95": { - RESISTANCE: 53, - RESERVED_EXCESS_PERCENT: 100, - }, +DIMMERS: { + "192.168.125.93": { + RESISTANCE: 53, + RESERVED_EXCESS_PERCENT: 50, + }, + "192.168.125.94": { + RESISTANCE: 53, + RESERVED_EXCESS_PERCENT: 25, + }, + "192.168.125.95": { + RESISTANCE: 53, + RESERVED_EXCESS_PERCENT: 100, + } +} ``` When you'll have 3000W of excess: @@ -215,10 +241,8 @@ When you'll have 3000W of excess: You can start / stop the script manually from the interface or remotely by calling: ``` - http://192.168.125.92/rpc/Script.Start?id=1 http://192.168.125.92/rpc/Script.Stop?id=1 - ``` - `192.168.125.92` begin the Shelly EM 50 static IP address. @@ -233,59 +257,90 @@ Once the script is uploaded and started, it will automatically manage the power You can view the status of the script by going to the script `status` endpoint, which is only available when the script is running. ``` - http://192.168.125.92/script/1/status - ``` ```json { + "config": { + "DEBUG": 2, + "READ_INTERVAL_S": 1, + "PID": { + "REVERSE": false, + "P_MODE": "input", + "D_MODE": "error", + "IC_MODE": "advanced", + "SETPOINT": 0, + "KP": 0.3, + "KI": 0.3, + "KD": 0.1, + "OUT_MIN": -10000, + "OUT_MAX": 10000 + }, + "DIMMERS": { + "192.168.125.98": { + "RESISTANCE": 24, + "RESERVED_EXCESS_PERCENT": 100 + }, + "192.168.125.97": { + "RESISTANCE": 0, + "RESERVED_EXCESS_PERCENT": 100 + } + } + }, "pid": { "input": 0, "output": 0, "error": 0, "pTerm": 0, "iTerm": 0, - "dTerm": 0 + "dTerm": 0, + "sum": 0 }, "divert": { - "voltage": 237.9, - "gridPower": 0, - "divertPower": 173.76, + "lastTime": 1720281498691.629, "dimmers": { - "192.168.125.93": { - "divertPower": 86.88, - "nominalPower": 2358.18375, - "dutyCycle": 0.03684191276, - "powerFactor": 0.19194247253, - "dimmedVoltage": 45.66311421705, - "current": 1.90262975904, - "apparentPower": 452.63561967657, - "thdi": 5.11302248812, - "rpc": "pending" - }, - "192.168.125.97": { - "divertPower": 86.88, - "nominalPower": 2358.18375, - "dutyCycle": 0.03684191276, - "powerFactor": 0.19194247253, - "dimmedVoltage": 45.66311421705, - "current": 1.90262975904, - "apparentPower": 452.63561967657, - "thdi": 5.11302248812, - "rpc": "pending" + "192.168.125.98": { + "divertPower": 0, + "maximumPower": 2263.98374999999, + "dutyCycle": 0, + "powerFactor": 0, + "dimmedVoltage": 0, + "current": 0, + "apparentPower": 0, + "thdi": 0, + "rpc": "success" } } } } ``` +### PID Control and Tuning + +The script uses a complex PID controller that can be tuned to really obtain a very good routing precision. +The algorithm used and default parameters are the same as in the YaSolr project. +You will find a lot of information in the [YaSolR manual](/manual#dashboard--pid-controller). + +## Future Improvements + +- Key Value storage to store the configuration and status of the script +- Virtual Components to expose script variables (also soon in [Home Assistant](https://github.com/home-assistant/core/pull/119932)) + ## Demo Here is the view of the Shelly device websites while the grid power is changing. -[![Shelly Solar Diverter Demo](http://img.youtube.com/vi/he5qPJx8_R4/0.jpg)](http://www.youtube.com/watch?v=he5qPJx8_R4 "Shelly Solar Diverter Demo") +[![Shelly Solar Diverter Demo](http://img.youtube.com/vi/qDV0VZnWXWU/0.jpg)](http://www.youtube.com/watch?v=qDV0VZnWXWU "Shelly Solar Diverter Demo") + +Here is a PoC box I am using for my testing with all the components wired. -Here is a PoC box I am using for my testing with all the components wired. I am still waiting for the dimmer gen 3 which works in **current sourcing** mode, but everything else is working. +The Shelly Dimmer Gen 3 with the Shelly Addon are in the black enclosure, the voltage regulator on the right and the Shelly EM Pro at the top right. + +In this PoC, I have used the LCTC voltage regulator which comes already pre-mounted on a heat sink, but I encourage you to use the LSA instead (which I also have). The LSA is better at detected lower DC voltage so the dimmer resolution will be increased. ![](/assets/img/hardware/shelly_solar_diverter_poc.jpeg) + +## Help and Support + +Please use the links at the bottom of the page. diff --git a/docs/downloads/solar_diverter_en.md b/docs/downloads/solar_diverter_en.md new file mode 100644 index 0000000..49fdeb3 --- /dev/null +++ b/docs/downloads/solar_diverter_en.md @@ -0,0 +1,41 @@ +The Shelly Solar Diverter/Router + +Hello, + +I wanted to share with you a small project that I carried out in the recent days as part of the "Shelly IoT Innovation Challenge" and which will surely pick your interest: a solar diverter/router that is quite innovative because it is very simple and based on... Shelly's of course! + +I always wanted to find a **simple** way to control the **Loncont LSA-H3P50YB** voltage regulator, which is interesting because in addition to being sturdy, it includes a Zero-Cross Detection module and is controlled very easily by voltage variation or PWM. +In particular, it can be controlled by an ESP32 but using an external module and an additional 12V power supply, which is not very practical. + +A few days ago, a friend pointed out to me that Shelly has a 0-10V dimmer, the voltage range required to control this regulator, but for it to work, the dimmer must be of the "sourcing current" type and not "sinking current". + +Except... Shelly has just released a new dimmer very recently: the **Shelly Dimmer 0/1-10V PM Gen3**, which is precisely of a "sourcing current" type! + +What a joy! + +So I decided to write a Shelly script which allows you to automatically control this type of voltage regulator via the Shelly Dimmer 0/1-10V PM Gen3, depending on the injection or consumption read from the Shelly EM Pro (purchased from Quintium), which also reads the output power of the regulator with its 2nd current clamp. + +I have not yet been able to test in the long term, but the small tests carried out for the moment show that it works, within the possible limit of the precision that can be obtained using the Shelly scripts: reading the measurements at each second and then a call to adjust the dimmer immediately afterwards. + +It is therefore a promising solution for the moment, and which remains easy to improve. + +Also, dimmers tend to get hot, so I suspect it needs to be installed not within an enclosure. + +Features and benefits of this diverter/router: + +- Shelly and LSA Loncont components are robust, compliant with standards, and used in industry +- Very easy to set up (Shelly script) +- Automatic divert management via a PID controller which supports several proportional and derivative modes +- Supports a bypass contactor (to force a heating), which will automatically cut off the dimmers if turned on +- Supports a DS1820 temperature probe via the Shelly Add-on to get the water tank temperature (or anything else) +- Support for up to N dimmers, with possible sharing of the excess power between dimmers +- And of course, everything you can have with remote control of Shelly's in the Shelly App, Home Assistant / Jeedom, etc. + +It's then up to you to write your Shelly automations to program forced operation, start or stop automatic routing remotely, etc. +Full of possibilities with Shelly's! + +The script can be downloaded and modified as you wish (it is under the MIT license), and it can be found on the blog of the YaSolR site, the routing software I've been working for several months now: + +https://yasolr.carbou.me/blog/2024-07-01_shelly_solar_diverter + +Happy hacking! diff --git a/docs/downloads/solar_diverter_fr.md b/docs/downloads/solar_diverter_fr.md new file mode 100644 index 0000000..2631c19 --- /dev/null +++ b/docs/downloads/solar_diverter_fr.md @@ -0,0 +1,41 @@ +Le Routeur Solaire Shelly + +Bonjour la communauté! + +Je souhaitais vous partager un petit projet que j'ai réalisé ces derniers jours dans le cadre du _"Shelly IoT Innovation Challenge"_ et qui va sûrement vous intéresser: un routeur solaire assez innovant car très simple et basé sur... les Shelly! + +J'ai toujours souhaité trouvé un moyen **simple** de contrôler le régulateur de tension **Loncont LSA-H3P50YB**, qui est intéressant car en plus d'être costaud, il comprend un module Zero-Detection et se contrôle donc très facilement par variation de tension ou PWM. +On peut notamment le contrôler par un ESP32 mais à l'aide d'un module externe et une alimentation de 12V supplémentaire, ce qui n'est pas très pratique. + +Il y a quelques jours, Mathieu Hertz m'a fait remarqué que Shelly ont des dimmers 0-10V, plage de tension justement requise au contrôle de ce régulateur, mais pour que cela fonctionne, il faut que le dimmer soit de type "sourcing current" et non "sinking current". + +Hors... Shelly vient justement de sortie un nouveau dimmer depuis très peu: le **Shelly Dimmer 0/1-10V PM Gen3**, qui est justement de type "sourcing current"! + +Quel bonheur! + +J'ai donc décidé d'écrire un **script Shelly** qui permet de contrôler automatiquement ce genre de régulateur de tension via le Shelly Dimmer 0/1-10V PM Gen3, en fonction de la l'injection ou consommation lue à partir du Shelly EM Pro (acheté chez Quintium), qui lit aussi la puissance en sortie du régulateur avec sa 2ème pince ampèremétrique. + +Je n'ai pas pu tester encore sur du long terme, mais les petits tests effectués pour le moment montrent que ça fonctionne, dans la limite possible de la précision qu'on peut avoir en passant par les scripts Shelly: lecture des mesure à chaque seconde et appel pour régler le dimmer de suite après. + +C'est donc une solution prometteuse pour le moment, et qui reste facile à améliorer. + +Également, les dimmer ont tendance à chauffer, donc je soupçonne qu'il faille l'installer espacé et dans un endroit aéré. + +Fonctionnalités et avantages de ce routeur: + +- Les composants Shelly et LSA Loncont, sont robustes, aux normes, et utilisés en industrie +- Très facile à mettre en place (script Shelly) +- Gestion automatique du routage via un contrôleur PID qui supporte plusieurs modes de proportionnelles et dérivées +- Supporte un contacteur pour la Marche forcée, qui va automatiquement couper les dimmer si mis en marche +- Supporte une sonde de température DS1820 via le Shelly Add-on pour avoir la température du ballon +- Support jusqu'à N dimmers, avec un partage possible du surplus entre dimmers +- Et bien sûr, tout ce qu'on peut avoir avec le contrôle à distance des Shelly via l'app Shelly, Home Assistant / Jeedom, etc. + +À vous ensuite d'écrite vos automatismes Shelly pour programmer la marche forcée, démarrer ou arrêter le routage automatique à distance, etc. +Plein de possibilité avec Shelly! + +Le script peut être téléchargé et modifié à votre guise (il est sous license MIT), et il se trouve sur le blog du site YaSolR, le logiciel de routage sur lequel je travaille depuis quelques mois: + +https://yasolr.carbou.me/blog/2024-07-01_shelly_solar_diverter + +Bonne lecture! diff --git a/docs/downloads/solar_diverter_v1.js b/docs/downloads/solar_diverter_v1.js index 5d24c7e..9482dd4 100644 --- a/docs/downloads/solar_diverter_v1.js +++ b/docs/downloads/solar_diverter_v1.js @@ -9,7 +9,7 @@ * * ====================================== */ -const scriptName = "solar_diverter.js"; +const scriptName = "solar_diverter"; // Config @@ -19,31 +19,51 @@ const CONFIG = { // Grid Power Read Interval (s) READ_INTERVAL_S: 1, PID: { + // Reverse + REVERSE: false, + // Proportional Mode: + // - "error" (proportional on error), + // - "input" (proportional on measurement), + // - "both" (proportional on 50% error and 50% measurement) + P_MODE: "input", + // Derivative Mode: + // - "error" (derivative on error), + // - "input" (derivative on measurement) + D_MODE: "error", + // Integral Correction + // - "off" (integral sum not clamped), + // - "clamp" (integral sum not clamped to OUT_MIN and OUT_MAX), + // - "advanced" (advanced anti-windup algorithm) + IC_MODE: "advanced", // Target Grid Power (W) - SET_POINT: 0, - // Number of Watts allowed to be above or below the set point (W) - SET_POINT_DELTA: 0, + SETPOINT: 0, // PID Proportional Gain - KP: 0.8, + KP: 0.3, // PID Integral Gain - KI: 0.01, + KI: 0.3, // PID Derivative Gain - KD: 0.8, - // PID Output Minimum Clamp (W) - ITERM_MIN: -10, + KD: 0.1, + // Output Minimum (W) + OUT_MIN: -10000, + // Output Maximum (W) + OUT_MAX: 10000, }, DIMMERS: { - "192.168.125.93": { + "192.168.125.98": { // Resistance (in Ohm) of the load connecter to the dimmer + voltage regulator // 0 will disable the dimmer RESISTANCE: 24, // Percentage of the remaining excess power that will be assigned to this dimmer // The remaining percentage will be given to the next dimmers - RESERVED_EXCESS_PERCENT: 100 + RESERVED_EXCESS_PERCENT: 100, + // Set whether the Shelly EM with this script will be used to control the bypass relay to force a heating + // When set to true, if you activate remotely the bypass to force a heating, then the script will tur the dimmer off + BYPASS_CONTROLLED_BY_EM: true }, "192.168.125.97": { RESISTANCE: 0, - RESERVED_EXCESS_PERCENT: 100 + RESERVED_EXCESS_PERCENT: 100, + BYPASS_CONTROLLED_BY_EM: false } } }; @@ -63,27 +83,26 @@ let PID = { iTerm: 0, // Derivative Term dTerm: 0, -} + // Sum + sum: 0, +}; // Divert Control let DIVERT = { lastTime: 0, - voltage: 0, - gridPower: CONFIG.PID.SET_POINT, - divertPower: CONFIG.PID.SET_POINT, dimmers: {} -} +}; function validateConfig(cb) { - print(scriptName, ":", "Validating Config...") + print(scriptName, ":", "Validating Config..."); if (CONFIG.DIMMERS.length === 0) { print(scriptName, ":", "ERR: No dimmer configured"); return; } - for (const ip in CONFIG.DIMMERS) { + for (let ip in CONFIG.DIMMERS) { if (CONFIG.DIMMERS[ip].RESISTANCE < 0) { print(scriptName, ":", "ERR: Dimmer resistance should be greater than 0"); return; @@ -97,32 +116,110 @@ function validateConfig(cb) { print(scriptName, ":", "Dimmer", ip, "is enabled"); DIVERT.dimmers[ip] = { divertPower: 0 - } + }; } cb(); } +function constrain(value, min, max) { return Math.min(Math.max(value, min), max); } + +// - https://github.com/Dlloydev/QuickPID +// - https://github.com/br3ttb/Arduino-PID-Library function calculatePID(input) { - PID.input = input; - const error = CONFIG.PID.SET_POINT - PID.input; - if (Math.abs(error) <= CONFIG.PID.SET_POINT_DELTA) { - PID.pTerm = 0; - PID.iTerm = 0; - PID.dTerm = 0; - PID.output = 0; - } else { - PID.pTerm = error * CONFIG.PID.KP; - PID.iTerm = Math.max(PID.iTerm + error * CONFIG.PID.KI, CONFIG.PID.ITERM_MIN); - PID.dTerm = (error - PID.error) * CONFIG.PID.KD; + const dInput = CONFIG.PID.REVERSE ? PID.input - input : input - PID.input; + const error = CONFIG.PID.REVERSE ? input - CONFIG.PID.SETPOINT : CONFIG.PID.SETPOINT - input; + const dError = error - PID.error; + + if (CONFIG.DEBUG > 1) { + print(scriptName, ":", "Input:", input, "W, Error:", error, "W, dError:", dError, "W"); + } + + let peTerm = CONFIG.PID.KP * error; + let pmTerm = CONFIG.PID.KP * dInput; + switch (CONFIG.PID.P_MODE) { + case "error": + pmTerm = 0; + break; + case "input": + peTerm = 0; + break; + case "both": + peTerm *= 0.5; + pmTerm *= 0.5; + break; + default: + return PID.output; + } + + // pTerm + PID.pTerm = peTerm - pmTerm; + + // iTerm + PID.iTerm = CONFIG.PID.KI * error; + + if (CONFIG.DEBUG > 1) { + print(scriptName, ":", "pTerm:", PID.pTerm, "W, iTerm:", PID.iTerm, "W"); + } + + // anti-windup + if (CONFIG.PID.IC_MODE == "advanced" && CONFIG.PID.KI) { + const iTermOut = PID.pTerm + CONFIG.PID.KI * (PID.iTerm + error); + if ((iTermOut > CONFIG.PID.OUT_MAX && dError > 0) || (iTermOut < CONFIG.PID.OUT_MIN && dError < 0)) { + _iTerm = constrain(iTermOut, -CONFIG.PID.OUT_MAX, CONFIG.PID.OUT_MAX); + } + } + + // integral sum + PID.sum = CONFIG.PID.IC_MODE == "off" ? (PID.sum + PID.iTerm - pmTerm) : constrain(PID.sum + PID.iTerm - pmTerm, CONFIG.PID.OUT_MIN, CONFIG.PID.OUT_MAX); + + // dTerm + switch (CONFIG.PID.D_MODE) { + case "error": + PID.dTerm = CONFIG.PID.KD * dError; + break; + case "input": + PID.dTerm = -CONFIG.PID.KD * dInput; + break; + default: + return PID.output; } - PID.output = PID.pTerm + PID.iTerm + PID.dTerm; + + PID.output = constrain(PID.sum + peTerm + PID.dTerm, CONFIG.PID.OUT_MIN, CONFIG.PID.OUT_MAX); + + PID.input = input; PID.error = error; + return PID.output; } +function callDimmerCallback(result, errCode, errMessage, data) { + if (errCode) { + print(scriptName, ":", "ERR callDimmerCallback:", errCode); + data.dimmer.rpc = "failed"; + } else if (result.code !== 200) { + const rpcResult = JSON.parse(result.body); + print(scriptName, ":", "ERR", rpcResult.code, ":", rpcResult.message); + data.dimmer.rpc = "failed"; + } else { + data.dimmer.rpc = "success"; + } + data.cb(); +} + +function callDimmer(ip, dimmer, cb) { + const url = "http://" + ip + "/rpc/Light.Set?id=0&on=" + (dimmer.dutyCycle > 0 ? "true" : "false") + "&brightness=" + (dimmer.dutyCycle * 100) + "&transition_duration=0.5"; + if (CONFIG.DEBUG > 1) + print(scriptName, ":", "Calling Dimmer: ", url); + Shelly.call("HTTP.GET", { url: url, timeout: 5 }, callDimmerCallback, { dimmer: dimmer, cb: cb }); +} + function callDimmers(cb) { - for (const ip in DIVERT.dimmers) { + function recallMe() { + callDimmers(cb) + } + + for (let ip in DIVERT.dimmers) { const dimmer = DIVERT.dimmers[ip]; // ignore contacted dimmers @@ -130,28 +227,14 @@ function callDimmers(cb) { continue; } - // build url - const url = "http://" + ip + "/rpc/Light.Set?id=0&on=" + (dimmer.dutyCycle > 0 ? "true" : "false") + "&brightness=" + (dimmer.dutyCycle * 100) + "&transition_duration=0.5"; - - if (CONFIG.DEBUG > 1) - print(scriptName, ":", "Calling Dimmer: ", url); + if (isNaN(dimmer.dutyCycle)) { + dimmer.rpc = "failed"; + print(scriptName, ":", "ERR: Invalid duty cycle for dimmer", ip); + continue; + } // call dimmer - Shelly.call("HTTP.GET", { url: url, timeout: 5 }, function (result, err) { - if (err) { - print(scriptName, ":", "ERR:", err); - dimmer.rpc = "failed"; - } else if (result.code !== 200) { - const rpcResult = JSON.parse(result.body); - print(scriptName, ":", "ERR", rpcResult.code, ":", rpcResult.message); - dimmer.rpc = "failed"; - } else { - dimmer.rpc = "success"; - } - - // once done, call ourself again until no dimmer is left - callDimmers(cb); - }); + callDimmer(ip, dimmer, recallMe); // exit the loop immediately to avoid multiple calls in case the yare made in parallel return; @@ -161,22 +244,43 @@ function callDimmers(cb) { cb(); } +function onSwitchGetStatus(result, errCode, errMessage, data) { + if (errCode) { + print(scriptName, ":", "ERR onSwitchGetStatus:", errCode); + throttleReadPower(); + return; + } + if (CONFIG.DEBUG > 1) + print(scriptName, ":", "onSwitchGetStatus:", JSON.stringify(result)); + if (result.output) { + for (let ip in DIVERT.dimmers) { + const dimmer = DIVERT.dimmers[ip]; + if (CONFIG.DIMMERS[ip].BYPASS_CONTROLLED_BY_EM) { + print(scriptName, ":", "Bypass is ON, turning off dimmer", ip); + dimmer.apparentPower = 0; + dimmer.current = 0; + dimmer.dimmedVoltage = 0; + dimmer.divertPower = 0; + dimmer.dutyCycle = 0; + dimmer.powerFactor = 0; + dimmer.thdi = 0; + } + } + } + callDimmers(throttleReadPower); +} + function divert(voltage, gridPower) { - DIVERT.voltage = voltage; - DIVERT.gridPower = gridPower; - const correction = calculatePID(DIVERT.gridPower); - DIVERT.divertPower = Math.max(0, DIVERT.divertPower + correction); + let newRoutingPower = calculatePID(gridPower); if (CONFIG.DEBUG > 0) - print(scriptName, ":", "Grid:", voltage, "V,", gridPower, "W. Correction:", correction, "W. Total Divert Power:", DIVERT.divertPower, "W"); - - let remaining = DIVERT.divertPower; + print(scriptName, ":", "Grid:", voltage, "V,", gridPower, "W. Power to divert:", newRoutingPower, "W"); - for (const ip in DIVERT.dimmers) { + for (let ip in DIVERT.dimmers) { const dimmer = DIVERT.dimmers[ip]; - dimmer.nominalPower = voltage * voltage / CONFIG.DIMMERS[ip].RESISTANCE; - dimmer.divertPower = Math.min(remaining * CONFIG.DIMMERS[ip].RESERVED_EXCESS_PERCENT / 100, dimmer.nominalPower); - dimmer.dutyCycle = dimmer.divertPower / dimmer.nominalPower; + dimmer.maximumPower = voltage * voltage / CONFIG.DIMMERS[ip].RESISTANCE; + dimmer.divertPower = Math.min(newRoutingPower * CONFIG.DIMMERS[ip].RESERVED_EXCESS_PERCENT / 100, dimmer.maximumPower); + dimmer.dutyCycle = dimmer.divertPower / dimmer.maximumPower; dimmer.powerFactor = Math.sqrt(dimmer.dutyCycle); dimmer.dimmedVoltage = dimmer.powerFactor * voltage; dimmer.current = dimmer.dimmedVoltage / CONFIG.DIMMERS[ip].RESISTANCE; @@ -184,18 +288,21 @@ function divert(voltage, gridPower) { dimmer.thdi = dimmer.dutyCycle === 0 ? 0 : Math.sqrt(1 / dimmer.dutyCycle - 1); dimmer.rpc = "pending"; - remaining -= dimmer.divertPower; + newRoutingPower -= dimmer.divertPower; if (CONFIG.DEBUG > 0) print(scriptName, ":", "Dimmer", ip, "=>", dimmer.divertPower, "W"); } - callDimmers(throttleReadPower); + Shelly.call("Switch.GetStatus", { id: 0 }, onSwitchGetStatus); } -function onEM1GetStatus(result, err) { - if (err) +function onEM1GetStatus(result, errCode, errMessage, data) { + if (errCode) { + print(scriptName, ":", "ERR onEM1GetStatus:", errCode); + throttleReadPower(); return; + } if (CONFIG.DEBUG > 1) print(scriptName, ":", "EM1.GetStatus:", JSON.stringify(result)); divert(result.voltage, result.act_power); @@ -218,12 +325,13 @@ function throttleReadPower() { // HTTP handlers -function onGetStatus(request, response) { +function onHttpGetStatus(request, response) { response.code = 200; response.headers = { "Content-Type": "application/json" - } + }; response.body = JSON.stringify({ + config: CONFIG, pid: PID, divert: DIVERT }); @@ -233,7 +341,7 @@ function onGetStatus(request, response) { // Main validateConfig(function () { - print(scriptName, ":", "Starting Shelly Solar Diverter...") - HTTPServer.registerEndpoint("status", onGetStatus); + print(scriptName, ":", "Starting Shelly Solar Diverter..."); + HTTPServer.registerEndpoint("status", onHttpGetStatus); readPower(); }); diff --git a/docs/manual.md b/docs/manual.md index 2370e7c..b66882e 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -168,6 +168,8 @@ The overview section shows some global information about the router. The temperature is coming from the sensor installed in the router box. +**A JSY or PZEM is required to see the measurements.** + ## Dashboard / Output 1 & 2 The output sections show the state of the outputs and the possibility to control them. @@ -184,6 +186,8 @@ The output sections show the state of the outputs and the possibility to control **Energy:** +**A JSY or PZEM is required to see the measurements.** + - `Power`: Routed power. - `Apparent Power`: Apparent power in VA circulating on the wires. - `Power Factor`: Power factor (if lower than 1, mainly composed of harmonic component). Ideal is close to 1. @@ -462,7 +466,7 @@ Here are some basic links to start with, which talks about the code used under t - Proportional Mode: `On Input` - Derivative Mode: `On Error` -- Integral Correction: `Anti-windup` +- Integral Correction: `Advanced` - Setpoint: `0` - Kp: `0.3` - Ki: `0.3`