Lua dzVents - Solar Data: Azimuth Altitude Lux

From Domoticz
Revision as of 10:38, 28 July 2021 by Walter vl (talk | contribs) (→‎Create Virtual device for Output :)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

The forumtopic of this script is here : https://domoticz.com/forum/viewtopic.php?f=72&t=35541

Purpose

The purpose of this script is to calculate in real-time some usefull solar data without any hardware sensor :

·       Azimuth : Angle between the Sun and the north, in degree. (north vector and the perpendicular projection of the sun down onto the horizon)

·       Altitude : Angle of the Sun with the horizon, in degree.

·       Lux : The illuminance, equal to one lumen per square metre. Taking account of the real time cloud layer.

·       Radiation : The amount of Watt per Meter2

·       and many more in the script but not exposed.


Solar Data


The calculation for your Geographical location (altitude, latitude, longitude) is based on the theoretical radiations of the sun according to its position in the sky at the moment, plus the real-time cloud layer and the real-time Atmospheric pressure.

What is it For ?

·       Automation of roller blinds and venetian blind both to optimise the heating and to prevent dazzle.

·       Manage your light according to the Luminosity

·       Use the real civil twilight dawn and dusk (-6 deg altitude) instead of sunrise and sunset.

·       Some calculation about photovoltaic production ?

·       Automate the heating of swimming pool

·       Personally I use a roller shutter to wake me up instead of an alarm clock, with a progressive opening of the roller to avoid glare.


History

The script had a lot of contributors.

Neutrino and I (jmleglise) have adapted to Domoticz a work of Sébastien Joly on an another automation system. Then it was migrate to dzvents.

In the beginning, the script was standalone and requested its data directly via API from a weather service providers. So a lot of forks appeared to adapt it, each time a provider disappeared: weather Underground => darksky => openweatherMap. The last big improvement was to base the input data only on domoticz devices. So It is up to you to feed the input data with the source you want. It is far better. The script is now source independent. And it remains easy because domoticz provide some weather provider in the Hardware section.

I have clean up the mess, merged again the forks, took the best of each of them and pay tribute to their contributors.

Here is the ultimate version. And please, let's avoid forks in the future. If you improve the script, don't do it like a savage. Propose it on the forum and wait for it to be accepted. Otherwise keep it to yourself.

Documentation

You can have a look to the very interesting documentations of the formulae :

·       www.domotique-info.fr/2015/09/ou-est-le-soleil-pour-votre-homecenter-2/

·       www.plevenon-meteo.info/technique/theorie/enso/ensoleillement.html

·       en.wikipedia.org/wiki/Azimuth

·       en.wikipedia.org/wiki/Dawn

Known limitations :

  • Under 1° of Altitude of the sun, the formulae reach their limit of precision. So I have fixed in hard the theoretical value of the Lux at dawn/dusk defined by the Civil twilight : "when the Sun is between 0 and -6 degrees below the horizon, there is enough light for objects to be distinguishable. The sky is bright, even when cloudy."


  • Originally, the script took the cloud cover from www.ogimet.com. To my knowledge, it is the only site in the world that gives access (for free) to synop data provided by all the weather stations in the world. Since then, we have abandoned this and now we get the cloudiness on the weather services integrated in domoticz. But openweatherMap seems to be much less performant on the coverage. The 2 services should be compared and perhaps come back to ogimet.

Installation instructions

The script

The last version of the script is here : https://github.com/jmleglise/mylittle-domoticz/blob/master/LuaScript/SolarData and below :

Put it in an event : Setup/More Options / Events / + / dzvents

Create Virtual device for Output :

The script calculates 4 data. But all are optional. Create the ones you want and enter their idx in the script. If you don't want them, just put nil instead of the idx.

E.g. :

local idxRadiation     = nil


LUX : Virtual sensor type "Lux", name as you want.

Sun Azimuth : Virtual sensor type "custom type" / Y axis = "degrees". You can choose the icon , by editing it in the dashboard.

Sun Altitude : Virtual sensor type "custom type" / Y axis = "degrees". You can choose the icon , by editing it in the dashboard.

Sun Radiation : Virtual sensor type "custom type" Y axis = "Watt/m2". You can choose the icon , by editing it in the dashboard.

Device for the data Input :

The script needs a barometer. (This must be a device of type 'barometer'.) and the cloud cover. (This must be a device of type percentage 0% - 100%).

You can use either :

- a domoticz weather provider. For example: openweatherMap.

The devices are created automatically. Found them in the Setup/devices menu to get their idx.

- your physical devices.

- or your own data sources. (example ogimet for cloud cover)


Note the idx and write them in the script.


FYI, in my case (french city near Paris) , the OpenweatherMap 's cloudiness is really bad compared to the data return by ogimet. Above : OWM = 0% cloud. Ogimet=88% ...


Other parameters

local altitude  = 27            -- Meters above sea level of your location. (Integer)  Can be found from coordinates on https://www.advancedconverter.com/map-tools/find-altitude-by-coordinates

local latitude  = nil     -- Keep nil if you have defined your lat. and long. in the settings. Otherwise you can overwrite it here. E.g. something like 51.748485

local longitude = nil     -- idem. E.g.something like 5.629728.

local logToFile = false   -- Set to true if you also want to log to a file. It might get big by time.

local tmpLogFile = '/tmp/logSun.txt' -- Logging to a file if specified

Enjoy!

I hope you will enjoy it and please, share the uses you will make of it in this forumtopic : https://domoticz.com/forum/viewtopic.php?f=72&t=35541

Source Code

Dzvents Version

--[[
This script calculate in real-time some usefull solar data without any hardware sensor :
·  Azimuth : Angle between the Sun and the north, in degree. (north vector and the perpendicular projection of the sun down onto the horizon)
·  Altitude : Angle of the Sun with the horizon, in degree.
·  Lux : The illuminance, equal to one lumen per square metre. Taking account of the real time cloud layer.

-- Installation & Documentation -----------------------------------------------------------
https://www.domoticz.com/wiki/index.php?title=Lua_dzVents_-_Solar_Data:_Azimuth_Altitude_Lux

-- Prerequisits  -----------------------------------------------------------
	2 sensors with Cloud Cover and Pressure updated
	Requires Domoticz v3.8551 or later.   
	Work with lua 5.3 too.
	No Platform dependent; Linux & Windows


-- Contributors  ----------------------------------------------------------------
	V1.0  - Sébastien Joly - Great original work
	V1.1  - Neutrino - Adaptation to Domoticz
	V1.2  - Jmleglise - An acceptable approximation of the lux below 1° altitude for Dawn and dusk + translation + several changes to be more userfriendly.
	V1.3  - Jmleglise - No update of the Lux data when <=0 to get the sunset and sunrise with lastUpdate
	V1.4  - Jmleglise - use the API instead of updateDevice to update the data of the virtual sensor to be able of using devicechanged['Lux'] in our scripts. (Due to a bug in Domoticz that doesn't catch the devicechanged event of the virtual sensor)
	V1.5  - xces - UTC time calculation.
	      - waaren - pow function for lua 5.3 compatibility
	V2.4  - BakSeeDaa - Converted to dzVents and changed quite many things.
	V2.41 - Oredin - Use Dark Sky API instead of WU API
	V3.0  - Bram Vreugdenhil/Hestia - Weather Api independent. Use OpenWeathermaps devices or your own sensors
	V3.1  - Jmleglise - Merge the different fork. Some clean Up, comment and wiki.
]]--

local scriptName = 'solarData'
local scriptVersion = '3.1'

-- Variables to customize ------------------------------------------------

-- Devices for Input Data
local idxCloudCover = 684       -- device holding cloudcoverage in percentage. Ex : 60%  (for E.g. a device from Openweathermap or darksky)
local idxBarometer  = 675       -- Barometer device. E.g. a device from Openweathermap that contains value in hPa: like a Temp+Humidity+Baro device with : -0.5 C, 59 %, 1023.0 hPa  (for E.g. a device from Openweathermap or your own hardware sensor)

-- Devices for Output Data  (can be nil if you dont want some data)------------------------------------------
local idxSolarAzimuth  = 111    -- Your device. A Virtual customer sensor for Solar Azimuth 
local idxSolarAltitude = 112    -- Your device. A Virtual customer sensor for Solar Altitude
local idxRadiation     = 130    -- Your device. A Virtual customer sensor for Solar Radiation  (in Watt/m2)
local idxLux           = 72     -- Your device. A Virtual customer sensor for Solar Lux

-- Other parameters -----------------------------------------------------
local intervalMins = 5	    -- The interval of running this script. No need to be faster than the data source. (For example it is 10 min)
local altitude  = 27            -- Meters above sea level of your location. (Integer)  Can be found from coordinates on https://www.advancedconverter.com/map-tools/find-altitude-by-coordinates
local latitude  = nil 		    -- Keep nil if you have defined your lat. and long. in the settings. Otherwise you can overwrite it here. E.g. something like 51.748485
local longitude = nil 		    -- idem. E.g.something like 5.629728.
local logToFile = true		  			-- Set to true if you also want to log to a file. It might get big by time. 
local tmpLogFile = '/tmp/logSun.txt'	-- Logging to a file if specified 


-- Don't make any changes below this line (Except for setting logging level) ----------------------------------------------------------------------


return {
	active = true,
	logging = {
--		 level = domoticz.LOG_DEBUG,                                     -- Uncomment to override the dzVents global logging setting
		marker = scriptName..' '..scriptVersion
	},
	on = {
--		devices = {'testSwitch'},                                       -- a switch for testing w/o waiting minutes
		timer = {'every '..tostring(intervalMins)..' minutes'}       -- There is no more limit to worry about as there is no API called
	},

	execute = function(domoticz, device)

		local function leapYear(year)   
			return year%4==0 and (year%100~=0 or year%400==0)
		end

		function math.pow(x, y)                                         -- Function math.pow(x, y) has been deprecated in Lua 5.3. 
			return x^y 
		end		
		
        if latitude == nil then
            latitude = domoticz.settings.location.latitude
        end
    
        if longitude == nil then
            longitude = domoticz.settings.location.longitude
        end		
		
		local arbitraryTwilightLux = 6.32                               -- W/m² egal 800 Lux (the theoritical value is 4.74 but I have more accurate result with 6.32...)
		local constantSolarRadiation = 1361                             -- Solar Constant W/m²

		local relativePressure = domoticz.devices(idxBarometer).barometer

		local year = os.date('%Y')
		local numOfDay = os.date('%j')
		local nbDaysInYear = (leapYear(year) and 366 or 365)

		local angularSpeed = 360/365.25
		local declination = math.deg(math.asin(0.3978 * math.sin(math.rad(angularSpeed) *(numOfDay - (81 - 2 * math.sin((math.rad(angularSpeed) * (numOfDay - 2))))))))
		local timeDecimal = (os.date('!%H') + os.date('!%M') / 60)      -- Coordinated Universal Time  (UTC)
		local solarHour = timeDecimal + (4 * longitude / 60 )           -- The solar Hour
		local hourlyAngle = 15 * ( 12 - solarHour )                     -- hourly Angle of the sun
		local sunAltitude = math.deg(math.asin(math.sin(math.rad(latitude))* math.sin(math.rad(declination)) + math.cos(math.rad(latitude)) * math.cos(math.rad(declination)) * math.cos(math.rad(hourlyAngle))))-- the height of the sun in degree, compared with the horizon

		local sunAzimuth = math.acos((math.sin(math.rad(declination)) - math.sin(math.rad(latitude)) * math.sin(math.rad(sunAltitude))) / (math.cos(math.rad(latitude)) * math.cos(math.rad(sunAltitude) ))) * 180 / math.pi -- deviation of the sun from the North, in degree
		local sinAzimuth = (math.cos(math.rad(declination)) * math.sin(math.rad(hourlyAngle))) / math.cos(math.rad(sunAltitude))
		if(sinAzimuth<0) then sunAzimuth=360-sunAzimuth end
		local sunstrokeDuration = math.deg(2/15 * math.acos(- math.tan(math.rad(latitude)) * math.tan(math.rad(declination))))  -- duration of sunstroke in the day . Not used in this calculation.
		local RadiationAtm = constantSolarRadiation * (1 +0.034 * math.cos( math.rad( 360 * numOfDay / nbDaysInYear )))         -- Sun radiation  (in W/m²) in the entrance of atmosphere.

		-- Coefficient of mitigation M
		local absolutePressure = relativePressure - domoticz.utils.round((altitude/ 8.3),1) -- hPa
		local sinusSunAltitude = math.sin(math.rad(sunAltitude))
		local M0 = math.sqrt(1229 + math.pow(614 * sinusSunAltitude,2)) - 614 * sinusSunAltitude
		local M = M0 * relativePressure/absolutePressure

		domoticz.log('', domoticz.LOG_INFO)
		domoticz.log('==================   '..scriptName..' V'..scriptVersion..'   ==================', domoticz.LOG_INFO)
		domoticz.log('Altitude:'..tostring(altitude)..', latitude: ' .. latitude .. ', longitude: ' .. longitude, domoticz.LOG_INFO)
		domoticz.log('Angular Speed = ' .. angularSpeed .. ' per day', domoticz.LOG_DEBUG)
		domoticz.log('Declination = ' .. declination .. '°', domoticz.LOG_DEBUG)
		domoticz.log('Universal Coordinated Time (UTC) '.. timeDecimal ..' H.dd', domoticz.LOG_DEBUG)
		domoticz.log('Solar Hour '.. solarHour ..' H.dd', domoticz.LOG_DEBUG)
		domoticz.log('Altitude of the sun = ' .. sunAltitude .. '°', domoticz.LOG_INFO)
		domoticz.log('Angular hourly = '.. hourlyAngle .. '°', domoticz.LOG_DEBUG)
		domoticz.log('Azimuth of the sun = ' .. sunAzimuth .. '°', domoticz.LOG_INFO)
		domoticz.log('Duration of the sun stroke of the day = ' .. domoticz.utils.round(sunstrokeDuration,2) ..' H.dd', domoticz.LOG_DEBUG)
		domoticz.log('Radiation max in atmosphere = ' .. domoticz.utils.round(RadiationAtm,2) .. ' W/m²', domoticz.LOG_DEBUG)
		domoticz.log('Local relative pressure = ' .. relativePressure .. ' hPa', domoticz.LOG_DEBUG)
		domoticz.log('Absolute pressure in atmosphere = ' .. absolutePressure .. ' hPa', domoticz.LOG_DEBUG)
		domoticz.log('Coefficient of mitigation M = ' .. M ..' M0 = '..M0, domoticz.LOG_DEBUG)
		domoticz.log('', domoticz.LOG_INFO)
		
		-- In meteorology, an okta is a unit of measurement used to describe the amount of cloud cover
		-- at any given location such as a weather station. Sky conditions are estimated in terms of how many
		-- eighths of the sky are covered in cloud, ranging from 0 oktas (completely clear sky) through to 8 oktas
		-- (completely overcast). In addition, in the synop code there is an extra cloud cover indicator '9'
		-- indicating that the sky is totally obscured (i.e. hidden from view),
		-- usually due to dense fog or heavy snow.

        Cloudpercentage = domoticz.devices(idxCloudCover).percentage
		
		okta = Cloudpercentage/12.5
		
		local Kc = 1-0.75*math.pow(okta/8,3.4)      -- Factor of mitigation for the cloud layer

		local directRadiation, scatteredRadiation, totalRadiation, Lux, weightedLux
		if sunAltitude > 1 then                     -- Below 1° of Altitude , the formulae reach their limit of precision.
			directRadiation = RadiationAtm * math.pow(0.6,M) * sinusSunAltitude
			scatteredRadiation = RadiationAtm * (0.271 - 0.294 * math.pow(0.6,M)) * sinusSunAltitude
			totalRadiation = scatteredRadiation + directRadiation
			Lux = totalRadiation / 0.0079           -- Radiation in Lux. 1 Lux = 0,0079 W/m²
			weightedLux = Lux * Kc                  -- radiation of the Sun with the cloud layer
		elseif sunAltitude <= 1 and sunAltitude >= -7  then -- apply theoretical Lux of twilight
			directRadiation = 0
			scatteredRadiation = 0
			arbitraryTwilightLux=arbitraryTwilightLux-(1-sunAltitude)/8*arbitraryTwilightLux
			totalRadiation = scatteredRadiation + directRadiation + arbitraryTwilightLux 
			Lux = totalRadiation / 0.0079           -- Radiation in Lux. 1 Lux = 0,0079 W/m²
			weightedLux = Lux * Kc                  -- radiation of the Sun with the cloud layer
		elseif sunAltitude < -7 then                -- no management of nautical and astronomical twilight...
			directRadiation = 0
			scatteredRadiation = 0
			totalRadiation = 0
			Lux = 0
			weightedLux = 0                         --  Lux for the nautic twilight should be around 3,2. I prefer to get 0.
		end

        totalRadiation=totalRadiation*Kc
        
		domoticz.log('Okta = '..okta.. ' Cloud coverage = ' ..Cloudpercentage .. '%', domoticz.LOG_INFO)
		domoticz.log('Kc = ' .. Kc, domoticz.LOG_DEBUG)
		domoticz.log('Direct Radiation = '.. domoticz.utils.round(directRadiation,2) ..' W/m²', domoticz.LOG_INFO)
		domoticz.log('Scattered Radiation = '.. domoticz.utils.round(scatteredRadiation,2) ..' W/m²', domoticz.LOG_DEBUG)
		domoticz.log('Total radiation = ' .. domoticz.utils.round(totalRadiation,2) ..' W/m²', domoticz.LOG_INFO)
		domoticz.log('Total Radiation in lux = '.. domoticz.utils.round(Lux,2)..' Lux', domoticz.LOG_DEBUG)
		domoticz.log('Total weighted lux  = '.. domoticz.utils.round(weightedLux,2)..' Lux', domoticz.LOG_INFO)

		-- No update if Lux is already 0. So lastUpdate of the Lux sensor will keep the time when Lux has reached 0.
		-- (Kind of timeofday['SunsetInMinutes'])
		if idxLux and domoticz.devices(idxLux).lux + domoticz.utils.round(weightedLux, 0) > 0 then
			domoticz.devices(idxLux).updateLux(domoticz.utils.round(weightedLux,0))
		end
		if idxSolarAzimuth then
		   domoticz.devices(idxSolarAzimuth).updateCustomSensor(domoticz.utils.round(sunAzimuth,0))
		end   
		if idxSolarAltitude then
		   domoticz.devices(idxSolarAltitude).updateCustomSensor(domoticz.utils.round(sunAltitude,0))
		end
		-- No update if radiation is already 0. See LUX
		if idxRadiation and (domoticz.devices(idxRadiation).rawData[1] + domoticz.utils.round(totalRadiation, 2) > 0) then 
		    domoticz.devices(idxRadiation).updateCustomSensor(domoticz.utils.round(totalRadiation,2))
		end
		if logToFile then
			local logDebug = os.date('%Y-%m-%d %H:%M:%S',os.time())
			logDebug=logDebug..' Azimuth:' .. sunAzimuth .. ' Altitude:' .. sunAltitude
			logDebug=logDebug..' Okta:' .. okta..'  KC:'.. Kc
			logDebug=logDebug..' Direct:'..directRadiation..' inDirect:'..scatteredRadiation..' TotalRadiation:'..totalRadiation..' LuxCloud:'.. domoticz.utils.round(weightedLux,2)
			os.execute('echo '..logDebug..' >>'..tmpLogFile)  -- compatible Linux & Windows
		end
	end
}

Plain Lua Version

And for Archive, one of the last Lua version

    --[[      Virtual Lux sensor and other real-time solar data

    ~/domoticz/var/scripts/lua/script_time_SolarSensor.lua

    -- Autors  ----------------------------------------------------------------
    V1.0 - Sébastien Joly - Great original work
    V1.1 - Neutrino - Adaptation to Domoticz
    V1.2 - Jmleglise - An acceptable approximation of the lux below 1° altitude for Dawn and dusk + translation + several changes to be more userfriendly.
    V1.3 - Jmleglise - No update of the Lux data when <=0 to get the sunset and sunrise with lastUpdate
    V1.4 - use the API instead of updateDevice to update the data of the virtual sensor to be able of using devicechanged['Lux'] in our scripts. (Due to a bug in Domoticz that doesn't catch the devicechanged event of the virtual sensor)
    V1.5 - Marco Pietersen - Reverted use of API, introduced WU dependency
    ]]--

    -- Variables to customize ------------------------------------------------
       local latitude = 52.134028          -- your home
       local longitude = 5.420183          -- your home
       local altitude = 27                 -- Your home altitude (get from Weather Underground or other source)
       local wu_deviceName='Outside Temperature' -- Your Weather Underground device name that shows the Barometer value
       local idxLux ='16'                   -- Your virtual Lux Device ID
       local idxSolarAzimuth ='7'          -- Your virtual Azimuth Device ID
       local idxSolarAltitude ='8'         -- Your virtual Solar Altitude Device ID
       local idxUserVarOcta='1'            -- Your user variable ID , named octa
       local WMOID = '06260'               -- Your nearest SYNOP Station for ogimet. Very important !
       local DEBUG = 0			           -- 0 , 1 for domoticz log , 2 for file log

    -- Below, edit at your own risk ------------------------------------------

    function leapYear(year)   
       return year%4==0 and (year%100~=0 or year%400==0)
    end

    function split(s, delimiter)   
       result = {};
       for match in (s..delimiter):gmatch("(.-)"..delimiter) do
         table.insert(result, match);
       end
       return result;
    end

    function round(num, dec)
       if num == 0 then
         return 0
       else
         local mult = 10^(dec or 0)
         return math.floor(num * mult + 0.5) / mult
       end
    end

    commandArray = {}

    time = os.date("*t")
    if  ((time.min % 5)==0)  then -- Run every 5 minutes. 

       local arbitraryTwilightLux=6.32     -- W/m² egal 800 Lux     (the theoritical value is 4.74 but I have more accurate result with 6.32...)
       local constantSolarRadiation = 1361 -- Solar Constant W/m²
       
       if (uservariables['octa'] == nil) then print("Error : Did you create the Uservariable octa ?") end
        sWeatherTemp, sWeatherHumidity, sHumFeelsLike, sWeatherPressure = otherdevices_svalues[wu_deviceName]:match("([^;]+);([^;]+);([^;]+);([^;]+);([^;]+)")
        sWeatherTemp = tonumber(sWeatherTemp)
        sWeatherHumidity = tonumber(sWeatherHumidity)
        relativePressure = tonumber(sWeatherPressure)
        if (DEBUG == 1) then print('WU Script Parsed Temp=' .. sWeatherTemp .. ' Humidity=' .. sWeatherHumidity .. ' Pressure=' .. relativePressure) end

       ----------------------------------
       local year = os.date("%Y")
       local numOfDay = os.date("%j")
       if  leapYear(year) == true then   
          nbDaysInYear = 366  -- How many days in the year ?
       else
          nbDaysInYear = 365
       end

       angularSpeed = 360/365.25
       local Declinaison = math.deg(math.asin(0.3978 * math.sin(math.rad(angularSpeed) *(numOfDay - (81 - 2 * math.sin((math.rad(angularSpeed) * (numOfDay - 2))))))))
       timeDecimal = (os.date("!%H") + os.date("!%M") / 60) -- Coordinated Universal Time  (UTC)
       solarHour = timeDecimal + (4 * longitude / 60 )    -- The solar Hour
       hourlyAngle = 15 * ( 12 - solarHour )          -- hourly Angle of the sun
       sunAltitude = math.deg(math.asin(math.sin(math.rad(latitude))* math.sin(math.rad(Declinaison)) + math.cos(math.rad(latitude)) * math.cos(math.rad(Declinaison)) * math.cos(math.rad(hourlyAngle))))-- the height of the sun in degree, compared with the horizon
          
       local azimuth = math.acos((math.sin(math.rad(Declinaison)) - math.sin(math.rad(latitude)) * math.sin(math.rad(sunAltitude))) / (math.cos(math.rad(latitude)) * math.cos(math.rad(sunAltitude) ))) * 180 / math.pi -- deviation of the sun from the North, in degree
       local sinAzimuth = (math.cos(math.rad(Declinaison)) * math.sin(math.rad(hourlyAngle))) / math.cos(math.rad(sunAltitude))
       if(sinAzimuth<0) then azimuth=360-azimuth end
       sunstrokeDuration = math.deg(2/15 * math.acos(- math.tan(math.rad(latitude)) * math.tan(math.rad(Declinaison)))) -- duration of sunstroke in the day . Not used in this calculation.
       RadiationAtm = constantSolarRadiation * (1 +0.034 * math.cos( math.rad( 360 * numOfDay / nbDaysInYear )))    -- Sun radiation  (in W/m²) in the entrance of atmosphere.
       -- Coefficient of mitigation M
       absolutePressure = relativePressure - round((altitude/ 8.3),1) -- hPa
       sinusSunAltitude = math.sin(math.rad(sunAltitude))
       M0 = math.sqrt(1229 + math.pow(614 * sinusSunAltitude,2)) - 614 * sinusSunAltitude
       M = M0 * relativePressure/absolutePressure

       if (DEBUG == 1) then
          print('<b style="color:Blue"==============  SUN  LOG ==================</b>')
          print(os.date("%Y-%m-%d %H:%M:%S", os.time()))
          print("latitude:" .. latitude .. ", longitude:" .. longitude)
          print("Home altitude = " .. tostring(altitude) .. " m")
          print("number Of Day = " .. numOfDay)     
          if nbDaysInYear==366 then
             print(year .." is a leap year !")
          else
             print(year.." is not a leap year")
          end
          print("Angular Speed = " .. angularSpeed .. " per day")
          print("Declinaison = " .. Declinaison .. "°")
          print("Universel Coordinated Time (UTC)".. timeDecimal .." H.dd")
          print("Solar Hour ".. solarHour .." H.dd")
          print("Altitude of the sun = " .. sunAltitude .. "°")
          print("Angular hourly = ".. hourlyAngle .. "°")
          print("Azimuth of the sun = " .. azimuth .. "°")
          print("Duration of the sunstroke of the day = " .. round(sunstrokeDuration,2) .." H.dd")  -- not used
          print("Radiation max in atmosphere = " .. round(RadiationAtm,2) .. " W/m²")
          print("Local relative pressure = " .. relativePressure .. " hPa")
          print("Absolute pressure in atmosphere = " .. absolutePressure .. " hPa")
          print("Coefficient of mitigation M = " .. M .." M0:"..M0)
       end

       -- Get  SYNOP  message from  Ogimet web  site
       hourUTCminus1 = os.date("!%H")-1
       if string.len(hourUTCminus1) == 1 then
          hourUTCminus1 = "0" .. hourUTCminus1
       end
       UTC = os.date("%Y%m%d").. hourUTCminus1.."00" -- os.date("!%M")
       if (DEBUG == 1) then
          --local WMOID = jsonLocation.current_observation.display_location.wmo
       end
       
       cmd='curl "http://www.ogimet.com/cgi-bin/getsynop?block='..WMOID..'&begin='..UTC..'"'
       if(DEBUG == 1) then print(cmd) end
       local ogimet=assert(io.popen(cmd))
       local synop = ogimet:read('*all')
       ogimet:close()
          
       if synop ~= nil
       then   
       	  rslt = split(synop,",")
          CodeStation = rslt[1]
          rslt = split(synop, " "..CodeStation.. " ")
          Trame = string.gsub(rslt[2], "=", "")
          Trame = CodeStation .." ".. Trame
          rslt = split(Trame, " ")
          Octa = string.sub(rslt[3], 1, 1)  -- 3rd char is the cloud layer.  0=no cloud , 1-8= cloudy from 1 to 8 max , 9 =Fog , / = no data
          
    		if(DEBUG == 1) then 
    			print('ogimet:'..synop ) 
    			print('ogimet Octa: '..Octa )     			    			
    		end   		
    	  
          if Octa == "/" then Octa = uservariables['octa'] end -- not defined ? take the previous value
          if Octa == "9" then Octa = 8 end
       else
          Octa = uservariables['octa']
       	  print('SolarSensor did not receive ogimet data. Reverting to last known value:'..Octa)
       end

       --os.execute('curl "http://127.0.0.1:8081/json.htm?type=command&param=updateuservariable&idx='..idxUserVarOcta..'&vname=octa&vtype=0&vvalue='..tostring(Octa)..'"')
       commandArray[#commandArray + 1] = {['Variable:octa'] = tostring(Octa)}
       
       Kc=1-0.75*math.pow(Octa/8,3.4)  -- Factor of mitigation for the cloud layer

       if sunAltitude > 1 then -- Below 1° of Altitude , the formulae reach their limit of precision.
          directRadiation = RadiationAtm * math.pow(0.6,M) * sinusSunAltitude
          scatteredRadiation = RadiationAtm * (0.271 - 0.294 * math.pow(0.6,M)) * sinusSunAltitude
          totalRadiation = scatteredRadiation + directRadiation
          Lux = totalRadiation / 0.0079  -- Radiation in Lux. 1 Lux = 0,0079 W/m²
          weightedLux = Lux * Kc   -- radiation of the Sun with the cloud layer
       elseif sunAltitude <= 1 and sunAltitude >= -7  then -- apply theoretical Lux of twilight
          directRadiation = 0
          scatteredRadiation = 0
          arbitraryTwilightLux=arbitraryTwilightLux-(1-sunAltitude)/8*arbitraryTwilightLux
          totalRadiation = scatteredRadiation + directRadiation + arbitraryTwilightLux 
          Lux = totalRadiation / 0.0079  -- Radiation in Lux. 1 Lux = 0,0079 W/m²
          weightedLux = Lux * Kc   -- radiation of the Sun with the cloud layer
       elseif sunAltitude < -7 then  -- no management of nautical and astronomical twilight...
          directRadiation = 0
          scatteredRadiation = 0
          totalRadiation = 0
          Lux = 0
          weightedLux = 0  --  should be around 3,2 Lux for the nautic twilight. Nevertheless.
       end
       
       if (DEBUG == 1) then   
          print("Station SYNOP = " .. WMOID)
          print( Octa .. " Octa")
          print("Kc = " .. Kc)
          print("Direct Radiation = ".. round(directRadiation,2) .." W/m²")
          print("Scattered Radiation = ".. round(scatteredRadiation,2) .." W/m²")
          print("Total radiation = " .. round(totalRadiation,2) .." W/m²")
          print("Total Radiation in lux = ".. round(Lux,2).." Lux")
          print("and at last, Total weighted lux  = ".. round(weightedLux,2).." Lux")   
        end

	if tonumber(otherdevices_svalues['Lux'])+round(weightedLux,0)>0   -- No update if Lux is already 0. So lastUpdate of the Lux sensor will keep the time when Lux has reached 0. (Kind of timeofday['SunsetInMinutes'])
	then
		commandArray[#commandArray + 1] = {['UpdateDevice'] = idxLux..'|0|'..tostring(round(weightedLux,0))}    --  THis form is not recommended. due to limitation of the eventsystem of Domoticz
	end
	commandArray[#commandArray + 1] = {['UpdateDevice'] = idxSolarAzimuth..'|0|'..tostring(round(azimuth,0))} 
	commandArray[#commandArray + 1] = {['UpdateDevice'] = idxSolarAltitude..'|0|'..tostring(round(sunAltitude,0))}
       
       if (DEBUG == 2) then
          logDebug=os.date("%Y-%m-%d %H:%M:%S",os.time())
          logDebug=logDebug.." Azimuth:" .. azimuth .. " Height:" .. sunAltitude
          logDebug=logDebug.." Octa:" .. Octa.."  KC:".. Kc
          logDebug=logDebug.." Direct:"..directRadiation.." inDirect:"..scatteredRadiation.." TotalRadiation:"..totalRadiation.." LuxCloud:".. round(weightedLux,2)
          os.execute('echo '..logDebug..' >>logSun.txt')  -- compatible Linux & Windows
       end
    end
    return commandArray