Handy functions for dzVents

Easy to use, 100% Lua-based event scripting framework.

Moderator: leecollings

Post Reply
HvdW
Posts: 591
Joined: Sunday 01 November 2015 22:45
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.2
Location: Twente
Contact:

Handy functions for dzVents

Post by HvdW »

I used to 'C', so I missed the IFF(this,then that,else) in dzVents
Here's mine:

Code: Select all

        IIF = function(condition, trueValue, falseValue)
            if condition then
                return trueValue
            else
                return falseValue
            end
        end,
The function is placed in the global_data script under helpers.
To call the function (example)

Code: Select all

domoticz.helpers.IIF(mySwitch.state == 'On', otherSwitch.switchOn(),otherSwitch.switchOff() )
Like the example in Quickstart in the dzVents WiKi.

There is a quick sleep(seconds) function as well:

Code: Select all

        sleep = function(seconds)
            os.execute("sleep " .. tonumber(seconds))
        end,
Called with

Code: Select all

domoticz.helpers.sleep(3)
The sleep() command produces some kind of error when time is set to >= 7 seconds.
The log displays

Code: Select all

Finished Test Script after >7 seconds. (using 0.040 seconds CPU time !)
Bugs bug me.
User avatar
waltervl
Posts: 5711
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2024.7
Location: NL
Contact:

Re: Handy functions for dzVents

Post by waltervl »

Never use sleep in a dzvents script! It will halt dzvents and other Domoticz executions.

That is also why we have the asynchronous os execute function in dzvents to prevent that these os executions halt Domoticz unexpectedly.
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
User avatar
RonkA
Posts: 100
Joined: Tuesday 14 June 2022 12:57
Target OS: NAS (Synology & others)
Domoticz version: 2025.1
Location: Harlingen
Contact:

Re: Handy functions for dzVents

Post by RonkA »

Nice little global_data knowledge nugget, thanks for that.

This has the potential to shorten my scripts a lot by centralizing all the same functions that are used in multiple scripts..

These type of handy uses of dzVents feel buried in the wiki and are not easily found and understand by people that have limited coding skills like me..
SolarEdge ModbusTCP - Kaku - Synology NAS - Watermeter - ESPEasy - DS18b20
Work in progress = Life in general..
HvdW
Posts: 591
Joined: Sunday 01 November 2015 22:45
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.2
Location: Twente
Contact:

Re: Handy functions for dzVents

Post by HvdW »

spaces() is a function to help you align data when writing to a Text Sensor

Code: Select all

-- Helper functions
local function spaces(count)
    return string.rep(" ", count)
end

local function round(num, decimals)
    local mult = 10^(decimals or 0)
    return math.floor(num * mult + 0.5) / mult
end
Here is an example how to use it, plus it is a structured way to fill a Text Sensor with text

Code: Select all

        -- Create or update text sensor
        local displayText = string.format(
            "Solar Power: %d\n" ..
            "Power Return: %d\n" ..
            "Power Consumption: %d\n" ..
            "Power Available: %d\n" ..
            "Battery Level: %d\n" ..
            spaces(15) .. "Battery Set: %d\n" ..
            spaces(15) .. "EVSE Switch: %s\n" ..
            spaces(15) .. "Charging Level: %s\n",
            solarPower,
            PowerReturn,
            PowerConsumption,
            availableEnergy,
            chargeLevelActual,
            chargeLevelSet,
            EVSwitch_16A.state,
            ChargingSwitchState
        )
        domoticz.devices('Car Charging Actual').updateText(displayText)
 
Bugs bug me.
HvdW
Posts: 591
Joined: Sunday 01 November 2015 22:45
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.2
Location: Twente
Contact:

Re: Handy functions for dzVents

Post by HvdW »

Here are some other dzVents examples for your code

Code: Select all

local message = (temperature > 30) and "It is very warm!" or "It is cool."
which replaces

Code: Select all

if temperature > 30 then
	local message =  "It is very warm!"
else
	local message =  "It is very warm!"
end
or

Code: Select all

local result = (a > b) and "greater" or "less or equal"
Happy programming.
Bugs bug me.
HvdW
Posts: 591
Joined: Sunday 01 November 2015 22:45
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.2
Location: Twente
Contact:

Re: Handy functions for dzVents

Post by HvdW »

Having answered in this topic I have been thinking about documentation of my scripts.
So I asked DeepSeek to have a look at my Car Charging on EV Power script and here is the result.

Code: Select all

--[[
    Script Name: Car Charging on PV Power
    Description: This script manages the charging of an electric vehicle (EV) using surplus solar power (PV).
                 It dynamically adjusts the charging level based on available solar energy and ensures that
                 the EV charger is only active when the car is connected and there is sufficient solar power.
                 The script also prevents unintended charging by turning off the charger when the car is
                 disconnected or when the battery reaches the desired charge level.

    Key Features:
    - Adjusts charging level based on available solar power.
    - Turns off the charger when the car is disconnected.
    - Prevents charging when the battery reaches the desired level.
    - Logs and notifies charging status changes.

    Devices Involved:
    - 'Zonnepanelen': Solar power generation (WhActual).
    - 'Charging Level': Selector switch for setting the charging level (6A, 8A, 10A, 13A, 16A, Off).
    - 'EVSE Switch 16A': Main switch for enabling/disabling the EV charger.
    - 'XC40 battery charge set': Desired battery charge level (percentage).
    - 'XC40-ChargeLevel': Current battery charge level (percentage).
    - 'Power': Device providing power consumption and return data.
    - 'Auto laden': Device for handling EVSE communication errors.
    - 'Car Charging Actual': Text sensor for displaying charging status and power data.

    Global Data:
    - EVSE_Connected: Indicates whether the car is connected to the charger.
    - EVSE_CommunicationError: Indicates if there is a communication error with the EVSE.

    Logic:
    - The script checks the available solar power and adjusts the charging level accordingly.
    - If the car is disconnected, the charger is turned off.
    - If the battery reaches the desired charge level, the charger is turned off.
    - Notifications are sent when charging starts or stops.

    Notes:
    - The script runs every minute to ensure timely adjustments.
    - Human intervention may be required if the EVSE_Connected status lags when connecting the car.
]]--
So from now on I'll strive to document the scripts as well as the sensors in the descriptions.
edit.jpg
edit.jpg (41.52 KiB) Viewed 526 times
Bugs bug me.
HvdW
Posts: 591
Joined: Sunday 01 November 2015 22:45
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.2
Location: Twente
Contact:

Re: Handy functions for dzVents

Post by HvdW »

I have overhauled the car charging script.
My personal script uses data from the Volvo plugin.
This simplified version relies on just the Power data from P1, a selector Switch and has a text sensor output which gives you insight in the charging situation.
car charging.png
car charging.png (9.2 KiB) Viewed 246 times
The nice thing about this version is that it has an adaptable buffer for switching.
This is to prevent selecting another level too often when the solar power changes.

Code: Select all

-- Helper functions
local function spaces(count)
    return string.rep(" ", count)
end

return {
    on = {   
        timer = {'every 1 minutes', },
    },

    logging = {
        level = domoticz.LOG_DEBUG,
        marker = 'Simple EV Charging on House Power'
    },

    data = {
        desiredLevel = { initial = 'Off' },
        powerLevels = { initial = {
            ['16A'] = 3700,
            ['13A'] = 3000,
            ['10A'] = 2300,
            ['8A'] = 1850,
            ['6A'] = 1400,
            Off = 0
        }},
        upBuffer = { initial = 200 },    -- Buffer for switching up
        downBuffer = { initial = 200 },  -- Buffer for switching down
    },

    execute = function(domoticz, triggeredItem)
        
        -- Start de timer om de uitvoeringstijd te meten
        local startTime = os.clock()
        
        -- Basisgegevens ophalen
        local ChargingSwitchState = domoticz.devices('Charging Level').levelName
        local EVSE_Connected = domoticz.globalData.EVSE_Connected
        local newLevel = 'Off'
        
        -- Huidig vermogen van de laadpaal bepalen
        local PowerLaadpaal = domoticz.data.powerLevels[ChargingSwitchState] or 0
        
        -- Power gegevens ophalen
        local PowerReturn = tonumber(domoticz.devices('Power').rawData[6]) -- Terug naar het net
        local PowerConsumption = tonumber(domoticz.devices('Power').rawData[5]) -- Verbruik
        local availableEnergy = PowerLaadpaal + PowerReturn - PowerConsumption
        
        -- Logging
        domoticz.log('#1 EVSE connected: ' .. domoticz.globalData.EVSE_Connected, domoticz.LOG_DEBUG)
        domoticz.log('#2 Energy usage from the network: ' .. PowerConsumption, domoticz.LOG_DEBUG)
        domoticz.log('#3 Energy return to the network: ' .. PowerReturn, domoticz.LOG_DEBUG)
        domoticz.log('#4 Actual power on laadpaal: ' .. PowerLaadpaal, domoticz.LOG_DEBUG)
        domoticz.log('#5 Available energy: ' .. availableEnergy, domoticz.LOG_DEBUG)

        -- Auto is niet aangesloten, zorg dat laadniveau uit staat
        if EVSE_Connected ~= 'Connected' then
            if domoticz.devices('Charging Level').levelName ~= 'Off' then
                domoticz.devices('Charging Level').switchSelector('Off')
                domoticz.log('#6 Charging Level switched Off because car is no longer connected', domoticz.LOG_DEBUG)
            end
        end
        
        -- Alleen uitvoeren als de auto is aangesloten
        if EVSE_Connected == 'Connected' then
            -- Bepaal het gewenste laadniveau op basis van beschikbare energie
            local currentLevel = domoticz.devices('Charging Level').levelName
            domoticz.log('#7 currentLevel : ' .. currentLevel, domoticz.LOG_DEBUG)
            
            -- Get current power level
            local currentLevel = domoticz.devices('Charging Level').levelName
            local currentPower = domoticz.data.powerLevels[currentLevel] or 0

            -- Kies het juiste niveau met buffer
            newLevel = currentLevel -- Start with current level as default

            -- Sort power levels for ordered comparison
            local sortedLevels = {}
            for level, power in pairs(domoticz.data.powerLevels) do
                if level ~= 'Off' then
                    table.insert(sortedLevels, {level = level, power = power})
                end
            end
            table.sort(sortedLevels, function(a, b) return a.power < b.power end)

            -- Check if we need to switch down
            if currentLevel ~= 'Off' then
                local needLowerLevel = true
                for i, levelData in ipairs(sortedLevels) do
                    if levelData.level == currentLevel then
                        -- Check if current level is still acceptable with downBuffer
                        if availableEnergy >= (levelData.power - domoticz.data.downBuffer) then
                            needLowerLevel = false
                            break
                        end
                    end
                end
                
                if needLowerLevel then
                    -- Find the highest level that fits with available energy
                    newLevel = 'Off'
                    for i, levelData in ipairs(sortedLevels) do
                        if availableEnergy >= levelData.power then
                            newLevel = levelData.level
                        end
                    end
                end
            end

            -- Check if we can switch up
            if currentLevel == 'Off' then
                -- If currently off, use normal threshold to turn on
                for i, levelData in ipairs(sortedLevels) do
                    if availableEnergy >= levelData.power then
                        newLevel = levelData.level
                    end
                end
            else
                -- If already on, check if we can go higher with upBuffer
                for i = #sortedLevels, 1, -1 do
                    local levelData = sortedLevels[i]
                    if levelData.power > currentPower and availableEnergy >= (levelData.power + domoticz.data.upBuffer) then
                        newLevel = levelData.level
                        break
                    end
                end
            end
            domoticz.log('#8 newLevel : ' .. newLevel, domoticz.LOG_DEBUG)

            -- Update het laadniveau als het anders is
            if domoticz.devices('Charging Level').levelName ~= newLevel then
                domoticz.devices('Charging Level').switchSelector(newLevel)
                domoticz.log('#9 Updated charging level to: ' .. newLevel, domoticz.LOG_DEBUG)
            end

            -- Stuur alleen een notificatie als het niveau is veranderd
            if domoticz.devices('Charging Level').levelName ~= currentLevel then
                local current_time = os.date('%d-%m-%Y %H:%M')
                local subject, message
                if newLevel == 'Off' and currentLevel ~= 'Off' then 
                    subject = '@2 Charging ended'
                    message = newLevel ..', Energy : ' .. availableEnergy .. '\nTime : ' .. current_time
                    domoticz.notify(subject, message, domoticz.PRIORITY_NORMAL)
                    domoticz.log('#10 Notification sent: ' .. subject, domoticz.LOG_DEBUG)
                elseif newLevel ~= 'Off' and (currentLevel == 'Off' or currentLevel ~= newLevel) then
                    subject = '@3 Charging'
                    message = newLevel .. ', Energy : ' .. availableEnergy .. '\nTime : ' .. current_time
                    domoticz.notify(subject, message, domoticz.PRIORITY_NORMAL)
                    domoticz.log('#11 Notification sent: ' .. subject, domoticz.LOG_DEBUG)
                end
            end
        else
            domoticz.log('#12 Car not connected. Script not controlling charging.', domoticz.LOG_DEBUG)
        end

        -- Tekst sensor bijwerken
        local modeText = "Automatic"

        -- Determine Home Power (Usage or Return)
        local homePowerText
        if PowerConsumption > 0 then
            homePowerText = string.format("Home Power Usage: %d W", PowerConsumption)
        else
            homePowerText = string.format("Home Power Return: %d W", PowerReturn)
        end

        -- Get the theoretical power and Amps for the current charging level
        local levelText
        local currentLevel = domoticz.devices('Charging Level').levelName
        if currentLevel == 'Off' then
            levelText = "Off"  -- Just 'Off' without the 'Level:' prefix
        else
            local theoreticalPower = domoticz.data.powerLevels[currentLevel]
            levelText = string.format("%d W (%s)", theoreticalPower, currentLevel)
        end

        -- Format the display text
        local displayText = string.format(
            "Control Mode: %s\n" ..
            "%s\n" ..
            "Available Power: %d W\n" ..
            "Charging Level: %s",
            modeText,
            homePowerText,
            availableEnergy,
            levelText  -- Now just the value (e.g., 'Off' or '2300 W (10A)')
        )

        -- Update the text sensor
        domoticz.devices('Car Charging Actual').updateText(displayText)

        -- Stop de timer en bereken de uitvoeringstijd
        local executionTime = os.clock() - startTime
        -- Voeg de uitvoeringstijd toe aan scriptExecutionTime_car_charge
        domoticz.globalData.scriptExecutionTime_car_charge.add(executionTime)
        -- Log de uitvoeringstijd
        domoticz.log('Uitvoeringstijd van het Simple EV Charging on House Power script: ' .. executionTime .. ' seconden', domoticz.LOG_DEBUG)
    end
}
The switching logic for the selector relies on this script

Code: Select all

return {
    on = {
        devices = {'Charging Level'},
    },
    logging = {
        level = domoticz.LOG_ERROR,
        marker = '---- Charging Level Selector Switch -----',
    },
    execute = function(domoticz, device)
        local level = device.level
        domoticz.log('#1 Current charging level: ' .. level, domoticz.LOG_DEBUG)
        
        local powerLevels = {
            [0] = { name = 'Off', current = 0, mode = 'Off' },
            [10] = { name = '6A', current = 60, mode = 'Normal' },
            [20] = { name = '8A', current = 80, mode = 'Normal' },
            [30] = { name = '10A', current = 100, mode = 'Normal' },
            [40] = { name = '13A', current = 130, mode = 'Normal' },
            [50] = { name = '16A', current = 160, mode = 'Normal' }
        }
        
        local levelSettings = powerLevels[level]
        if not levelSettings then
            domoticz.log('Unknown charging level: ' .. level, domoticz.LOG_ERROR)
            return
        end
        
        -- MQTT broker settings
        local broker = "192.168.2.104"
        
        -- Send current override command
        domoticz.executeShellCommand({
            command = 'mosquitto_pub -h ' .. broker .. ' -t "SmartEVSE/Set/CurrentOverride" -m ' .. levelSettings.current,
            callback = function(exitStatus, output)
                if exitStatus ~= 0 then
                    domoticz.log('Failed to set current: ' .. tostring(output), domoticz.LOG_ERROR)
                end
            end
        })
        
        -- Send mode command
        domoticz.executeShellCommand({
            command = 'mosquitto_pub -h ' .. broker .. ' -t "SmartEVSE/Set/Mode" -m ' .. levelSettings.mode,
            callback = function(exitStatus, output)
                if exitStatus ~= 0 then
                    domoticz.log('Failed to set mode: ' .. tostring(output), domoticz.LOG_ERROR)
                end
            end
        })
        
        -- Set LED color only if charging is turned off
        if levelSettings.mode == 'Off' then
            domoticz.executeShellCommand({
                command = 'mosquitto_pub -h ' .. broker .. ' -t "SmartEVSE/Set/ColorOff" -m 0,250,0',
                callback = function(exitStatus, output)
                    if exitStatus ~= 0 then
                        domoticz.log('Failed to set LED color: ' .. tostring(output), domoticz.LOG_ERROR)
                    end
                end
            })
        end
        
        domoticz.log('Charging set to: ' .. levelSettings.name, domoticz.LOG_DEBUG)
    end
}
It uses MQTT to switch a SmartEVSE switch.
See how it handles available power.
Usage-return.png
Usage-return.png (56.75 KiB) Viewed 119 times
Power
PV power.png
PV power.png (34.74 KiB) Viewed 118 times
PV Power
Bugs bug me.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest