PDA

View Full Version : Weather



Promixis
April 12th, 2005, 04:38 PM
All,

G4 will have weather, 7 day forecast, and satelite maps builtin. This data is provided by www.customweather.com

If will post some code for review and testing shortly.

quixote
April 12th, 2005, 04:46 PM
Beautiful! Will that include Canada and other countries as well for the satellite imagery?

Promixis
April 12th, 2005, 04:47 PM
International weather and satelite imagery

quixote
April 12th, 2005, 04:51 PM
Excuse me, I need to go change my underwear.
:lol: :lol: :lol:

VaioUserChris
April 12th, 2005, 05:04 PM
Can we build a TV schedule into it too??? :D

Chris

Rob H
April 12th, 2005, 05:06 PM
Sounds good Mike

Promixis
April 12th, 2005, 07:58 PM
Satelite Map

VaioUserChris
April 12th, 2005, 09:48 PM
Very cool.

Chris

Promixis
April 12th, 2005, 09:55 PM
note: you have to enable animations to see them on your browser.

danward79
April 13th, 2005, 12:06 AM
Nice!

danward79
April 22nd, 2005, 01:08 PM
Is this functional yet in G4?

Do they do the UK?

Promixis
April 22nd, 2005, 02:17 PM
yes they do the uk, haven't looked at the detail in the maps though.

soon will be out. maybe this weekend.

danward79
April 22nd, 2005, 02:27 PM
Thanks

Promixis
April 26th, 2005, 02:29 PM
Ok, I have finally got something worth using. This will only work on the A13 release. Please do not modify the code below. Only access the data using the provided methods. The core code may change but the structure of the returned data should not. This code will eventually be moved into G4. Use the variable inspector to see the data that is returned.






--[[

Weather Object

Object/class for retreiving the weather from customweather.com

(c) copyright Promixis LLC

NOTES: This object uses threads. You must only access the data in the object using the object mutex.

3 Get functions (which take care of the mutex) are provided to get current conditions,
forecast conditions and satelite map filename.

--]]


CWWeatherRetrieval = {

-- URL Request settings (constant) ....
BaseURL = "http://xml.customweather.com/xml",
ClientURL = "client=proximis_test&client_password=t3mp",
LocationURL = nil, -- declared in each object (ie not constant)
CCProductURL = "product=current_conditions",
FCProductURL = "product=expanded_forecast",
SatMapProductURL = "product=maps&map_product=satellite",
UnitURL = "metric=",

New = function (self,o)
o = o or {}
setmetatable (o,self)
self.__index = self
return o
end,

Start = function (self)

-- setup locationurl to retreive weather
if not self.LocationURL then -- allows external control over location lookup ie, won't overwrite a preset url
if self.CWID then
self.LocationURL = "id="..self.CWID
elseif self.Location.Zipcode and self.Location.Country == "United States" then
self.LocationURL = "zip_code="..self.Location.Zipcode
elseif self.Location.City then
self.LocationURL = "name="..self.Location.City
if self.Location.State then
self.LocationURL = self.LocationURL ..","..self.Locaton.State
end
if self.Location.Country then
self.LocationURL = self.LocationURL ..","..self.Locaton.Country
end
elseif self.Location.Latitude and self.Location.Longitude then
self.LocationURL = "latitude="..self.Location.Latitude.."&longitude="..self.Location.Longitude
else
return nil -- no location given to do the look up....
end
end

self.NotificationCallbacks = {} -- table of callback functions
self.Mutex = thread.newmutex () -- for async retreivals...

-- misc settings
if self.Location.Country == "United States" then
self.Metric = self.Metric or false
else -- non us
self.Metric = self.Metric or true
end

self.AutoUpdate = self.AutoUpdate or false -- set to true to enable autoupdates
self.SatMapAnimation = self.SatMapAnimation or false

-- weather data tables
self.CC = {
RefreshInterval = 15, -- minutes
TimeStamp = {},
LastUpdate = 0,
URL = "",
Product = "Current Conditions",
Data = nil,
}

self.FC = {
RefreshInterval = 4*60, --minutes
TimeStamp = {},
LastUpdate = 0,
URL = 0,
Product = "Forecast Conditions",
Data = nil,
}

self.SatMap = {
RefreshInterval = 30, --minutes
LastUpdate = 0,
URL = 0,
Product = "Satellite Map",
Filename = win.GetTempFilename (win.GetTempPath (),"CSM",0),
}

if not self.SatMap.Filename then
return
end

-- setup urls
self.CC.URL = self.BaseURL.."?"..self.ClientURL.."&"..self.CCProductURL.."&"..self.LocationURL.."&"..self.UnitURL..tostring (self.Metric)
self.FC.URL = self.BaseURL.."?"..self.ClientURL.."&"..self.FCProductURL.."&"..self.LocationURL.."&"..self.UnitURL..tostring (self.Metric)
self.SatMap.URL = self.BaseURL.."?"..self.ClientURL.."&"..self.SatMapProductURL.."&"..self.LocationURL.."&anim="..tostring(self.SatMapAnimation)

if self.AutoUpdate then
self.AutoUpdateTimer = gir.CreateTimer (function (...) return self:UpdateCheck (unpack (arg)) end,function (...) return self:UpdateCheck (unpack (arg)) end,nil,1)
self.AutoUpdateTimer:Arm (60000)--check every minute
end
return true
end,


UpdateCC = function (self)
local error,status,data
gir.CoInitialize ()
local WeatherDOM = luacom.CreateObject("Msxml2.DOMDocument")

if WeatherDOM then
WeatherDOM.async = false
-- note luacom error settings may change the way this function behaves...
status,error = pcall ( function (...) return WeatherDOM:load(self.CC.URL) end)
if status then -- no error
if WeatherDOM.parseError.errorCode == 0 then -- no error
data,error = self:ParseCC (WeatherDOM)
else
error = WeatherDOM.parseError.reason
end
end
else
error = "DOM Failure"
end

if data then -- we got data
local timestamp
if data.Report then
local h = string.find ((data.Report.localupdatetime or""),"..:..:..")
if h then
local months = {Jan=1,Feb=2,Mar=3,Apr=4,May=5,Jun=6,Jul=7,Aug=8,S ep=9,Oct=10,Nov=11,Dec=12}
timestamp = {Hour = tonumber (string.sub (data.Report.localupdatetime,h,h+1)),
Minute = tonumber (string.sub (data.Report.localupdatetime,h+3,h+4)),
Second = tonumber (string.sub (data.Report.localupdatetime,h+6,h+7)),
Year = tonumber (string.sub (data.Report.localupdatetime,13,16)),
Day = tonumber (string.sub (data.Report.localupdatetime,6,7)),
Month = tonumber (months [string.sub (data.Report.localupdatetime,9,11)]) }
end
end
if not self.CC.Data or not self.CC.Data.Report or self.CC.Data.Report.localupdatetime ~= data.Report.localupdatetime then -- we have an update
self.CC.Data = data
self.CC.TimeStamp = timestamp
self:LogUpdate (self.CC.Product)
self.CC.LastUpdate = win.GetElapsedSeconds ()
else
--print ("update not new")
end
else
self:LogUpdate (self.CC.Product,(error or "unknown error"))
end
gir.CoUninitialize ()
WeatherDOM = nil
collectgarbage ()
end,


ParseCC = function (self,WeatherDOM)
local data = {}

local rn = WeatherDOM:selectSingleNode ("report")
if not rn then
rn = WeatherDOM:selectSingleNode ("cw_report")
if rn then
return nil,rn.text
end
return nil,"parsing error"
end

data.Report = self:ParseAttributes (rn.attributes)

if rn and rn:hasChildNodes () then -- observations
data.Observations = {}
local cns = rn.childNodes
for j = 0, cns.length-1 do
data.Observations [j+1] = self:ParseAttributes (cns:item (j).attributes)
end
end
return data
end,


UpdateCCAsync = function (self)
self.Mutex:lock ()
gir.CoInitialize ()
self.CCThreadID = thread.newthread (self.UpdateCC,{self})
gir.CoUninitialize ()
self.Mutex:unlock ()
end,


PrintCC = function (self) -- NOT THREAD SAFE

if self.CC.Data and self.CC.Data.Report then
print ("Current Conditions")
print ("| Location")
for x,y in pairs (self.CC.Data.Report) do
print ("| ",x,": ",y)
end
end
if self.CC.Data and self.CC.Data.Observations then
for i = 1, table.getn (self.CC.Data.Observations) do
print ("| Observation #:",i)
for x,y in pairs (self.CC.Data.Observations [i]) do
print ("| ",x,": ",y)
end
end
end
end,


GetCC = function (self)
self.Mutex:lock ()
local data = {}
for a,b in pairs (self.CC.Data) do
data [a] = b
end
self.Mutex:unlock ()
return data
end,


UpdateFC = function (self)
local error,status,data
gir.CoInitialize ()
local WeatherDOM = luacom.CreateObject("Msxml2.DOMDocument")

if WeatherDOM then
WeatherDOM.async = false
-- note luacom error settings may change the way this function behaves...
status,error = pcall ( function (...) return WeatherDOM:load(self.FC.URL) end)
if status then -- no error
if WeatherDOM.parseError.errorCode == 0 then -- no error
data,error = self:ParseFC (WeatherDOM)
else
error = WeatherDOM.parseError.reason
end
end
else
error = "DOM Failure"
end

if data then -- we got data
local timestamp
if data.Report then
local h = string.find ((data.Report.localupdatetime or""),"..:..:..")
if h then
local months = {Jan=1,Feb=2,Mar=3,Apr=4,May=5,Jun=6,Jul=7,Aug=8,S ep=9,Oct=10,Nov=11,Dec=12}
timestamp = {Hour = tonumber (string.sub (data.Report.localupdatetime,h,h+1)),
Minute = tonumber (string.sub (data.Report.localupdatetime,h+3,h+4)),
Second = tonumber (string.sub (data.Report.localupdatetime,h+6,h+7)),
Year = tonumber (string.sub (data.Report.localupdatetime,13,16)),
Day = tonumber (string.sub (data.Report.localupdatetime,6,7)),
Month = tonumber (months [string.sub (data.Report.localupdatetime,9,11)]) }
end
end
if not self.FC.Data or not self.FC.Data.Report or self.FC.Data.Report.localupdatetime ~= data.Report.localupdatetime then -- we have an update
self.FC.Data = data
self.FC.TimeStamp = timestamp
self:LogUpdate (self.FC.Product)
self.FC.LastUpdate = win.GetElapsedSeconds ()
else
-- print ("update not new")
end
else
self:LogUpdate (self.FC.Product,(error or "unknown error"))
end
gir.CoUninitialize ()
WeatherDOM = nil
collectgarbage ()
end,


ParseFC = function (self,WeatherDOM)
local data = {}

local rn = WeatherDOM:selectSingleNode ("report")
if not rn then
return
end

data.Report = self:ParseAttributes (rn.attributes)

local ln = rn:selectSingleNode ("location")
if not ln then
return
end

if ln and ln:hasChildNodes () then -- observations
data.Location = self:ParseAttributes (ln.attributes)
data.Forecast = {}
local lns = ln.childNodes
for j = 0, lns.length-1 do
data.Forecast [j+1] = self:ParseAttributes (lns:item (j).attributes)
end
end
return data
end,


UpdateFCAsync = function (self)
self.Mutex:lock ()
gir.CoInitialize ()
self.FCThreadID = thread.newthread (self.UpdateFC,{self})
gir.CoUninitialize ()
self.Mutex:unlock ()
end,


PrintFC = function (self) -- NOT THREAD SAFE
if self.FC and self.FC.Report then
print ("Forecast Conditions")
print ("| Report")
for x,y in pairs (self.FC.Report) do
print ("| ",x,": ",y)
end
end
if self.FC and self.FC.Location then
print ("| Location")
for x,y in pairs (self.FC.Location) do
print ("| ",x,": ",y)
end
for i = 1, table.getn (self.FC.Forecast) do
print ("| Forecast Day #:",i)
for x,y in pairs (self.FC.Forecast [i]) do
print ("| ",x,": ",y)
end
end
end
end,

GetFC = function (self)
self.Mutex:lock ()
local data = {}
for a,b in pairs (self.FC.Data)do
data [a] = b
end
self.Mutex:unlock ()
return data
end,


ParseAttributes = function (self,attributes)
local t = {}

for x = 0, attributes.length - 1 do
t [attributes:item (x).name] = attributes:item (x).value
end

return t
end,


UpdateSatMap = function (self)
local http=require("socket.http")
local b,HTTPERROR=http.request( self.SatMap.URL )
if HTTPERROR == 200 then -- no error
local f = assert(io.open(self.SatMap.Filename, "wb"))
local t = f:write(b)
f:close()
self.SatMap.LastUpdate = win.GetElapsedSeconds ()
self:LogUpdate (self.SatMap.Product)
else
self:LogUpdate (self.SatMap.Product,"Error "..(HTTPERROR or "unknown"))
end
end,


GetSatMapFilename = function (self)
self.Mutex:lock ()
local fn = self.SatMap.Filename
self.Mutex:unlock ()
return fn
end,


UpdateSatMapAsync = function (self)
self.Mutex:lock ()
self.SatMapThreadID = thread.newthread (self.UpdateSatMap,{self})
self.Mutex:unlock ()
end,


UpdateCheck = function (self)
if self.UpdateThreadID and self.UpdateThreadID:isthreadrunning () then
return
end
self.UpdateThreadID = thread.newthread (self.UpdateCheckAsync,{self})
end,


UpdateCheckAsync = function (self)
self.Mutex:lock () -- note, an error between the locked mutex means a girder reboot...
--we check time since last update and the expected time of the update as per CW guidelines
if self.CC.LastUpdate == 0 or win.GetElapsedSeconds (self.CC.LastUpdate) / 60 > self.CC.RefreshInterval then
-- do not use below, this results in too aggressive timing the timestamp is too far out of date with the actual post time....
--&#40;win.DateToFileTime &#40;self.CC.TimeStamp&#41; or 0&#41; + self.CC.RefreshInterval * 60 < win.DateToFileTime &#40;win.GetLocalTime &#40;&#41;&#41; then
if not pcall &#40;function &#40;...&#41; return self&#58;UpdateCC &#40;unpack &#40;arg&#41;&#41; end&#41; then -- something bad happened
print &#40;"Error in Current Condition Update"&#41;
end
end
if self.FC.LastUpdate == 0 or win.GetElapsedSeconds &#40;self.FC.LastUpdate&#41; / 60 > self.FC.RefreshInterval or
-- note the 1.2 below, this is a fudge factor to limit too many request before new data is ready...
&#40;win.DateToFileTime &#40;self.FC.TimeStamp&#41; or 0&#41;+ self.FC.RefreshInterval * 60 *1.2 < win.DateToFileTime &#40;win.GetLocalTime &#40;&#41;&#41; then
if not pcall &#40;function &#40;...&#41; return self&#58;UpdateFC &#40;unpack &#40;arg&#41;&#41; end&#41; then -- something bad happened
print &#40;"Error in Forecast Condition Update"&#41;
end
end
--we don't know the time the map should be updating as we don't get this info from CW when the next update is due....
if self.SatMap.LastUpdate == 0 or win.GetElapsedSeconds &#40;self.SatMap.LastUpdate&#41; /60 > self.SatMap.RefreshInterval then
if not pcall &#40;function &#40;...&#41; return self&#58;UpdateSatMap &#40;unpack &#40;arg&#41;&#41; end&#41; then -- something bad happened
print &#40;"Error in Satelite Map Update"&#41;
end
end
self.Mutex&#58;unlock &#40;&#41;
end,


LogUpdate = function &#40;self,ProductID,Error&#41; -- default callback
if not Error then
gir.LogMessage &#40;"Weather",ProductID.." updated for "..self.Location.City,3&#41;
else
gir.LogMessage &#40;"Weather",ProductID.." error for "..self.Location.City.. &#40;Error or ""&#41;,1&#41;
end

for a,b in self.NotificationCallbacks do
gir.RunCodeOnMainThread &#40;b,ProductID,Error&#41; -- note, runcodeonmainthread is an sync call which is good for COM objects getting data back. This is an ASYNC cal
end
end,

-- functions to call whenever weather is updated. calls function with productid plus error if one occurred.
AddNotificationCallback = function &#40;self,f&#41;
self.Mutex&#58;lock &#40;&#41;
self.NotificationCallbacks &#91;f&#93; = f
self.Mutex&#58;unlock &#40;&#41;
end,

RemoveNotificationCallback = function &#40;self,f&#41;
self.Mutex&#58;lock &#40;&#41;
self.NotificationCallbacks &#91;f&#93; = nil
self.Mutex&#58;unlock &#40;&#41;
end,

Quit = function &#40;self&#41; -- must call this to dispose of properly or do a script reset.
local timeout = win.GetElapsedSeconds &#40;&#41;
while self.UpdateThreadID and self.UpdateThreadID&#58;isthreadrunning &#40;&#41; and win.GetElapsedSeconds &#40;timeout&#41; < 60 do
win.Sleep &#40;50&#41;
end

self.Mutex&#58;lock &#40;&#41;
self.AutoUpdateTimer&#58;Destroy &#40;&#41;
self.AutoUpdateTimer = nil
self.Mutex&#58;unlock &#40;&#41;
-- self = nil
collectgarbage &#40;&#41;
end


&#125;


if LocalWeather then
LocalWeather&#58;Quit &#40;&#41;
end


LocalWeather = CWWeatherRetrieval&#58;New &#40;&#123;Location = gir.GetLocation &#40;&#41;,AutoUpdate = true,SatMapAnimation = true&#125;&#41;
if not LocalWeather&#58;Start &#40;&#41; then
gir.LogMessage &#40;"Weather","Unable to start",1&#41;
end

quixote
April 27th, 2005, 06:10 PM
Where in the variable inspector do we look? (Which table?)

Promixis
April 27th, 2005, 09:04 PM
LocalWeather, CC for current conditions, FC for forecast.

danward79
June 2nd, 2005, 07:16 AM
Mike,

Just having a go with the weather script you posted above, but... It does not work for me. I get the following errors


[string "HTPC G4 Setup.gml:\G4 Weather\Scripting"]:462: attempt to index field `Mutex' (a nil value)
stack traceback:
[string "HTPC G4 Setup.gml:\G4 Weather\Scripting"]:462: in function `Quit'
[string "HTPC G4 Setup.gml:\G4 Weather\Scripting"]:475: in main chunk


What is Mutex?

birty
June 2nd, 2005, 08:36 AM
mutex is mutal exclusion, usually used to prevent two pieces of code accessing the same memory at the same time, don't know why its not working though

Promixis
June 2nd, 2005, 09:01 AM
Dan,

This script has changed significantly in the last couple of weeks. I will be uploading an update shortly.

danward79
June 2nd, 2005, 09:08 AM
Thanks Chaps