Soleil.lua
Code : Tout sélectionner
--[[
Prerequisits
==================================
Domoticz v3.8837 or later (dzVents version 2.4 or later)
CHANGE LOG: See http://www.domoticz.com/forum/viewtopic.php?t=19220
Virtual Lux sensor and other real-time solar data
-- Authors ----------------------------------------------------------------
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 - xces - UTC time calculation.
V2.0 - BakSeeDaa - Converted to dzVents and changed quite many things.
V2.5 - Utilisation de Open Weather Map
]]--
-- Variables to customize ------------------------------------------------
local countryCode = 'FR' -- Country code for Wunderground API
local idxSolarAzimuth = 304 -- (Integer) Virtual Azimuth Device ID
local idxSolarAltitude = 305 -- (Integer) Your virtual Solar Altitude Device I
local OWMAPIkey = 'xxxxxxxxxxxxxxxxxxxxxxxxx' -- openweathermap API Key
local WMOID = '07300' -- (String) Nearest synop station for ogimet.
local logToFile = false -- (Boolean) Set to true if you also wish to log to a file. It might get big by time.
local tmpLogFile = '/var/tmp/logSun.txt'-- Logging to this file if logging to file is activated
local fetchIntervalDayMins = 4 -- Day time scraping interval. Never set this below 4 if you have a free WU API account.
local fetchIntervalNightMins = 30 -- Night time scraping interval.
-- Optional Domoticz devices
local idxLux = 303 -- (Integer) Domoticz virtual Lux device ID
--local idxCloudCover = 555 -- (Integer) Domoticz Cloud Cover (PERCENTAGE TYPE) sensor device ID
local latitude = 46.83333206 -- Latitude. (Decimal number) Decimal Degrees. E.g. something like 50.748485
local longitude = -1.76666701 -- Longitude. (Decimal number) Decimal Degrees. E.g.something like 12.629728.
local altitude = 61 -- Altitude. (Integer) Meters above sea level.
local warnNoCloudDataHours = 24 -- Warn limit (hours) if no cloud cover report has been received.
-- Please don't make any changes below this line (Except for setting logging level)
local scriptName = 'solarData'
local scriptVersion = '2.4.1'
return {
active = true,
logging = {
level = domoticz.LOG_DEBUG, -- Uncomment to override the dzVents global logging setting
marker = scriptName..' '..scriptVersion
},
on = {
timer = {
'every '..tostring(fetchIntervalDayMins)..' minutes at daytime',
'every '..tostring(fetchIntervalNightMins)..' minutes at nighttime',
},
httpResponses = {
scriptName..'Ogimet',
scriptName..'OWM',
},
},
data = {
lastOkta = {initial=0},
lastOgimetTime = {initial='198001010000'}
},
execute = function(domoticz, item)
if item.isTimer then
local url = 'https://api.openweathermap.org/data/2.5/weather?lat='..latitude..'&lon='..longitude..'&appid='..OWMAPIkey
domoticz.log('Requesting new weather data from openweathermap...', domoticz.LOG_DEBUG)
domoticz.openURL({url = url, method = 'GET', callback = scriptName..'OWM'}).afterSec(10)
local ogimetDelay = 1140 -- Minimum anticipated Ogimet data lag (19 minutes)
local qOgimetTime = os.date('!%Y%m%d%H', os.time()- ogimetDelay)..'00'
local lastOgimetTime = domoticz.data.lastOgimetTime
if qOgimetTime > lastOgimetTime then
-- There might be recent ogimet data to fetch
qOgimetTime = os.date('!%Y%m%d%H', os.time()-(12*3600+ogimetDelay))..'00' -- Twelve hours of data
if domoticz.data.lastOgimetTime > qOgimetTime then
qOgimetTime = domoticz.data.lastOgimetTime:sub(1, -2)..'1' -- Add 1 minute to it
end
-- Get synopCode (surface synopCodetic observations) message from Ogimet web site
url ='http://www.ogimet.com/cgi-bin/getsynop?block='..WMOID..'&begin='..qOgimetTime
domoticz.log('Requesting new cloud cover data from Ogimet...', domoticz.LOG_DEBUG)
domoticz.openURL({url = url, method = 'GET', callback = scriptName..'Ogimet'}).afterSec(5)
else
domoticz.log('No need to request new cloud cover data from Ogimet. Using old data with UTC time stamp: '..lastOgimetTime, domoticz.LOG_DEBUG)
end
end
if not item.isHTTPResponse then return end
local response = item
if (not response.ok) or (not response.isJSON and response.trigger ~= scriptName..'Ogimet') then
domoticz.log('Last http response was not what expected. Trigger: '..response.trigger, domoticz.LOG_ERROR)
domoticz.log('Status Code: '..response.statusCode, domoticz.LOG_ERROR)
domoticz.log('ok: '..tostring(response.ok), domoticz.LOG_ERROR)
domoticz.log('isJSON: '..tostring(response.isJSON), domoticz.LOG_ERROR)
domoticz.log(response.data, domoticz.LOG_ERROR)
return
end
if response.trigger == scriptName..'Ogimet' then
domoticz.log('Ogimet data has been received', domoticz.LOG_DEBUG)
local function split(s, delimiter)
local result = {}
for match in (s..delimiter):gmatch('(.-)'..delimiter) do
table.insert(result, match)
end
return result
end
-- 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.
-- Find the okta value for the last valid line in the response
-- The response may contain multiple rows and some of them may not be valid.
local okta, ogimetTime
for line in response.data:gmatch("[^\r\n]+") do
if line == nil then break end
if (string.find(line,'NIL=') == nil)
and (string.find(line,'Status: 500') == nil)
and (string.find(line, WMOID) ~= nil) then
local s = split(line, ',')
if s and #s >= 7 then
local x = string.sub(split(s[7], ' ')[5], 1, 1)
if x ~= '/' then
okta = x
ogimetTime = s[2]..s[3]..s[4]..s[5]..s[6]
end
end
end
end
local lastOgimetTime = domoticz.data.lastOgimetTime
if (not ogimetTime) or (lastOgimetTime >= ogimetTime) then
local lastOkta = domoticz.data.lastOkta
domoticz.log('Using the saved Okta value: '..lastOkta..' with UTC timestamp: '..lastOgimetTime, domoticz.LOG_DEBUG)
okta = lastOkta
-- No cloud data. Shall we throw an error?
local tLastOgimetTime = os.time({ year = tonumber(lastOgimetTime:sub(1,4)), month = tonumber(lastOgimetTime:sub(5,6)),
day = tonumber(lastOgimetTime:sub(7,8)), hour = tonumber(lastOgimetTime:sub(9,10)), min = tonumber(lastOgimetTime:sub(11,12)) })
local elapsed_time = os.difftime(os.time(os.date("!*t")), tLastOgimetTime)
if elapsed_time >= 3600 * warnNoCloudDataHours then
domoticz.log('We\'ve got no cloud data from WMOID: '..WMOID..' for more than '..tostring(domoticz.utils.round(elapsed_time/3600))..' hours. Maybe you should look for a more reliable weather station to query. Read the Wiki how to do that.', domoticz.LOG_ERROR)
end
else
okta = okta == '9' and 8 or okta
-- We store the last fetched value here to be used as a backup value
domoticz.log('Using the newly fetched Okta value: '..okta..' with UTC timestamp: '..ogimetTime, domoticz.LOG_DEBUG)
domoticz.data.lastOkta = okta
domoticz.data.lastOgimetTime = ogimetTime
end
return
end
if response.trigger ~= scriptName..'OWM' then return end
domoticz.log('openweathermap API json data has been received'..response.data, domoticz.LOG_DEBUG)
local function leapYear(year)
return year%4==0 and (year%100~=0 or year%400==0)
end
local OWMAPIData = response.json
if not OWMAPIData then
domoticz.log('Could not find any OWMAPIData in the OWM API response', domoticz.LOG_ERROR)
return
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²
-- In case of that latitude, longitude and altitude has not been defined in the configuration,
-- we simply use the values that is returned for the current observation location.
-- Reading longitude, latitude and altitude from the observation_location instead of from
-- display_location. API documentation is not so clear about what display_location is.
--if not latitude then latitude = wuAPIData.current_observation.observation_location.latitude end
--if not longitude then longitude = wuAPIData.current_observation.observation_location.longitude end
--if not altitude then
-- altitude = wuAPIData.current_observation.observation_location.elevation
-- altitude = domoticz.utils.round(tonumber((altitude:gsub('[%a%s]',''))) / 3.2808, 1)
--end
--local WULocWMO = wuAPIData.current_observation.display_location.wmo
local relativePressure = OWMAPIData.main.pressure -- if you have an another way to get the Pressure, (local barometer ...) then you may optimize the script and avoid the call to api.openweathermap)
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 azimuth = 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 azimuth=360-azimuth 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 M0 = math.sqrt(1229 + ((614 * sinusSunAltitude)^2)) - 614 * sinusSunAltitude
local M = M0 * relativePressure/absolutePressure
domoticz.log('', domoticz.LOG_INFO)
domoticz.log('============== SUN LOG ==================', domoticz.LOG_INFO)
--domoticz.log(city .. ', latitude: ' .. latitude .. ', longitude: ' .. longitude, domoticz.LOG_INFO)
domoticz.log('Home altitude = ' .. tostring(altitude) .. ' m', domoticz.LOG_DEBUG)
--domoticz.log('WU Location WMO = ' .. WULocWMO, 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 = ' .. azimuth .. '°', 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)
local function split(s, delimiter)
local result = {}
for match in (s..delimiter):gmatch('(.-)'..delimiter) do
table.insert(result, match)
end
return result
end
local okta = domoticz.data.lastOkta
--local Kc = 1-0.75*math.pow(okta/8,3.4) -- Factor of mitigation for the cloud layer
local Kc = 1-0.75*((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
directRadiation = RadiationAtm * 0.6^M * sinusSunAltitude
--scatteredRadiation = RadiationAtm * (0.271 - 0.294 * math.pow(0.6,M)) * sinusSunAltitude
scatteredRadiation = RadiationAtm * (0.271 - 0.294 * 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
domoticz.log('SYNOP Station = ' .. WMOID, domoticz.LOG_INFO)
domoticz.log('Okta = '..okta, 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_DEBUG)
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
domoticz.devices(idxSolarAzimuth).updateCustomSensor(domoticz.utils.round(azimuth,0))
domoticz.devices(idxSolarAltitude).updateCustomSensor(domoticz.utils.round(sunAltitude,0))
local oktaPercent = domoticz.utils.round(okta*100/8)
local fetchIntervalMins = (domoticz.time.matchesRule('at daytime') and fetchIntervalDayMins or fetchIntervalNightMins)
if idxCloudCover and ((domoticz.devices(idxCloudCover).percentage ~= oktaPercent)
or (domoticz.devices(idxCloudCover).lastUpdate.minutesAgo >= (60 - fetchIntervalMins))) then
domoticz.devices(idxCloudCover).updatePercentage(oktaPercent)
end
if logToFile then
local logDebug = os.date('%Y-%m-%d %H:%M:%S',os.time())
logDebug=logDebug..' Azimuth:' .. azimuth .. ' Height:' .. 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
}
Bonjour à tous,
Suite à la demande de l'un d'entre vous, je vais partager mon script qui calcule de manière théorique la luminosité extérieure.
Je n'ai rien inventé, juste adapté le script de Sebastien Joly dont les explications sont ici : http://www.domotique-info.fr/2015/09/ou ... ecenter-2/
Ce script renvoie une valeur théorique en Lux de la luminosité extérieure basée sur la longitude, la latitude, le jour de l'année et la nébulosité.
Au début, j'avais tout mis dans le même script LUA, mais Domoticz avait tendance à figer pendant quelques secondes à chaque calcul, rendant l'interface lourde.
J'ai donc séparé le lancement et la récupération des données dans 2 scripts LUA.
Il faut d'abord créer une variable utilisateur nommée 'octa' de type entier. Vous pouvez l'initialiser à 0.
---SCRIPTS SUPPRIME -----
C'est marrant de voir enfin la luminosité remonter