blinds move on the sun

In this subforum you can show projects you have made, or you are busy with. Please create your own topic.

Moderator: leecollings

rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

hestia wrote: Friday 03 June 2022 8:27
I have this one that I think is like yours (up and down only)
roller.png
It's the 1st one in the script => BLIND_TYPE'] = 'V'
Ah ok, I thought you meant something like this
harmony_-_houten_jaloezie_privacy_50mm_-_zwart_met_nerf_nd246_PWPR50-ND246_detail_320x350_2.jpg
harmony_-_houten_jaloezie_privacy_50mm_-_zwart_met_nerf_nd246_PWPR50-ND246_detail_320x350_2.jpg (53.88 KiB) Viewed 2010 times
(not mine, I do have some of these, but I did not automate these yet) for the 'V' type blinds where your script would control the angle of the slats in them, not the up & down movement. I will give the 'V' blind type another try for my roller blinds before making radical changes :-). Comming to think of it, I don't know why I thought it would be these, as I think these are called horizontal blinds...

But 'R' as Roller is a better code name, if my 'V' script is ok for you, I could change, to make it easier to understand - also I had thought that rollers are only vertical ;-) but that's not right because I have roller (shutter) on my Velux roof!
R for Roller would be more generic indeed. But I will change to 'V' easily now that I understand what you mean :-)


Also I see that you're applying timing to run the slats to a specific angle. Does your slats device not provide a percentage slider for setting the angle of the slats? Or do you have a specific reason not to use the device's percentage setting?
I have RFY / RFY roller and brise-soleil
rfy.png
I wasn't aware that I could give directly an angle and my devices (slats and roller) do not provide a percentage slider, perhaps I didn't configure them correctly?
What do you have on you side?
[/quote]
rolluik device.JPG
rolluik device.JPG (23.03 KiB) Viewed 2010 times
Mine are controlled by zwave, some have a Fibaro Roller Shutter 3 switch others a Neo coolcam curtain switch (I pretty much like the price and aesthetics of the neocam device, but it's no longer for sale because it has some serious quality issues, which I too have experienced 1st hand :-( ) . Both do show up in DOmoticz like yours initially, but if you go into the device's properties (the 'aanpassen' button in Dutch) you can change the switch type to 'Blinds Inverted + Stop'. After that the device will have a percentage slider and you can use the device.setLevel() function in dzVents to set a percentage from 0 to 99, directly instructing the device to open/close to the specified percentage. You do need to 'calibrate' the device though before the slider can be used. Instructions how to calibrate the switches are device specific, so you may have to look them up in the manual for your device. Also, if your device wasn't calibrated at the time you included it, Domoticz may not have the required node information on the 'dimmer' subdevice yet. This can easily be solved by renewing the node information after the calibration is done.
I'm working on a new version to improve things regarding what I was experiencing with summer arriving...
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
next? - add HOT+ program: shut the blinds if very hot and the sun strikes the blind !!!!!!!!!!!!!!!!!!
I could publish the script if interested
Always interested ;-) . I'm going to experiment somewhat with my own version of your script and I'll send you my outcome too. I've already got some nice tricks that could be helpful for you too. Do you have the script in github, so I can fork it and send you suggestions for updates/extensions?
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

Some examples of things I would like to add/change are:
- a "privacy" feature. Some of our blinds on the ground floor are not only used for heat/light control, but also serve the purpose of keeping out prying eyes into our home. This may require a different type of "program".
- the time components in the script. I need to investigate this more, but I think these could be replaced by conditions based on the position of the sun completely.
hestia
Posts: 361
Joined: Monday 25 December 2017 23:06
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Paris
Contact:

Re: blinds move on the sun

Post by hestia »

I answer this one now, because it's "easy" and the previous after that need more time...
privacy => ok
time components => I'm not sure why I did this :-( I think it was because I don't have zwave protocol, so no feedback of the physical button
I think if I remove it, there will be no change for me - old thinking not challenged until today ;-)
hestia
Posts: 361
Joined: Monday 25 December 2017 23:06
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Paris
Contact:

Re: blinds move on the sun

Post by hestia »

Both do show up in Domoticz like yours initially, but if you go into the device's properties (the 'aanpassen' button in Dutch) you can change the switch type to 'Blinds Inverted + Stop'.
I could configure it, but the slider is not working (not clickable), perhaps because it is RFY and not zwave?
I've tried several types of blind with the same result
Screenshot 2022-06-04 124949.png
Screenshot 2022-06-04 124949.png (27.24 KiB) Viewed 1986 times
Always interested ;-) . I'm going to experiment somewhat with my own version of your script and I'll send you my outcome too. I've already got some nice tricks that could be helpful for you too. Do you have the script in github, so I can fork it and send you suggestions for updates/extensions?
Last version updated in the 1st post
I don't have the script in github and I've never used it! I'm not a professional coder as you ;-) ; I've been more or less for 2 years, but before git was made!
I could learn git, but it could take some times
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

hestia wrote: Saturday 04 June 2022 13:25
Both do show up in Domoticz like yours initially, but if you go into the device's properties (the 'aanpassen' button in Dutch) you can change the switch type to 'Blinds Inverted + Stop'.
I could configure it, but the slider is not working (not clickable), perhaps because it is RFY and not zwave?
I've tried several types of blind with the same result
I've got no experience at all with Somfy devices, so I can't tell you with certainty. But if the device doesn't respond, then I assume you're right and the hardware or its driver doesn't support the level feature. That's a shame, because it makes setting a device to specific locations a lot more simple and reliable.
For example, I've got multiple softwares installed in the same pi running my Domoticz. Something -I still have not been able to identify yet what- takes up all cpu cycles for around 30 - 45 seconds at every whole hour, making that my Domoticz stops responding during that time. If I were to use your timing based solution, and the blinds would start moving a second before an hour, the blinds could continue moving for 30 - 45 seconds before the stop signal is sent when Domoticz becomes responsive again. The position of the blinds would be completely off because of the 'missing time'. For this reason I think I'm not going to use your timing based script directly. But I am certainly going to use some of your ideas in my own script. Plus I'm going to make sure you get a copy. If possible, I'll even try to make it work for your Somfy devices, but I'm not making any promises on that.
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

hestia wrote: Saturday 04 June 2022 13:25 I don't have the script in github and I've never used it! I'm not a professional coder as you ;-) ; I've been more or less for 2 years, but before git was made!
I could learn git, but it could take some times
I can recommend installing github desktop. This makes it very easy to keep a safe copy of any set of files in git and giving you easy insight in what changed between revisions of those files.
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

Hi Hestia,

I think your sun in the front logic is incorrect.

Code: Select all

            if P_SolarAzimuth >= P_BlindAzimuth and P_SolarAzimuth <= P_BlindAzimuth + 180 then

It should be something like this:

Code: Select all

            if math.sin(math.rad(P_BlindAzimuth - P_SolarAzimuth )) >= 0
I have blinds on the east and on the west of my house, i.e. the east facing one needs to be down in the morning and the west facing one in the afternoon. Both were however reported to have the sun in the front at the same time. I haven't tried it in your script because that doesn't work well with my blinds. But I had your logic copied into my script, so I think yours will behave the same way.
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

Plus a small improvement making it easier to maintain the blinds settings. Since you always have to include the dummy devices in the set of trigger items, why not have the script do that for you so you never again forget to add one or leave one in that isn't in your blinds settings any more. An example could be this :

Code: Select all

-- Build the set of devices that will trigger our script to be
-- executed. First the devices that are required ...
local DEVICES_TRIGGER = {
        SOLAR_ALTITUDE, 
        SOLAR_AZIMUTH,
        OUTSIDE_TEMP, 
        SUN_CRITERIA1
    } -- SOLAR_AZIMUTH is updated at the same time as SUN_CRITERIA

-- ... next add an optional device ...
if SUN_CRITERIA2 ~= nil then
    table.insert( DEVICES_TRIGGER, SUN_CRITERIA2 )
end

-- ... and finally add the dummy blinds devices from 
-- the BLINDS definitions.
for blind_idx, _ in ipairs(BLINDS) do
    table.insert( DEVICES_TRIGGER, blind_idx )
end
hestia
Posts: 361
Joined: Monday 25 December 2017 23:06
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Paris
Contact:

Re: blinds move on the sun

Post by hestia »

thanks!
I didn't do it because I'm not so good with LUA, I discovered it with DzVents, not before...
I'll use it in other scripts
hestia
Posts: 361
Joined: Monday 25 December 2017 23:06
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Paris
Contact:

Re: blinds move on the sun

Post by hestia »

rrozema wrote: Sunday 05 June 2022 12:13 Hi Hestia,

I think your sun in the front logic is incorrect.

Code: Select all

            if P_SolarAzimuth >= P_BlindAzimuth and P_SolarAzimuth <= P_BlindAzimuth + 180 then

It should be something like this:

Code: Select all

            if math.sin(math.rad(P_BlindAzimuth - P_SolarAzimuth )) >= 0
I have blinds on the east and on the west of my house, i.e. the east facing one needs to be down in the morning and the west facing one in the afternoon. Both were however reported to have the sun in the front at the same time. I haven't tried it in your script because that doesn't work well with my blinds. But I had your logic copied into my script, so I think yours will behave the same way.
Hi rrozema
First thing is to be sure of the angles considered
P_BlindAzimuth = Angle from North to where the sun is // to the blind (like the Azimuth)
Screenshot 2022-06-05 130808.png
Screenshot 2022-06-05 130808.png (352.96 KiB) Viewed 1956 times
(sorry in French, I've not the time today to translate)
Could you pls give me a drawing like this one for your blinds so I could give you the P_BlindAzimuth that I think is correct regarding the math I did? (I'll try to look at it tonight)
What I did to define it, was to wait the sun to be // to the blind after the sun passed the North
hestia
Posts: 361
Joined: Monday 25 December 2017 23:06
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Paris
Contact:

Re: blinds move on the sun

Post by hestia »

rrozema wrote: Friday 03 June 2022 16:00 - a "privacy" feature. Some of our blinds on the ground floor are not only used for heat/light control, but also serve the purpose of keeping out prying eyes into our home. This may require a different type of "program".
Could you be more specific about this feature?

Do you mean you want a specific button on the widget to have the roller closed?
=> That's the down button.

Do you mean you want to have specific hours with the roller closed?
=> That's the "time components in the script" that could be in a parameter of the blind, or could be simply the Timers of the widget

What else? ;-)
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

hestia wrote: Sunday 05 June 2022 13:18
rrozema wrote: Sunday 05 June 2022 12:13 Hi Hestia,

I think your sun in the front logic is incorrect.

Code: Select all

            if P_SolarAzimuth >= P_BlindAzimuth and P_SolarAzimuth <= P_BlindAzimuth + 180 then

It should be something like this:

Code: Select all

            if math.sin(math.rad(P_BlindAzimuth - P_SolarAzimuth )) >= 0
I have blinds on the east and on the west of my house, i.e. the east facing one needs to be down in the morning and the west facing one in the afternoon. Both were however reported to have the sun in the front at the same time. I haven't tried it in your script because that doesn't work well with my blinds. But I had your logic copied into my script, so I think yours will behave the same way.
Hi rrozema
First thing is to be sure of the angles considered
P_BlindAzimuth = Angle from North to where the sun is // to the blind (like the Azimuth)
Screenshot 2022-06-05 130808.png
(sorry in French, I've not the time today to translate)
Could you pls give me a drawing like this one for your blinds so I could give you the P_BlindAzimuth that I think is correct regarding the math I did? (I'll try to look at it tonight)
What I did to define it, was to wait the sun to be // to the blind after the sun passed the North
Here's a drawing of the placement of my blinds. As you can see the both sides of my house are towards (mostly) the east and the west. In the morning the east facing window will get sunshine, in the afternoon the west facing window will get sunshine. Using your logic however both windows were computed to face the sun at pretty most the same time. You could be right in that I don't yet have the correct angles, but even if the angles aren't correct yet, with 2 blinds with azimuth angles so far from each other it can't be that they both face the sun at the same time. Using my suggested alternative using the sin() over the difference between the blinds' and the sun's angles to the north I do get 2 different times for the both blinds, one in the morning and the other in the afternoon.
blinds rrozema.jpg
blinds rrozema.jpg (242.28 KiB) Viewed 1939 times
To get these angles I simply used a compass app in my phone and the phone with the side held against the window to read the compass direction. For one side I held the phone with the top of the phone pointing North-ish and for the other I pushed the other side of the phone against the window, with the top of the phone pointing South-ish. I considered worst case I could have the east blind close in the afternoon when the sun is at the other side and the west blind close in the morning. All I would need to do is point my phone in the other direction (or just switch the both azimuth values on the blinds).

As a reference, a random site I found when searching for "sin positive" is https://www.toppr.com/ask/question/what ... quadrants/. This shows that sin() is positive (i.e. >= 0) in the 1st and 2nd quadrant and negative (i.e. < 0) in the 3rd and 4th quadrant. So I think this is exactly what we need to determine if the sun is in front or "behind" each blind.

I think part of the problem is that in your explaination I have no clue what you mean by 2 lines being "//"...

I've created an excel document to demonstrate that the formula for Sun In The Front should realy use the sinus function. The document has 2 sheets, one with the original formula plus one using the sinus formula. Both have the blind angle along the vertical axis and the sloar angle along the horizontal axis. I'm expecting half of the times (=180 degrees) to be 'front' for each column and the other half to be 'behind'. It is not going to be exactly half because both 0 and 180 degrees are included in 'front' (by choice). The 1st sheet shows the outcome for the orginal formula, the 2nd sheet that foro the sinus formula. I've already marked the cells in red where the both formulas diverge.
SunInTheFront.xlsx
(6.88 KiB) Downloaded 34 times
You can be right that it needs a +90 or -90 somewhere depending on the definition of the 'angle of the blinds' which is not completely clear to me yet.
Last edited by rrozema on Monday 06 June 2022 11:52, edited 2 times in total.
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

hestia wrote: Sunday 05 June 2022 14:34
rrozema wrote: Friday 03 June 2022 16:00 - a "privacy" feature. Some of our blinds on the ground floor are not only used for heat/light control, but also serve the purpose of keeping out prying eyes into our home. This may require a different type of "program".
Could you be more specific about this feature?

Do you mean you want a specific button on the widget to have the roller closed?
=> That's the down button.

Do you mean you want to have specific hours with the roller closed?
=> That's the "time components in the script" that could be in a parameter of the blind, or could be simply the Timers of the widget

What else? ;-)
My ideas on this privacy setting aren't fully formed yet. Plus it would be for a horizontal blind that is not yet electronically controlled, so no actual test-case yet. This is horizontal blind that can move up and down, plus the slats can be tilted. As this window faces a path where people can walk by, we almost always have this blind in the down position and during day-time we only tilt the slats to let more or less sun in. However, in the evening, when we have the lights on in the house, we don't want to expose ourselves too much to the people outside in the dark, so we always tilt the slats so that from the outside you can only see the ceiling.
hestia
Posts: 361
Joined: Monday 25 December 2017 23:06
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Paris
Contact:

Re: blinds move on the sun

Post by hestia »

rrozema wrote: Sunday 05 June 2022 16:02 I think your sun in the front logic is incorrect.
You're right :?
I'm working on it, with a simplification of the code to avoid doing twice the same thing.
I created fake blinds (on real windows to look what the sun do) to have something like you.
It took me some time to understood what I did before, so I'll make some changes to be easier for the next time. One of the thing is to consider the azimuth of the normal of the blind instead of the surface to be nearer to the doc I've got http://www.foad.uadb.edu.sn/mod/book/to ... hp?id=2397 (sorry in French)
Here is a new temporary version not fully tested that could do it for rollers (not for slat I think)

Code: Select all

--[[
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
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"
--]]

local ALL_MAX_SUN_RADIATION = 400
local ALL_MAX_OUTSIDE_TEMP = 22
local ALL_XXL_OUTSIDE_TEMP = 30
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 = .8 -- 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 = {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'

-- log options
local LOG_DEBUG = 1         -- 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


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 -- the sun is behind

        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) -- <0, sun behind
            
            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 when the sun is low :' .. F_SlatAngle)
        else
            if P_cosIncidence > 0 then 
            --if P_SolarAzimuth >= P_BlindAzimuth and P_SolarAzimuth <= P_BlindAzimuth + 180 then
                F_SunInTheFront = true -- the sun is in the front = where the blind is)
                logWrite('Sun in the front')
            else
                logWrite('Sun in the back')            
            end
            --[[
            local F_IncidenceSunBlind = 90 - P_SolarAzimuth + P_BlindAzimuth -- 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))
    	                                    )
                                                    )
                                                    
            logWrite('F_ProjectedSolarAltitude: ' .. F_ProjectedSolarAltitude)
            ]]--
            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)
                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

                logWrite('P_SlatDownAngle ' .. P_SlatDownAngle .. ' / P_SlatUpAngle ' .. P_SlatUpAngle)
                local q =  math.tan(math.rad(F_ProjectedSolarAltitude + P_BLIND_ANGLE))
                --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("BLIND_TYPE unknown " .. P_BLIND_TYPE, dz.LOG_ERROR)
                return(0)
            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, dz.LOG_FORCE)

        logWrite('F_SlatAngleDelta ' .. F_SlatAngleDelta, dz.LOG_FORCE)
        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 = GetSunRadiation(sunAzimuth, sunAltitude, BLIND_AZIMUTH + 90, BLIND_ANGLE)       !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!   
            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 .. '] - Sun criteria: ' .. sunRadiationValue .. '/' .. MAX_SUN_RADIATION, 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)
                        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, dz.LOG_FORCE)
                            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...
                        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
                        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
}
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

The sinus() >= 0 formula should do the trick nicely. All it needs is a +90 or -90 if your 'blinds azimuth' is perpendicular to the compass direction given when you hold the phone against the blind.
hestia
Posts: 361
Joined: Monday 25 December 2017 23:06
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Paris
Contact:

Re: blinds move on the sun

Post by hestia »

I removed this formula

Code: Select all

            if P_cosIncidence > 0 then 
            --if P_SolarAzimuth >= P_BlindAzimuth and P_SolarAzimuth <= P_BlindAzimuth + 180 then
                F_SunInTheFront = true -- the sun is in the front = where the blind is)
                logWrite('Sun in the front')
            else
                logWrite('Sun in the back')            
            end
because the computing to give if the sun is behind or not is here

Code: Select all

    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
'blinds azimuth' is perpendicular to the compass direction given when you hold the phone against the blind.
Now blinds azimuth is what gives you compass
I did this to have the same angles as the doc
Screenshot 2022-06-06 134350.png
Screenshot 2022-06-06 134350.png (127.29 KiB) Viewed 1889 times
The script should also work for blinds that are not only vertical but also on roofs
I'll give the translation to what is in the script...
Last edited by hestia on Monday 06 June 2022 13:51, edited 1 time in total.
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

hestia wrote: Monday 06 June 2022 12:36 I'll give the translation to what is in the script...
No need to translate french into english for me. I can read quite a bit of french and what I don't get, I use google translate ;-)

B.t.w. are you on slack?
hestia
Posts: 361
Joined: Monday 25 December 2017 23:06
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Paris
Contact:

Re: blinds move on the sun

Post by hestia »

translation, for other (few) readers and because it's an English forum
slack, yes, I used it once, same name hestia
rrozema
Posts: 470
Joined: Thursday 26 October 2017 13:37
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Delft
Contact:

Re: blinds move on the sun

Post by rrozema »

I used to measure the blinds angle with the long side of the phone against the window. But I think I will get the reading you intend to use when I put the top of the phone against the glass, like in the picture. Is that right?
blinds compass.jpg
blinds compass.jpg (128.69 KiB) Viewed 1895 times
hestia
Posts: 361
Joined: Monday 25 December 2017 23:06
Target OS: Raspberry Pi / ODroid
Domoticz version: 2022.1
Location: Paris
Contact:

Re: blinds move on the sun

Post by hestia »

correct for the new version, if your phone is accurate...
What I tried to explain with the //, is when the sun is // to your window, just no sun inside, you look at the sun azimuth widget and the value is the azimuth of your window ; now to get the azimuth of the normal of the window, you add 90° (and check with the value of the phone). It's more accurate like this
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest