[DzVents] Store et brise-soleil

Vous avez créé un script LUA dont vous êtes fier, un .sh génial, un programme Python hors du commun, un Tuto, c'est ici que vous pouvez les partager.
Soyez précis quant aux prérequis, les manips à faire pour que votre bijou fonctionne (des chmod ?, un apt-get à faire ...)
Décrivez précisément son fonctionnement.
Merci d'utiliser la balise correspondante à votre sujet : [Tuto], [Plugin], [DzVents], [LUA], [Python], [Bash] ...
Entourez votre code et les logs avec les balises nommées code grâce au bouton <\>.
Répondre
hestia
Messages : 245
Enregistré le : 12 sept. 2018, 22:36

[DzVents] Store et brise-soleil

Message par hestia »

Voici un script qui permet de bouger automatiquement les brises-soleil, stores, volets (dans la suite, j'utilise store dans tous les cas), en fonction:
- de la température extérieure,
- de la position du soleil et donc aussi "jour" et "nuit",
- de l'heure.

Il y a 2 modes: Automatique et Manuel
- Manuel est le mode du bouton normal, pris en compte pour savoir si l'on est en automatique ou non
- Automatique: le store bouge tout seul quelque soit l'heure et la saison

Automatique, il y a 3 programmes qui se changent automatiquement
- Cold: en dessous d'une certaine température extérieure: on ferme le store la nuit pour garder la chaleur et on l'ouvre le jour pour y voir et recevoir la chaleur du soleil ; j'ai mis 20°C
- Hot: quand il y a beaucoup de soleil, on ouvre le store la nuit pour refroidir et le jour on suit le soleil pour fermer le store au fur et à mesure que le soleil monte (pour les brises-soleil, car pour un store sans lame, c'est tout ou rien).
J'ai pris la température extérieure comme critère en attendant de pouvoir mesurer la radiation solaire avec une bonne réactivité ; j'ai mis 24°C (mais ça dépend d'où est le thermomètre ; dans mon cas, il est très à l'ombre)
- Warm: ni Cold, ni Hot: on laisse le store ouvert tout le temps
Voici le script

Code : Tout sélectionner

--[[
A dz dzVents Script to close or open blinds or shutters regarding outside information: light, temperature, solar posiiton and time
In this script, it could be vertical shutters or blinds like roller shades (w/o slates) or fix shutters or blinds with slates ("brise-soleil")
For brise-soleil, the slates follow the sun to get just the shade needed
In the following I'm going to use the word blind only for all cases
It is possible to declare several blinds in the same script with a different configuration for each ones

tips to setup the selector (BLIND_ID)
10: &#9650  => UP
20: &#9724  => STOP
30: &#9660  => DOWN
40: A       => Auto

05/04/2020 - new!
07/02/2021 - clarify log if newBlindProgram is empty
04/04/2021 - add new type of device for SUN_CRITERIA (temperature, lux, percentage)
04/06/2021 - round angle in the device name / fix lastSlatAngle init / fix rename conditions
06/07/2021 - logging precision
08/08/2021 - bug fixed, changed lastBlindProgram to newBlindProgram (one occurence forgotten in Auto mode)
11/08/2021 - clarify log
17/08/2021 - the sun radiation (lux or W/m2) is considered on the floor (horizontal projection of the sun radiation or measured on an horizontal surface)
                for a whole house what matter is more the whole radiation than only the horizontal one
                particularly when the sun is low and vertical windows are considered
                to have a better criteria for the impact of the sun on a house, the whole value is used (projection on the floor removed)
19/05/2022 - adjust the rounded of the movement to avoid a line of sun when the brise-soleil are closing
22/05/2022 - separate sun radiation and max outside temperature to combine them
06/06/2022 - fix bugs where blinds are mostly on east and west => refactoring some old parts done for vertical windows using new parts (see 17/08/2021)
                Breaking change BLIND_AZIMUTH is now the Azimuth of the normal of the area
12/06/2022 - fix "nil value (local 'P_SlatAngleTarget'"
14/06/2022 - idem
17/06/2022 - remove cosIncidence from the sunRadiationValue: thru a glass at any inclination, the sun strike a body the same?
            
next?      - add HOT+ program: shut the blinds if very hot and the sun strikes the blind


prerequisite: dzVents Version: 3.0.2
3 sensors: 
SOLAR_ALTITUDE  Device that gives Solar Altitude see https://www.domoticz.com/wiki/index.php?title=Lua_dzVents_-_Solar_Data:_Azimuth_Altitude_Lux
SOLAR_AZIMUTH   Device that gives Solar Azimuth see https://www.domoticz.com/wiki/index.php?title=Lua_dzVents_-_Solar_Data:_Azimuth_Altitude_Lux
OUTSIDE_TEMP    Device that gives Outside temperature
SUN_RADIATION   1 or 2 devices that gives the value of the outside radiation to determine if the blind must be closed because it is too sunny
    (lux or W/m2)
SUN_RADIATION: 1 or 2 devices to determine if the blind must be closed (according to the sun position) because it is too sunny
It could be solar radiation in lux, or solar radiation in W/m2
There could be 1 or 2 devices
If 1 device, it is a total radiation value (usually on the ground)
If 2 devices, the SUN_RADIATION1 is the direct radiation and SUN_RADIATION2 is the scattered (or diffuse) radiation
and the total radiation is calculated on the surface of the blind with the angle of this surface and the sun
    
At the first use or after any change of the script name, to click Auto mode to initiate "data"
--]]

-- log options
local LOG_DEBUG = 0         -- 0 =>ERROR / 1 => FORCE / 2 => DEBUG
local LOG_LEVEL
local LOGGING
if LOG_DEBUG == 2 then
    LOGGING = domoticz.LOG_DEBUG
    LOG_LEVEL = domoticz.LOG_DEBUG
elseif LOG_DEBUG == 1 then
    LOGGING = domoticz.LOG_FORCE
    LOG_LEVEL = domoticz.LOG_FORCE
else
    LOGGING = domoticz.LOG_ERROR
    LOG_LEVEL = domoticz.LOG_INFO
end


local ALL_MAX_SUN_RADIATION = 400
local ALL_MAX_OUTSIDE_TEMP = 21
local ALL_XXL_OUTSIDE_TEMP = 30 -- future version...
local ALL_MIN_OUTSIDE_TEMP = 20

local BLINDS = 
    {   [1203] =                            -- Dummy device with Automatics command (to create) (id or 'name')
            {
            ['BLIND_TYPE'] = 'V',           -- "B" Brise Soleil or "V" Vertical
            ['BLIND_NAME'] = 'Store',-- Name of the dummy device
            ['BLIND_ID'] = 953,             -- Device of the BLIND to close or open (id or 'name') 
            ['BLIND_AZIMUTH'] = 289,        -- Angle from the projection on the ground of the normal of the blind and the North (Azimuth of the normal of the area)
            ['SOLAR_ALTITUDE_MIN'] = 10,    -- Solar Altitude where the sun is hidden by a house, a tree, if nothing 0    
            ['MAX_SUN_RADIATION'] = ALL_MAX_SUN_RADIATION,   -- If radiation is more than this max, the blind is closed during the day and according to the sun position (HOT)
            ['MAX_OUTSIDE_TEMP'] = ALL_MAX_OUTSIDE_TEMP,   -- If temperature is more than this max, the blind is closed during the day and according to the sun position (HOT)
            ['MIN_OUTSIDE_TEMP']  = ALL_MIN_OUTSIDE_TEMP,     -- If outside temperature in less than this min temp, the blind is closed night (COLD) 
            ['SLAT_DOWN_ANGLE']  = -90,     -- Angle from the horizontal when the slat is down (<0), -90 for vertical blind
            ['SLAT_UP_ANGLE'] = 90,         -- Angle from the horizontal when the slat is up, 90 for vertical blind
            ['SLAT_LENGHT']  = '',          -- Length of the slate, any unit (cm...), nil for vertical blind
            ['SLAT_DISTANCE']  = '',        -- Distance between slates, any unit (cm...), nil for vertical blind
            ['BLIND_ANGLE']  = 90,          -- Angle of the blind from the horizontal (>0)
            ['TTC_SLAT_SEC'] = 1            -- Time To Close the blind in seconds, to be measured, 1 for vertical blind
            },
        [1122] =                             -- Dummy device with Automatics command (to create) (id or 'name')
            {
            ['BLIND_TYPE'] = 'B',           -- "B" Brise Soleil or "V" Vertical
            ['BLIND_NAME'] = 'Brise Soleil',-- Name of the dummy device
            ['BLIND_ID'] = 673,             -- Device of the BLIND to close or open (id or 'name') 
            ['BLIND_AZIMUTH'] = 289,        -- Angle from the projection on the ground of the normal of the blind and the North (Azimuth of the normal of the area)
            ['SOLAR_ALTITUDE_MIN'] = 15,   -- 20 pour relever les lattes -- Solar Altitude where the sun is hidden by a house, a tree, if nothing 0    
            ['MAX_SUN_RADIATION'] = ALL_MAX_SUN_RADIATION,   -- If radiation is more than this max, the blind is closed during the day and according to the sun position (HOT)
            ['MAX_OUTSIDE_TEMP'] = ALL_MAX_OUTSIDE_TEMP,   -- If temperature is more than this max, the blind is closed during the day and according to the sun position (HOT)
            ['MIN_OUTSIDE_TEMP']  = ALL_MIN_OUTSIDE_TEMP,     -- If outside temperature in less than this min temp, the blind is closed night (COLD) 
            ['SLAT_DOWN_ANGLE']  = -4,      -- Angle from the horizontal when the slat is down (<0), -90 for vertical blind
            ['SLAT_UP_ANGLE'] = 55,         -- Angle from the horizontal when the slat is up, 90 for vertical blind
            ['SLAT_LENGHT']  = 8,           -- Length of the slate, any unit (cm...), nil for vertical blind
            ['SLAT_DISTANCE']  = 7,         -- Distance between slates, any unit (cm...), nil for vertical blind
            ['BLIND_ANGLE']  = 16,          -- Angle of the blind from the horizontal (>0)
            ['TTC_SLAT_SEC'] = 16           -- Time To Close the blind in seconds, to be measured, 1 for vertical blind
            }
    }
--[[            ,
        [2618] =                             -- Dummy device with Automatics command (to create) (id or 'name')
            {
            ['BLIND_TYPE'] = 'V',           -- "B" Brise Soleil or "V" Vertical
            ['BLIND_NAME'] = 'Rideau Studio',-- Name of the dummy device
            ['BLIND_ID'] = 205,             -- Device of the BLIND to close or open (id or 'name') 
            ['BLIND_AZIMUTH'] = 19,         -- Angle from the projection on the ground of the normal of the blind and the North (Azimuth of the normal of the area)
            ['SOLAR_ALTITUDE_MIN'] = 10,    -- Solar Altitude where the sun is hidden by a house, a tree, if nothing 0    
            ['MAX_SUN_RADIATION'] = 200,   -- If radiation is more than this max, the blind is closed during the day and according to the sun position (HOT)
            ['MAX_OUTSIDE_TEMP'] = 10,   -- If temperature is more than this max, the blind is closed during the day and according to the sun position (HOT)
            ['MIN_OUTSIDE_TEMP']  = 10,     -- If outside temperature in less than this min temp, the blind is closed night (COLD) 
            ['SLAT_DOWN_ANGLE']  = -90,     -- Angle from the horizontal when the slat is down (<0), -90 for vertical blind
            ['SLAT_UP_ANGLE'] = 90,         -- Angle from the horizontal when the slat is up, 90 for vertical blind
            ['SLAT_LENGHT']  = '',          -- Length of the slate, any unit (cm...), nil for vertical blind
            ['SLAT_DISTANCE']  = '',        -- Distance between slates, any unit (cm...), nil for vertical blind
            ['BLIND_ANGLE']  = 90,          -- Angle of the blind from the horizontal (>0)
            ['TTC_SLAT_SEC'] = 1            -- Time To Close the blind in seconds, to be measured, 1 for vertical blind
            },
        [2619] =                             -- Dummy device with Automatics command (to create) (id or 'name')
            {
            ['BLIND_TYPE'] = 'V',           -- "B" Brise Soleil or "V" Vertical
            ['BLIND_NAME'] = 'Rideau Chambre',-- Name of the dummy device
            ['BLIND_ID'] = 206,             -- Device of the BLIND to close or open (id or 'name') 
            ['BLIND_AZIMUTH'] = 199,        -- Angle from the projection on the ground of the normal of the blind and the North (Azimuth of the normal of the area
            ['SOLAR_ALTITUDE_MIN'] = 10,    -- Solar Altitude where the sun is hidden by a house, a tree, if nothing 0    
            ['MAX_SUN_RADIATION'] = 200,   -- If radiation is more than this max, the blind is closed during the day and according to the sun position (HOT)
            ['MAX_OUTSIDE_TEMP'] = 10,   -- If temperature is more than this max, the blind is closed during the day and according to the sun position (HOT)
            ['MIN_OUTSIDE_TEMP']  = 10,     -- If outside temperature in less than this min temp, the blind is closed night (COLD) 
            ['SLAT_DOWN_ANGLE']  = -90,     -- Angle from the horizontal when the slat is down (<0), -90 for vertical blind
            ['SLAT_UP_ANGLE'] = 90,         -- Angle from the horizontal when the slat is up, 90 for vertical blind
            ['SLAT_LENGHT']  = '',          -- Length of the slate, any unit (cm...), nil for vertical blind
            ['SLAT_DISTANCE']  = '',        -- Distance between slates, any unit (cm...), nil for vertical blind
            ['BLIND_ANGLE']  = 90,          -- Angle of the blind from the horizontal (>0)
            ['TTC_SLAT_SEC'] = 1            -- Time To Close the blind in seconds, to be measured, 1 for vertical blind
            }
    }
--]]

local TEST = 203  -- a dummy switch for testing w/o waiting minutes / remove comment to use / comment to ignore

local SOLAR_ALTITUDE = 338      -- Device that give Solar Altitude
local SOLAR_AZIMUTH = 337       -- Device that give Solar Azimuth

local OUTSIDE_TEMP = 2560        -- Device that give Outside temperature
local OUTSIDE_TEMP_MARGING = .3 -- Marging to have an hysteresis to change mode or program to avoid multiple changes when few changes of temp

local SUN_RADIATION1 = 1914 -- total radiation if 1 device, direct radiation if 2 devices
local SUN_RADIATION2 = 1915 -- if the first is the direct radiation, this one is the scattered radiation
local SUN_RADIATION_MARGING = 20 -- Marging to have an hysteresis to change mode or program to avoid multiple changes when few changes of the value

local DEVICES_TRIGGER = {
        OUTSIDE_TEMP, 
        SUN_RADIATION1,
        TEST
    } -- SOLAR_AZIMUTH is updated at the same time as SUN_RADIATION1

-- ... next add the BLINDS definitions
for blindId, _ in pairs(BLINDS) do
    --print('STORES insert')
    table.insert( DEVICES_TRIGGER, blindId )
end

--local DEVICES_TRIGGER = {1203, 1122, 2618, 2619, OUTSIDE_TEMP, SUN_RADIATION1, TEST} -- SOLAR_AZIMUTH is updated at the same time as SUN_RADIATION

--local TIME_INTERVAL = 'every 5 minutes'


return {
    logging =   {
                level =   LOGGING
                },
    on      =   {   devices =   DEVICES_TRIGGER,
                    --timer   =   {TIME_INTERVAL} -- trigger to choose
        },
                        
    data    =   {   slatAngle =         { initial = {}},    -- Last angle of the slate
                    lastMaxSlatAngle =  { initial = {}},    -- Last max angle of the slate to kwnow if it goes up or down -- 19/05/2022
                    blindMode =         { initial = {}},    -- Last mode of the blind Manual, Auto
                    blindProgram =      { initial = {}}     -- Last program of the blind Cold, Warm, Hot
        },
        
	execute = function(dz, item, triggerInfo)
        
        _G.logMarker =  dz.moduleLabel -- set logmarker to scriptname 
    	local _u =  dz.utils

    -- /// Functions start \\\

    local function logWrite(str,level)  -- Support function for shorthand debug log statements
        if level == nil then
            level = LOG_LEVEL
        end
        dz.log(tostring(str),level)
    end
    
        
    local function CalculateSunIncidence(P_SolarAzimuth, P_SolarAltitude, P_AreaAzimuth, P_AreaInclination)
        --logWrite('CalculateSunIncidence')
        -- Calculate the sun incidence (the cosinuss) on a tilt surface

        --P_SolarAzimuth: azimuth of the sun in degrees (from the Noth)
        --P_SolarAltitude: elevation of the sun or altitude in degrees
        --P_AreaAzimuth: azimuth of the normale surface in degrees (from the Noth)
        --P_AreaInclination: inclination of the surface (from the horizontal)

        logWrite('P_SolarAltitude: ' .. P_SolarAltitude)
        logWrite('P_SolarAzimuth: ' .. P_SolarAzimuth)
        logWrite('P_AreaInclination: ' .. P_AreaInclination)
        logWrite('P_AreaAzimuth: ' .. P_AreaAzimuth)
            
        local F_cosIncidence
        F_cosIncidence = math.cos(math.rad(P_SolarAltitude)) * math.sin(math.rad(P_AreaInclination)) * math.cos(math.rad(P_AreaAzimuth - P_SolarAzimuth))
                    + math.sin(math.rad(P_SolarAltitude)) * math.cos(math.rad(P_AreaInclination))
        F_cosIncidence = _u.round(F_cosIncidence, 3)
        
        --logWrite('F_cosIncidence: ' .. F_cosIncidence)
        local F_Incidence = math.deg(math.acos(F_cosIncidence))
        --logWrite('F_Incidence: ' .. F_Incidence)
        
        if F_cosIncidence < 0 then
            logWrite('The sun is behind, cos incidence=' .. F_cosIncidence)
            F_cosIncidence = 0
        end

        logWrite('SolarAltitude: ' .. P_SolarAltitude .. ' SolarAzimuth: ' .. P_SolarAzimuth
            .. ' AreaInclination: ' .. P_AreaInclination .. ' AreaAzimuth: ' .. P_AreaAzimuth
            .. ' CosIncidence: ' .. F_cosIncidence .. ' ' .. ' Incidence: ' .. _u.round(F_Incidence,0) .. '°')
        return F_cosIncidence
    end
    

    local function GetSunRadiation(P_SolarAzimuth, P_SolarAltitude, P_AreaAzimuth, P_AreaInclination)
        -- Get the sun criteria values
        local F_dev_sunRadiation = dz.devices(SUN_RADIATION1)
        local F_sunRadiationValue
        --logWrite('sunRadiationValue ' .. F_dev_sunRadiation.name .. ' type ' .. F_dev_sunRadiation.deviceType .. ' s/type ' .. F_dev_sunRadiation.deviceSubType)
   
        local F_cosIncidence = CalculateSunIncidence(P_SolarAzimuth, P_SolarAltitude, P_AreaAzimuth, P_AreaInclination)
            
        if F_dev_sunRadiation.deviceSubType == 'Lux' then
            F_sunRadiationValue = F_dev_sunRadiation.lux
            
        elseif F_dev_sunRadiation.deviceSubType == 'Solar Radiation' then    
            local F_solarRadiation = F_dev_sunRadiation.radiation
            if SUN_RADIATION2 ~= nil then -- it is the scattered radiation and the previous was the direct
                local F_solarRadiationScattered = dz.devices(SUN_RADIATION2).radiation
                -- the total radation on the blind depends on the incidence of the sun
                -- F_cosIncidence = CalculateSunIncidence(P_SolarAzimuth, P_SolarAltitude, P_AreaAzimuth, P_AreaInclination) -- to remove 06/06/2022
                -- the direct radiation is projected on the incidence, the scattered (diffused) is in all directions
                
                --F_sunRadiationValue = math.max(_u.round(F_solarRadiation * F_cosIncidence + F_solarRadiationScattered, 0), 0)

                F_sunRadiationValue = math.max(_u.round(F_solarRadiation + F_solarRadiationScattered, 0), 0) -- 17/06/2022 -- thru a glass at any inclinaison, the sun strike a body the same?
            
            else -- it is the total radiation
                F_sunRadiationValue = F_solarRadiation
            end
            
        else
            logWrite(F_dev_sunRadiation.id .. ' ' .. F_dev_sunRadiation.name .. ' device Type not supported ' .. F_dev_sunRadiation.deviceType, dz.LOG_ERROR)
        end

        logWrite('sunRadiation ' .. F_sunRadiationValue, dz.LOG_DEBUG)
    
    return F_sunRadiationValue, F_cosIncidence
    end
 
    local function InitPosition(P_devBlind, P_SLAT_UP_ANGLE)
        P_devBlind.open().silent()
        return P_SLAT_UP_ANGLE
    end
    
    
    local function CalculateSlatAngle(P_SolarAzimuth, P_SolarAltitude, P_BlindAzimuth, P_SlatDownAngle, P_SlatUpAngle, P_SolarAltitudeMin, P_SLAT_LENGHT, P_SLAT_DISTANCE, P_BLIND_ANGLE, P_BLIND_TYPE, P_cosIncidence)
        -- calculate the angle the slat need to have to be against the sun
        -- normal to the projection of the sun ray on a plan perpendicular at the blind azimuth
        --
        logWrite('CalculateSlatAngle')
        logWrite('P_BLIND_TYPE ' .. P_BLIND_TYPE)
        logWrite('P_SolarAzimuth ' .. P_SolarAzimuth)
        logWrite('P_SolarAltitude ' .. P_SolarAltitude)
        logWrite('P_BlindAzimuth ' .. P_BlindAzimuth)
        logWrite('P_SolarAltitudeMin ' .. P_SolarAltitudeMin)
        logWrite('P_cosIncidence '  .. P_cosIncidence)
        --
        --local F_SunInTheFront = false
        local F_SlatAngle
        if P_SolarAltitude < P_SolarAltitudeMin then -- if the sun is low, open the slates
            F_SlatAngle = P_SlatUpAngle
            logWrite('F_SlatAnglee open as the sun is low :' .. F_SlatAngle, dz.LOG_FORCE)
        else
            if P_cosIncidence > 0 then 
                logWrite('Sun on the area', dz.LOG_FORCE)

                if P_BLIND_TYPE == "V" then -- vertical blind: open or closed
                    --if F_SunInTheFront then -- the slate should be closed enough not to let the sun shine in
                       F_SlatAngle = P_SlatDownAngle
                       logWrite('Blind closed: ' .. F_SlatAngle, dz.LOG_FORCE)
                    --else
                        --F_SlatAngle = P_SlatUpAngle
                        --logWrite('Blind open: ' .. F_SlatAngle)
                    --end
    
                elseif P_BLIND_TYPE == "B" then -- brise-soleil, the angle of slates is calculated to follow the sun
                
                
                    local F_IncidenceSunBlind = P_BlindAzimuth - P_SolarAzimuth -- angle of the sun on the front of the blind
                    local F_ProjectedSolarAltitude = math.deg(math.atan
        		                            (
    		                                math.tan(math.rad(P_SolarAltitude))
    		                                /math.cos(math.rad(F_IncidenceSunBlind))
    	                                    )    
                                                            )

                    if F_ProjectedSolarAltitude > 0 then
                        logWrite('F_ProjectedSolarAltitude: ' .. F_ProjectedSolarAltitude)
                        logWrite('P_SlatDownAngle ' .. P_SlatDownAngle .. ' / P_SlatUpAngle ' .. P_SlatUpAngle)
                        local q =  math.tan(math.rad(F_ProjectedSolarAltitude + P_BLIND_ANGLE))
                        logWrite('q=' .. q)
                        --print('q ' .. q)
                        local a = (P_SLAT_DISTANCE / P_SLAT_LENGHT) + 1
                        --print('a '  .. a)
                        local b = 2 / q
                        --print('b ' .. b)
                        local c = (P_SLAT_DISTANCE / P_SLAT_LENGHT) - 1
                        --print('c ' .. c)
                        local d = b*b - 4*a*c
                        --print('d ' .. d)
                        local rac2d = math.sqrt(d)
                        --print('rac2d ' .. rac2d)
                        local t = (rac2d - b) / (2*a)
                        --print('t ' .. t)
                        local i =  2*math.deg(math.atan(t)) -- angle between the slat and the blind (roof)
                        --print('i ' .. i)
                        F_SlatAngle = i - P_BLIND_ANGLE
                        logWrite('F_SlatAngle therorical: ' .. F_SlatAngle)
        
                        -- the slate angle should be inside the capability of the blind
                        if F_SlatAngle > P_SlatUpAngle then
                            F_SlatAngle = P_SlatUpAngle
                        elseif F_SlatAngle < P_SlatDownAngle then
                            F_SlatAngle = P_SlatDownAngle
                        end
                        logWrite('Slat angle target: ' .. F_SlatAngle .. '° / Inclination from horizontal: ' .. (F_SlatAngle - P_BLIND_ANGLE) .. '°', dz.LOG_FORCE)
                    else
                        logWrite('F_ProjectedSolarAltitude: ' .. F_ProjectedSolarAltitude .. ' < 0 ; sun not on the side of the slats', dz.LOG_FORCE)
                        F_SlatAngle = P_SlatUpAngle -- 14/06/2022
                    end
                    
                else
                    logWrite("BLIND_TYPE unknown " .. P_BLIND_TYPE, dz.LOG_ERROR)
                    return(0)
                end
            else
                logWrite('Sun not on the area', dz.LOG_FORCE)     
                F_SlatAngle = P_SlatUpAngle -- 12/06/2022
            end

        end
        return(_u.round(F_SlatAngle,0))
    end
    
    
    local function CalculateSlatMvt(P_SlatAngleTarget, P_LastSlatAngle, P_lastMaxSlatAngle, P_SLAT_UP_ANGLE, P_SLAT_DOWN_ANGLE, P_SLAT_MARGING, P_TTC_SLAT_SEC)
    -- calculte the time is seconds to move the slates and the direction
        --[[
        logWrite('CalculateSlatMvt')
        logWrite('P_SlatAngleTarget '.. P_SlatAngleTarget )
        logWrite('P_LastSlatAngle ' .. P_LastSlatAngle )
        --]]
        -- if the slat target is near an edge it is moving to this edge
        local F_SlatMvt -- duration of the move in seconds
        local F_SlatAngleDelta
        if P_SlatAngleTarget == P_SLAT_UP_ANGLE and P_LastSlatAngle == P_SLAT_UP_ANGLE then -- already up
            F_SlatMvt = 0
            F_SlatAngleDelta = 0
            logWrite('P_SlatAngleTarget already to the max => F_SlatMvt ' .. F_SlatMvt)
        elseif P_SlatAngleTarget + P_SLAT_MARGING > P_SLAT_UP_ANGLE then -- up edge
            F_SlatMvt = 999
            F_SlatAngleDelta = 999
            logWrite('P_SlatAngleTarget near the up edge ' .. F_SlatMvt)
        elseif P_SlatAngleTarget == P_SLAT_DOWN_ANGLE and P_LastSlatAngle == P_SLAT_DOWN_ANGLE then -- already down
            F_SlatMvt = 0
            F_SlatAngleDelta = 0
            logWrite('P_SlatAngleTarget already to the min ' .. F_SlatMvt)
        elseif P_SlatAngleTarget - P_SLAT_MARGING <= P_SLAT_DOWN_ANGLE then -- down edge
            F_SlatMvt = -999
            F_SlatAngleDelta = -999
            logWrite('P_SlatAngleTarget near the down edge ' .. F_SlatMvt, dz.LOG_FORCE)
        else
            F_SlatAngleDelta = P_SlatAngleTarget - P_LastSlatAngle
            logWrite('=> P_SlatAngleTarget ' .. P_SlatAngleTarget, dz.LOG_FORCE)
            logWrite('P_LastSlatAngle ' .. P_LastSlatAngle, dz.LOG_FORCE)
            logWrite('F_SlatAngleDelta ' .. F_SlatAngleDelta, dz.LOG_FORCE)
            F_SlatMvt = P_TTC_SLAT_SEC * F_SlatAngleDelta / (P_SLAT_UP_ANGLE - P_SLAT_DOWN_ANGLE)
            logWrite('F_SlatMvt ' .. F_SlatMvt .. ' s', dz.LOG_FORCE)
            logWrite('=> P_lastMaxSlatAngle: ' .. P_lastMaxSlatAngle, dz.LOG_FORCE)
            if P_lastMaxSlatAngle == nil or P_SlatAngleTarget < P_lastMaxSlatAngle then -- 19/05/2022
                logWrite('=> The slates go down', dz.LOG_FORCE)
                F_SlatMvt = math.ceil(F_SlatMvt)
                --F_SlatMvt = math.floor(F_SlatMvt)
            else
                F_SlatMvt = math.floor(F_SlatMvt)
                --F_SlatMvt = math.ceil(F_SlatMvt)
                logWrite('The slates go up', dz.LOG_FORCE)
            end
            
            -- rounded toward the next move
            --[[
            if F_SlatMvt < 0 then
                F_SlatMvt = math.floor(F_SlatMvt)
            else
                F_SlatMvt = math.ceil(F_SlatMvt)
            end
            --]]
            F_SlatMvt = _u.round(F_SlatMvt)
            logWrite('F_SlatMvt ' .. F_SlatMvt .. ' s', dz.LOG_FORCE)
            F_SlatAngleDelta = F_SlatMvt * (P_SLAT_UP_ANGLE - P_SLAT_DOWN_ANGLE) / P_TTC_SLAT_SEC -- the "real" move
            logWrite('F_SlatAngleDelta ' .. F_SlatAngleDelta, dz.LOG_FORCE)
        end
    
        logWrite('F_SlatMvt ' .. F_SlatMvt)

        logWrite('F_SlatAngleDelta ' .. F_SlatAngleDelta)
        return F_SlatMvt, F_SlatAngleDelta
    end
    
    
    local function MoveSlat(P_SlatMvt, P_devBlind)
    -- move the slat the number of seconds in parameter, if > 0 to the open direction , if < 0 to the close direction
        logWrite('MoveSlate ' .. P_SlatMvt .. ' ' .. P_devBlind.name)
        if P_SlatMvt == 0 then
            logWrite('No need to move the blind ' .. P_SlatMvt .. ' sec')
        else
            if P_SlatMvt > 0 then
                --if P_SlatMvt < 999 then
                    --P_devBlind.open().forSec(P_SlatMvt).silent()
                    logWrite('Blind opens for ' .. P_SlatMvt .. ' seconds')
                --else
                    P_devBlind.open().silent()
                    --logWrite('Blind opens to the max')
                --end
            else
                --if P_SlatMvt > -999 then
                    logWrite('Blind closes for ' .. P_SlatMvt .. ' seconds')
                --else
                    P_devBlind.close().silent()
                    --logWrite('Blind closes ')
                --end
                P_SlatMvt = - P_SlatMvt 
                logWrite('Blind closes ' .. P_SlatMvt .. ' seconds')
            end
            
            if math.abs(P_SlatMvt) ~= 999 then -- it is not an edge position
                P_devBlind.stop().afterSec(P_SlatMvt).silent()
            end
        end
        return
    end
    
    
    local function Calibration(P_timeSec, P_devBlind)
        logWrite('CALIBRATION ' .. P_timeSec, dz.LOG_FORCE)
        MoveSlat(P_timeSec, P_devBlind)
        currentBlindMode = 'CALIBRATION'
        return
    end
    
	-- \\\ Functions end ///

        if item.isDevice then -- selector by sbdy or change is temp or sun criteria or sun position
            logWrite('==>> triggered by device ' ..  item.id .. ' ' .. item.name .. ' ' .. item.state, dz.LOG_FORCE)
        elseif item.isTimer then -- Timer trigger
            logWrite('==>> triggered by timer', dz.LOG_FORCE)
        else -- Impossible error!
            logWrite('==>> triggered by ?????', dz.LOG_ERROR)
            return
        end

    
        for blindId, blinfInfo in pairs(BLINDS) do
            local devDummyBlind = dz.devices(blindId)               -- Switch dummy device with commands 
            local devBlind = dz.devices(BLINDS[blindId].BLIND_ID)   -- Device of the BLIND to close or open

            logWrite('-->> ' .. devDummyBlind.name .. ' ' .. blindId)
            logWrite(devBlind.name .. ' ' .. devBlind.id)
            
            local lastSlatAngle = dz.data.slatAngle[blindId]
            local lastMaxSlatAngle = dz.data.lastMaxSlatAngle[blindId]
            local currentBlindMode = dz.data.blindMode[blindId]
            local lastBlindProgram = dz.data.blindProgram[blindId]
            
            if currentBlindMode == nil then currentBlindMode = 'INITIAL' end
            if lastBlindProgram == nil then lastBlindProgram = 'INITIAL' end
            
            if lastSlatAngle == nil then
                if currentBlindMode == 'Auto' then -- 01/06/2021
                    logWrite('Start ' .. currentBlindMode .. ' lastSlatAngle null !!!', dz.LOG_ERROR)
                else
                    logWrite('Start ' .. currentBlindMode .. ' slat angle null')
                end
            else
                logWrite('Start ' .. currentBlindMode .. ' slat angle ' .. lastSlatAngle)
            end
                
            local BLIND_TYPE = BLINDS[blindId].BLIND_TYPE
            local BLIND_NAME = BLINDS[blindId].BLIND_NAME
            local BLIND_AZIMUTH = BLINDS[blindId].BLIND_AZIMUTH
            local SOLAR_ALTITUDE_MIN = BLINDS[blindId].SOLAR_ALTITUDE_MIN
            local SLAT_DOWN_ANGLE = BLINDS[blindId].SLAT_DOWN_ANGLE
            local SLAT_UP_ANGLE = BLINDS[blindId].SLAT_UP_ANGLE
            local SLAT_LENGHT = BLINDS[blindId].SLAT_LENGHT
            local SLAT_DISTANCE = BLINDS[blindId].SLAT_DISTANCE
            local BLIND_ANGLE = BLINDS[blindId].BLIND_ANGLE


            -- < Device trigger
            if item.isDevice then -- selector by sbdy or change is temp or sun criteria or sun position
                if item == devDummyBlind then -- sbdy used the dummy selector
                    if devDummyBlind.level == 10 then -- up
                        if CALIBRATION ~= nil then -- move the slate up to count the times to open
                            Calibration(CALIBRATION, devBlind)
                            return
                        else
                            devBlind.open().silent()
                            lastSlatAngle =  SLAT_UP_ANGLE
                            lastMaxSlatAngle = SLAT_UP_ANGLE
                            currentBlindMode = 'Manual'
                            dz.log('devDummyBlind level: ' .. devDummyBlind.level .. ' open', LOG_LEVEL)
                        end
                    elseif devDummyBlind.level == 20 then -- stop
                        devBlind.stop().silent()
                        lastSlatAngle =  nil
                        currentBlindMode = 'Manual'
                        logWrite('devDummyBlind level: ' .. devDummyBlind.level .. ' stop')
                    elseif devDummyBlind.level == 30 then -- down                    
                        if CALIBRATION ~= nil then -- move the slate up to count the times to close
                            Calibration(-CALIBRATION, devBlind)
                            return
                        else
                            devBlind.close().silent()
                            devBlind.close().afterSec(3).silent() -- twice in case of loss of message (12/11/2020)
                            lastSlatAngle =  SLAT_DOWN_ANGLE
                            currentBlindMode = 'Manual'
                            logWrite('devDummyBlind level: ' .. devDummyBlind.level .. ' close')
                        end
                    elseif devDummyBlind.level == 40 then -- automatic
                        if currentBlindMode ~= 'Auto' then
                            lastSlatAngle = nil -- to force an init in Auto mode
                            logWrite('devDummyBlind level: ' .. devDummyBlind.level .. ' init auto mode')
                        else
                            logWrite('devDummyBlind level: ' .. devDummyBlind.level .. ' go on auto mode')
                        end
                        currentBlindMode = 'Auto'
                    else
                        logWrite('devDummyBlind level unknown: ' .. devDummyBlind.state, dz.LOG_ERROR)
                    end
                end
            end
       
            
            -- < Auto mode
            local MAX_SUN_RADIATION = BLINDS[blindId].MAX_SUN_RADIATION
            logWrite('MAX_SUN_RADIATION: ' .. MAX_SUN_RADIATION, dz.LOG_DEBUG)
            local MAX_OUTSIDE_TEMP =  BLINDS[blindId].MAX_OUTSIDE_TEMP
            logWrite('MAX_OUTSIDE_TEMP: ' .. MAX_OUTSIDE_TEMP, dz.LOG_DEBUG)
            local MIN_OUTSIDE_TEMP =  BLINDS[blindId].MIN_OUTSIDE_TEMP
            logWrite('MIN_OUTSIDE_TEMP: ' .. MIN_OUTSIDE_TEMP, dz.LOG_DEBUG)
            logWrite('SLAT_UP_ANGLE: ' .. BLINDS[blindId].SLAT_UP_ANGLE, dz.LOG_DEBUG)
            logWrite('SLAT_DOWN_ANGLE: ' .. BLINDS[blindId].SLAT_DOWN_ANGLE, dz.LOG_DEBUG)
            local TTC_SLAT_SEC = BLINDS[blindId].TTC_SLAT_SEC
            
            local SLAT_MARGING = (BLINDS[blindId].SLAT_UP_ANGLE - BLINDS[blindId].SLAT_DOWN_ANGLE) / BLINDS[blindId].TTC_SLAT_SEC  -- choice: it is 1 second = 1 move
            --logWrite('SLAT_MARGING ' .. SLAT_MARGING, dz.LOG_FORCE)
            
            local outsideTemp = dz.devices(OUTSIDE_TEMP).temperature
            local sunAzimuth = tonumber(dz.devices(SOLAR_AZIMUTH).sValue)
            logWrite('sunAzimuth: ' .. sunAzimuth, dz.LOG_DEBUG)
            local sunAltitude = tonumber(dz.devices(SOLAR_ALTITUDE).sValue)
            logWrite('sunAltitude: ' .. sunAltitude, dz.LOG_DEBUG)
            local wBlindNormalAzimuth

            local sunRadiationValue, cosIncidence = GetSunRadiation(sunAzimuth, sunAltitude, BLIND_AZIMUTH, BLIND_ANGLE) 
            
            local newBlindProgram
            local wCold = false
            local wHot = false
            
            logWrite('lastBlindProgram ' .. lastBlindProgram .. ' sunRadiationValue ' .. sunRadiationValue .. ' outsideTemp ' .. outsideTemp .. ' cosIncidence ' .. cosIncidence, dz.LOG_DEBUG)
            logWrite('Sum+ ' .. MAX_SUN_RADIATION + SUN_RADIATION_MARGING, dz.LOG_DEBUG)
            logWrite('Temp+ ' .. MAX_OUTSIDE_TEMP + OUTSIDE_TEMP_MARGING, dz.LOG_DEBUG)
            if currentBlindMode == 'Auto' then
                logWrite('Auto mode execution')
                
                if (lastBlindProgram == "Cold" and outsideTemp < MIN_OUTSIDE_TEMP + OUTSIDE_TEMP_MARGING) then
                    
                    newBlindProgram = "Cold"
                    logWrite('newBlindProgram stay Cold', dz.LOG_DEBUG)
                    wCold = true -- 01/06/2021
                    
                elseif (lastBlindProgram ~= "Cold" and outsideTemp < MIN_OUTSIDE_TEMP - OUTSIDE_TEMP_MARGING) then
                    
                    newBlindProgram = "Cold"
                    logWrite('newBlindProgram go Cold', dz.LOG_DEBUG)
                    wCold = true -- 01/06/2021
                    
                end
                if (lastBlindProgram == "Hot" and
                    sunRadiationValue > MAX_SUN_RADIATION - SUN_RADIATION_MARGING and
                    outsideTemp > MAX_OUTSIDE_TEMP - OUTSIDE_TEMP_MARGING) then -- 22/05/2022
                    
                    newBlindProgram = "Hot"
                    logWrite('newBlindProgram stay Hot', dz.LOG_DEBUG)
                    wHot = true -- 01/06/2021
                    
                elseif (lastBlindProgram ~= "Hot" and
                    sunRadiationValue > MAX_SUN_RADIATION + SUN_RADIATION_MARGING and
                    outsideTemp > MAX_OUTSIDE_TEMP + OUTSIDE_TEMP_MARGING) then -- 22/05/2022
                    
                    newBlindProgram = "Hot"
                    logWrite('newBlindProgram go Hot', dz.LOG_DEBUG)
                    wHot = true -- 01/06/2021
                    
                end
                logWrite('wHot ' .. tostring(wHot) .. ' wCold ' .. tostring(wCold), dz.LOG_DEBUG)
                if (wCold and wHot) or (not wCold and not wHot) then
                    
                    newBlindProgram = "Warm"
                    logWrite('newBlindProgram Warm', dz.LOG_DEBUG)
                    
                end
        
                local blindProgramChanged = false
                local logAction = '?' -- 06/07/2021
                if newBlindProgram ~= lastBlindProgram then
                    blindProgramChanged = true
                    logAction = 'changed to '
                else
                    logAction = 'remains on '
                end
                -- 11/08/2021
                logWrite(blindId .. ' ' .. BLIND_NAME .. ' - ' .. currentBlindMode .. ', ' .. logAction .. newBlindProgram .. ' - Outside Temp: ' .. _u.round(outsideTemp,1) .. '/[' .. MIN_OUTSIDE_TEMP .. ' - ' .. MAX_OUTSIDE_TEMP .. ']+/-' .. OUTSIDE_TEMP_MARGING .. ' - Sun criteria: ' .. sunRadiationValue .. '/' .. MAX_SUN_RADIATION .. '+/-' ..
SUN_RADIATION_MARGING, dz.LOG_FORCE)

                if lastSlatAngle == nil then -- on Auto mode, if initial position is unknown, the blind is open to get the zero -- move up 01/06/2021
                    logWrite('lastSlatAngle to init') 
                    lastSlatAngle = InitPosition(devBlind, SLAT_UP_ANGLE)
                end
                
                if newBlindProgram == "Hot" then
                    -- -- comment 01/06/2021
                    --if lastSlatAngle == nil then -- on Auto mode, if initial position is unknown, the blind is open to get the zero
                      --  logWrite('lastSlatAngle to init') 
                        --lastSlatAngle = InitPosition(devBlind)
                    --else
                        logWrite('Auto Program: ' .. newBlindProgram .. ' / Last Slat Angle '.. lastSlatAngle)            
                        local slatAngleTarget = CalculateSlatAngle(sunAzimuth, sunAltitude, BLIND_AZIMUTH, SLAT_DOWN_ANGLE, SLAT_UP_ANGLE, SOLAR_ALTITUDE_MIN, SLAT_LENGHT, SLAT_DISTANCE, BLIND_ANGLE, BLIND_TYPE, cosIncidence)
                        
                        if slatAngleTarget == nil then
                            
                            logWrite(blindId .. ' ' .. BLIND_NAME, dz.LOG_ERROR)
                            logWrite('sunAzimuth=' .. sunAzimuth, dz.LOG_ERROR)
                            logWrite('sunAltitude=' .. sunAltitude, dz.LOG_ERROR)
                            logWrite('BLIND_AZIMUTH=' .. BLIND_AZIMUTH, dz.LOG_ERROR)
                            logWrite('SLAT_DOWN_ANGLE=' .. SLAT_DOWN_ANGLE, dz.LOG_ERROR)
                            logWrite('SLAT_UP_ANGLE=' .. SLAT_UP_ANGLE, dz.LOG_ERROR)
                            logWrite('SOLAR_ALTITUDE_MIN=' .. SOLAR_ALTITUDE_MIN, dz.LOG_ERROR)
                            logWrite('SLAT_LENGHT"=' .. SLAT_LENGHT, dz.LOG_ERROR)
                            logWrite('SLAT_DISTANCE"=' .. SLAT_DISTANCE, dz.LOG_ERROR)
                            logWrite('BLIND_ANGLE"=' .. BLIND_ANGLE, dz.LOG_ERROR)
                            logWrite('BLIND_TYPE=' .. BLIND_TYPE, dz.LOG_ERROR)
                            logWrite('cosIncidence=' .. cosIncidence, dz.LOG_ERROR)
                            
                            slatAngleTarget = SLAT_UP_ANGLE
                        
                        end
                        
                        local slateMvt = 0
                        local lastSlatAngleDelta
                        slateMvt, lastSlatAngleDelta = CalculateSlatMvt(slatAngleTarget, lastSlatAngle, lastMaxSlatAngle, SLAT_UP_ANGLE, SLAT_DOWN_ANGLE, SLAT_MARGING, TTC_SLAT_SEC) -- mouvement to make to the slates in seconds
                        MoveSlat(slateMvt, devBlind)
                        if lastSlatAngleDelta == 999 then       -- it is the up position
                            lastSlatAngle = SLAT_UP_ANGLE
                            lastMaxSlatAngle = SLAT_UP_ANGLE -- 19/05/2022
                        elseif lastSlatAngleDelta == -999 then  -- it is the down position 
                            lastSlatAngle = SLAT_DOWN_ANGLE
                            lastMaxSlatAngle = SLAT_DOWN_ANGLE -- 19/05/2022
                        else
                            logWrite('lastSlatAngle ' .. lastSlatAngle)
                            lastSlatAngle = lastSlatAngle + lastSlatAngleDelta
                        end
                    --end
                elseif newBlindProgram == "Cold" then -- 08/08/2021 - bug! lastBlindProgram changed to newBlindProgram
                    --local solarAltitude = tonumber(devAltitude.state)
                    --logWrite("Solar Altitude=" .. tostring(solarAltitude))

                    --if dz.time.matchesRule('at 17:00-00:30') then -- !!! -> to put in parameters...  !!!!!!!!!!!!! 07/06/2022 to try!
                        --logWrite("timeConditionsClose OK")
                        if sunAltitude < -8 then
                            if devBlind.state ~= "Closed" then
                                devBlind.close()
                                devBlind.close().afterSec(5).silent() -- twice in case of loss of message
                                logWrite('Auto Program: ' .. newBlindProgram .. ' / ' .. devBlind.name .. " closing")
                            end
                            lastSlatAngle =  SLAT_DOWN_ANGLE
                            lastMaxSlatAngle = SLAT_DOWN_ANGLE -- 19/05/2022
                        else -- end
                    --elseif dz.time.matchesRule('at 6:00-12:00') then-- !!! -> to put in parameter 
                        --logWrite("timeConditionsOpen OK")
                        --if sunAltitude > -8 then
                            if devBlind.state ~= "Open" then
                                devBlind.open().silent()
                                devBlind.open().afterSec(5).silent()
                                logWrite('Auto Program: ' .. newBlindProgram .. ' / ' .. devBlind.name .. " opening")
                            end
                            lastSlatAngle =  SLAT_UP_ANGLE
                            lastMaxSlatAngle = SLAT_UP_ANGLE -- 19/05/2022
                        end
                    --end
                else -- Warm
                    --if dz.time.matchesRule('at 6:00-10:00') or blindProgramChanged then
                        --logWrite("Time to check if it is open")
                        --local solarAltitude = tonumber(devAltitude.state)
                        --logWrite("Solar Altitude=" .. tostring(solarAltitude))
                        if sunAltitude > -8 then
                            if devBlind.state ~= "Open" then
                                devBlind.open()
                                logWrite('Auto Program: ' .. newBlindProgram .. ' / ' .. devBlind.name .. " opening")
                            end
                            lastSlatAngle =  SLAT_UP_ANGLE
                            lastMaxSlatAngle = SLAT_UP_ANGLE -- 19/05/2022
                        end
                    --end
                end
    
            else   
                newBlindProgram = ''
            end
                
        
            -- < Update global variables if changed
            local renameFlag = false
            if dz.data.blindMode[blindId] ~= currentBlindMode then
                renameFlag = true
                logWrite('currentBlindMode changed to ' .. currentBlindMode, dz.LOG_FORCE)
                dz.data.blindMode[blindId] = currentBlindMode
            else
                logWrite('currentBlindMode NOT changed: ' .. currentBlindMode)
            end
            
            if dz.data.blindProgram[blindId] ~= newBlindProgram then
                renameFlag = true
                if newBlindProgram == '' then -- 07/02/2021 clarify log if newBlindProgram is empty
                    logWrite('newBlindProgram changed to NONE', dz.LOG_FORCE)                
                else
                    logWrite('newBlindProgram changed to ' .. newBlindProgram, dz.LOG_FORCE)
                 end
                dz.data.blindProgram[blindId] = newBlindProgram
            else
                logWrite('newBlindProgram NOT changed: ' .. newBlindProgram)
            end

            if (dz.data.slatAngle[blindId] ~= lastSlatAngle) then -- 16/08/2021
                renameFlag = true
                if lastSlatAngle == nil then
                    logWrite('SlatAngle changed to nul')
                else
                    if lastSlatAngle == SLAT_DOWN_ANGLE then
                        logWrite(devBlind.name ..  ' closes ', dz.LOG_FORCE)
                    elseif lastSlatAngle == SLAT_UP_ANGLE then
                        logWrite(devBlind.name ..  ' opens ', dz.LOG_FORCE)
                    else
                        local newSlatAngle = _u.round(lastSlatAngle,0)
                        logWrite('Slates of ' .. devBlind.name .. ' move to ' .. newSlatAngle .. '° - ' .. newBlindProgram, dz.LOG_FORCE)
                    end
                end
                dz.data.slatAngle[blindId] = lastSlatAngle
                dz.data.lastMaxSlatAngle[blindId] = lastMaxSlatAngle -- 19/05/2022
            else    
                if lastSlatAngle == nil then
                    logWrite('SlatAngle NOT changed: nul')
                else
                    if lastSlatAngle == SLAT_DOWN_ANGLE then
                        logWrite(devBlind.name ..  ' remains closed ')
                    elseif lastSlatAngle == SLAT_UP_ANGLE then
                        logWrite(devBlind.name ..  ' remains open ')
                    else
                        logWrite('Slates of ' .. devBlind.name .. ' remains on ' .. lastSlatAngle .. ' °')
                    end
                end
            end
        
            if renameFlag then -- 16/08/2021
                local blindRename = BLIND_NAME
                if lastSlatAngle == nil then
                    blindRename = blindRename .. ' (' .. devBlind.state .. ') - '  .. currentBlindMode
                else
                    if lastSlatAngle == SLAT_DOWN_ANGLE then
                        blindRename = blindRename .. ' (Closed) - ' .. currentBlindMode
                    elseif lastSlatAngle == SLAT_UP_ANGLE then
                        blindRename = blindRename .. ' (Open) - ' .. currentBlindMode
                    else
                        local newSlatAngle = _u.round(lastSlatAngle,0)
                        blindRename = blindRename .. ' (' .. newSlatAngle .. '°) - ' .. currentBlindMode
                    end
                end
                blindRename = blindRename .. ' ' .. newBlindProgram
                devDummyBlind.rename(blindRename)
                devDummyBlind.switchSelector(devDummyBlind.level).silent()
            end
        end

    end
}
22/06/2022 : nouvelle version

On peut déclarer plusieurs stores même s'ils ne sont pas sur la même façade.
Dans mon cas, il est paramétré pour 2 stores:
- un brise soleil sur un toit en pente
lames.png
lames.png (169.15 Kio) Vu 1846 fois
- un store vertical
Un selector pour actionner le store et surtout passer en mode Auto
Selector.png
Selector.png (24.22 Kio) Vu 1846 fois
La suite des explications demain...
(scripts donnés sans garantie de support, il vaut mieux connaitre un peu dzvents et domoticz ou, bien faire attention!)
Modifié en dernier par hestia le 22 juin 2022, 23:00, modifié 2 fois.

hestia
Messages : 245
Enregistré le : 12 sept. 2018, 22:36

Re: store et brise-soleil

Message par hestia »

Le script du store a besoin de la position du soleil: hauteur et azimut.
Je propose ce script

Edit du 22/06/2022: ici la dernière version du script
Solar Data script : Azimuth, Altitude, Lux

Il faut aussi créer les dummy devices pour les résultats, voir les commentaires dans le script
Modifié en dernier par hestia le 22 juin 2022, 23:04, modifié 1 fois.

hestia
Messages : 245
Enregistré le : 12 sept. 2018, 22:36

Re: store et brise-soleil

Message par hestia »

Ensuite, il faut donner les paramètres du store.
C'est le tableau à 2 niveaux, avec un s/tableau par store.
Je reprends en français

Code : Tout sélectionner

['BLIND_TYPE'] = 'B',           -- "B" Brise Soleil or "V" Vertical (store sans lame et vertical)
['BLIND_NAME'] = 'Brise Soleil',-- Nom que l'on donne au dummy device qui représente le store
['BLIND_ID'] = 673,             -- le device dans domoticz pour ouvrir et fermer le store
['BLIND_AZIMUTH'] = 199,        -- Angle à partir du nord quand le soleil est // au store (angle OM sur le schéma)
['SOLAR_ALTITUDE_MIN'] = 20,    -- Altitude basse du soleil à partir de laquelle on peut rouvrir le store    
['MAX_SUN_CRITERIA'] = 24,      -- température max extérieure à partir de laquelle on passe en programme HOT
['MIN_OUTSIDE_TEMP']  = 20,     -- température min extérieur à partir de laquelle on passe en programme COLD 
['SLAT_DOWN_ANGLE']  = -4,      -- Angle de la lame par rapport à l’horizontale quand elle est en bas (<0), -90 pour les stores verticaux sans lame
['SLAT_UP_ANGLE'] = 55,         --Angle de la lame par rapport à l’horizontale quand elle est en haut, +90 pour les stores verticaux sans lame
['SLAT_LENGHT']  = 8,           -- Longueur de la lame, (cm par exemple), rien pour les stores verticaux sans lame
['SLAT_DISTANCE']  = 7,         -- Distance entre 2 lames, rien pour les stores verticaux sans lame
['BLIND_ANGLE']  = 16,          -- Angle du store (toit) avec l'horizontale (>0), rien pour les stores verticaux sans lame
['TTC_SLAT_SEC'] = 16           -- Durée pour fermer le store, 1 for vertical blind
Il y a des mesures à prendre sur le store...
vueDessus.png
vueDessus.png (242.22 Kio) Vu 1806 fois
et à la boussole ;-)
Attention: attendre qu'il fasse beau pour monter sur le toit. Pour l'hiver, mettre des valeurs au doigt mouillé, de toute façon la plupart des valeurs ne servent que pour le programme HOT, il y a encore un peu de temps ;-)
Modifié en dernier par hestia le 11 janv. 2021, 22:29, modifié 1 fois.

hestia
Messages : 245
Enregistré le : 12 sept. 2018, 22:36

Re: store et brise-soleil

Message par hestia »

J'aurais dû commencer par là.
Mon brise-soleil et le store sont avec du Somfy en RFX, donc sans retour d'état.
Si l'on touche la bouton physique ça dérègle tout, car pas possible de le savoir.
=> c'est ce que je me suis demandé: ne serait-il pas possible d'associer le bouton à domoticz pour savoir que l'on a touché au bouton???
Ce serait bien d'avoir aussi un capteur d'angle pour connaître la position réelle, mais je n'ai rien trouvé d'acceptable.
A l'usage, ce n'est pas nécessaire!

hestia
Messages : 245
Enregistré le : 12 sept. 2018, 22:36

Re: [DzVents] Store et brise-soleil

Message par hestia »

Summer is coming...
Pour mémoire ce script utile en ce moment qui permet de fermer les stores et volets roulant en fonction du soleil
pour un store ou rideau...
roller.png
roller.png (354.7 Kio) Vu 138 fois
et de bouger les lames d'un brise soleil en fonction du soleil!
Screenshot 2022-06-22 231102.png
Screenshot 2022-06-22 231102.png (677.12 Kio) Vu 138 fois

Répondre