Still in test but ready enough to play with. Have fun !
Code: Select all
local scriptVersion = '0.201911091100'
local scriptVar = 'ObserverIP_' .. scriptVersion
--[[
this dzVents script is used to collect data from ObserverIP weatherstation with software version 4.5.7. (weatherlogger 2.2.2.)
Because there is no known API / JSON interface at the time this script was created, it uses a call to the Web Gui and interprets the returned HTML code.
This code is likely to change when a new version is released. If you encounter an incompatibility between this version of the script and a new version of the ObserverIP software then please report this on the domoticz forum.
Also post there if you have a requirement to expose more of the available information to domoticz (either in a device or a uservariable)
Before activating the script:
please read the GETTING STARTED section of the dzVents wiki.
change xxx.xxx.xxx.xxx in this script to the IP of your weatherStation
define a dummy hardware in domoticz if not already done
define virtual sensors in domoticz for this dummy hardware
type Will show
wind windspeed, windgust, winddirection (numeric and string), temperature, windchill temperature
temperature indoor temperature
temp/humidity indoor temperature, indoor humidity, indoor humidity status
temperature outdoor temperature
temp/humidity outdoor temperature, outdoor humidity, outdoor humidity status
solarRadiation solar Radiation
UV index UV index
rain Rainrate in mm/h and amount of rain.
barometer air pressure in HPA
dailyRain Daily amount of rain in mm ( define as general Custom sensor with x-as mm )
weeklyRain Weekly amount of rain in mm ( define as general Custom sensor with x-as mm )
monthlyRain Monthly amount of rain in mm ( define as general Custom sensor with x-as mm )
yearlyRain Yearly amount of rain in mm ( define as general Custom sensor with x-as mm )
gustSpeed Gust speed ( define as general Custom sensor with x-as m/sec )
avgWind Wind speed ( define as general Custom sensor with x-as m/sec )
and change the names in the device declarations between the marked lines below.
these devices will overtime keep an history of the collected data that can be viewed by pressing the log button on the device.
History
========
20190828 Start coding
20180829 Start testing (thanks @eddieb !) based on prototype
20190901 Added comments and history
20190904 Added option to get a notification when time since last device update is too long
20191016 Fixed wrong link between names and values
20191017 Added updates of custom sensors for daily-, weekly-, monthly- and yearly rain
20191017 Fixed typos (wrong case in table)
20191018 Added option to add separate custom sensors for gustSpeed and average Wind
20191030 Added option for script to be started by a device.
20191109 Replaced math.pow(x,y) with x^y (Lua 5.3 deprecated math.pow)
]]--
return
{
on =
{
devices = {'your trigger device name here' }, -- Script can be triggered by a device
timer = { 'every 5 minutes' }, -- or by a timer-rule
-- timer = { 'every 1 minutes' }, --
httpResponses = { scriptVar }, -- do not change this
},
logging =
{
level = domoticz.LOG_DEBUG, -- change to LOG_ERROR when everything works as expected
marker = scriptVar,
},
data =
{
rainTotal = { initial = 0 },
previousRainSample = { initial = 0 },
},
execute = function(dz, item)
-- ********** local settings below this line *************
-- change to IP of your weatherstation
local weatherStationIP = 'xxx.xxx.xxx.xxx'
-- change to number of seconds after you want to receive a notification that the response of the website is unexpected long
-- set to -1 if you never want to be notified
local maxResponseSeconds = 720
-- Change devicenames below to reflect your names and uncomment the lines with the devices you defined.
-- You can also use idx but if you do, then don't use the surrounding quotes.
-- local wind = dz.devices('AWindDir')
-- local insideTemperature = dz.devices('AinTemp')
-- local insideTemperatureHumidity = dz.devices('AinTempHum')
-- local outsideTemperature = dz.devices('AoutTemp')
-- local outsideTemperatureHumidity = dz.devices('AoutTempHum')
-- local solarRadiation = dz.devices('ASolarRad')
-- local uvIndex = dz.devices('AUvi')
-- local rain = dz.devices('ARain')
-- local barometer = dz.devices('ARelPress')
-- local dailyRain = dz.devices('aDailyRain')
-- local weeklyRain = dz.devices('aWeeklyRain')
-- local monthlyRain = dz.devices('aMonthlyRain')
-- local yearlyRain = dz.devices('aYearlyRain')
-- local gustSpeed = dz.devices('AGustSpeed')
-- local avgWind = dz.devices('AvgWind')
-- ********** No changes necessary below this line *************
-- this function will calculate the last time the script updated devices
-- to check if the webpage response was within limits
local function checkWebpageResponse()
local deltaSeconds = dz.time.dDate - dz.data.previousRainSample
if ( maxResponseSeconds > 0 ) and ( deltaSeconds > maxResponseSeconds ) then
dz.notify(scriptVar, 'weatherStation did not respond within ' .. tostring(maxResponseSeconds) .. ' seconds.')
end
end
-- this function will call the webpage
local function getWeatherStationData()
local url = 'http://' .. weatherStationIP .. '/livedata.htm'
dz.openURL ({ url = url, callback = scriptVar })
end
-- this function will process the HTML and from the relevant data it will create a table to hold the values to update the devices
local function processHTM()
local fieldNames = { -- the commented fields are available in the html but not used in this script.
-- CurrTime='Current time',
-- inBattSta='internalBatteryStatus',
-- outBattSta1='outdoorBatteryStatus1',
-- outBattSta2='outdoorBatteryStatus2',
inTemp='indoorTemperature',
inHumi='indoorHumidity',
AbsPress='absolutePressure',
RelPress='relativePressure',
outTemp='outdoorTemperature',
outHumi='outdoorHumidity',
windir='windDirection',
avgwind='windSpeed',
gustspeed='windGust',
dailygust='maxDailyGust',
solarrad='solarRadiation',
uv='UV',
uvi='UVI',
pm25='PM2.5',
rainofhourly='hourlyRainRate',
eventrain='eventRain',
rainofdaily='dailyRain',
rainofweekly='weeklyRain',
rainofmonthly='monthlyRain',
rainofyearly='yearlyRain',
}
-- Prepare WSData (remove quotes, nullify option value, and make fieldName )
WSData = (item.data):gsub('"',''):gsub("'",""):gsub("option value"," xx"):gsub("input name"," fieldName")
-- fill table with keys
local lines = {}
local linesIndex = 1
for keyField in string.gmatch( WSData,"[^%s, ]+" ) do -- loop over all 'words' in thedata
if string.find(keyField,'fieldName=') then -- if 'word' is like fieldName=text
lines[linesIndex] = {}
lines[linesIndex].name = keyField:gsub('fieldName=','') -- put text into name field
linesIndex = linesIndex + 1
end
end
-- fill table with values
linesIndex = 1
for keyField in string.gmatch( WSData,"[^%s, ]+" ) do
if string.find(keyField,'value=') then -- if 'word' is like value=text / number
if lines[linesIndex] == nil then lines[linesIndex] = {} end
lines[linesIndex].value = keyField:gsub('value=','') -- put text / number into corresponding value field
linesIndex = linesIndex + 1
end
end
-- fill table usedFields only with names and values we are going to use to fill devices
local usedFields = {}
for linesIndex = 1, #lines do
if fieldNames[lines[linesIndex].name] ~= nil then
usedFields[fieldNames[lines[linesIndex].name]] = lines[linesIndex].value
end
end
return usedFields
end
local function updateDevices(fields) -- update all ( declared ) devices. Specialized function to create required fields are local to this function
local function windDirectionName(degrees) -- do not translate as domoticz web GUI depends on strings like E or S
degrees = tonumber(degrees) % 360 -- Ensure between 0 and 360
local windDirections =
{
N={0,22.5}, -- 337,5- 360 will be catched later
NE={22.5,67.5},
E={67.5,112.5},
SE={112.5,157.5},
S={157.5,202.5},
SW={202.5,247.5},
W={247.5,292.5},
NW={292.5,337.5},
}
local minDegrees = 1
local maxDegrees = 2
for key, value in pairs(windDirections) do
if degrees >= value[minDegrees] and degrees < value[maxDegrees] then return key end
end
return 'N'
end
local function windChill(windSpeed, temperature) -- official KNMI function to compute windchill
windSpeed = tonumber(windSpeed)
temperature = tonumber(temperature)
if windSpeed < 1.3 or windSpeed > 49 or temperature < -46 or temperature > 17 then return temperature end
return dz.utils.round(13.12 + (0.6215 * temperature) - (13.96 * windSpeed^0.16) + (0.4867 * temperature * windSpeed^0.16), 1)
end
local function humidityStatus(temperature, humidity) -- as used in domoticz source
humidity = tonumber(humidity)
temperature = tonumber(temperature)
if humidity <= 30 then return dz.HUM_DRY
elseif humidity >= 70 then return dz.HUM_WET
elseif humidity >= 35 and humidity <= 65 and temperature >= 22 and temperature <= 26 then return dz.HUM_COMFORTABLE end
return dz.HUM_NORMAL
end
local function BarometerForecast(pressure) -- simplified forecast model
if pressure == nil then return nil end -- Should not happen
pressure = tonumber(pressure)
if pressure < 966 then return dz.BARO_THUNDERSTORM
elseif pressure < 993 then return dz.BARO_CLOUDY
elseif pressure < 1007 then return dz.BARO_PARTLYCLOUDY
elseif pressure < 1013 then return dz.BARO_UNSTABLE
elseif pressure < 1033 then return dz.BARO_STABLE
end
return dz.BARO_SUNNY
end
local function updateRain(rainRate) -- use time since last sample to calculate new total. Daily total is derived from total by domoticz
rainRate = tonumber(rainRate)
if dz.data.previousRainSample == 0 then
dz.log('Initial setting of the raindata. No update of the device yet',dz.LOG_FORCE)
dz.data.previousRainSample = dz.time.dDate
return
end
if rainRate > 1000 then
dz.log('Obvious wrong value. This would be more then 1 meter of rain per hour',dz.LOG_ERROR)
return
end
local deltaSeconds = dz.time.dDate - dz.data.previousRainSample
local rainIncrease = rainRate * ( deltaSeconds / 3600 )
local previousRainTotal = math.max(dz.data.rainTotal, rain.rain or 0) -- protect against reset or new device
dz.data.rainTotal = previousRainTotal + rainIncrease
rain.updateRain(rainRate * 100 , dz.utils.round(dz.data.rainTotal,1)) -- one decimal should be accurate enough
dz.data.previousRainSample = dz.time.dDate
end
-- update weatherDevices/ See dzVents wiki (https://www.domoticz.com/wiki/DzVents:_next_generation_LUA_scripting) for required parms of the update functions
if wind ~= nil then wind.updateWind
(
fields.windDirection,
windDirectionName(fields.windDirection),
fields.windSpeed,
fields.windGust,
fields.outdoorTemperature,
windChill(fields.windSpeed, fields.outdoorTemperature)
) end
if insideTemperature ~= nil then insideTemperature.updateTemperature(fields.indoorTemperature) end
if insideTemperatureHumidity then insideTemperatureHumidity.updateTempHum
(
fields.indoorTemperature,
fields.indoorHumidity,
humidityStatus(fields.indoorTemperature,fields.indoorHumidity)
) end
if outsideTemperature ~= nil then outsideTemperature.updateTemperature(fields.outdoorTemperature) end
if outsideTemperatureHumidity ~= nil then outsideTemperatureHumidity.updateTempHum
(
fields.outdoorTemperature,
fields.outdoorHumidity,
humidityStatus(fields.outdoorTemperature,fields.outdoorHumidity)
) end
if solarRadiation ~= nil then solarRadiation.updateRadiation(fields.solarRadiation) end
if uvIndex ~= nil then uvIndex.updateUV(fields.UVI) end
if rain ~= nil then updateRain(fields.hourlyRainRate) end
if barometer ~= nil then barometer.updateBarometer
(
fields.relativePressure,
BarometerForecast(fields.relativePressure)
) end
if _G.logLevel == dz.LOG_DEBUG then dz.utils.dumpTable(fields) end
if dailyRain ~= nil then dailyRain.updateCustomSensor(fields.dailyRain) end
if weeklyRain ~= nil then weeklyRain.updateCustomSensor(fields.weeklyRain) end
if monthlyRain ~= nil then monthlyRain.updateCustomSensor(fields.monthlyRain) end
if yearlyRain ~= nil then yearlyRain.updateCustomSensor(fields.yearlyRain) end
if gustSpeed ~= nil then gustSpeed.updateCustomSensor(fields.windGust) end
if avgWind ~= nil then avgWind.updateCustomSensor(fields.windSpeed) end
end
-- main
if item.isTimer or item.isDevice then
checkWebpageResponse()
getWeatherStationData()
elseif item.ok then -- statusCode == 2xx
updateDevices(processHTM(item.data))
else
dz.log('Could not get (good) data from ' .. weatherStationIP,dz.LOG_ERROR)
dz.log(item.data,dz.LOG_DEBUG)
end
end
}