This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Tibber API
#21
(05.04.2020, 00:10)Erwin van der Zwart Wrote: Hi,

You should check if the table content is valid before drilling down deeper...
Code:
if data then
   if data.payload then
      if data.payload.power then
         grp.checkwrite('1/1/1’, data.payload.power)
      end
   end
end
BR,

Erwin

Of course, thanks Erwin  Smile

Best regards, Jørn.
Reply
#22
THank you for the input Jørn. It now Works like a charm.
Schneider Wiser (homeLynk), Power Tags, DALI, Multitouch Pro, Panasonic Heating pump, Flexit balansed ventilation, HUE integration, Lemus Speaker system. Tibber integration.
Reply
#23
Jørn and stemic01:

Could you give a short brief here about what you could do with Tibber and LM in combination? Did you get this working with Tibber API? Do you translate the hour prices to KNX GA?
Reply
#24
(07.01.2021, 19:12)Rune Wrote: Jørn and stemic01:

Could you give a short brief here about what you could do with Tibber and LM in combination? Did you get this working with Tibber API? Do you translate the hour prices to KNX GA?

Hi.  
Group addresses: Yes, I translate the Tibber values into Logic Machine Group Addresses. Per now over the bus-range of addresses, but I plan to change this to virtual addresses treduce the traffic load on the KNX bus. If you plan to show tibber values on a KNX visualization panel you might need to stay in the standard group addresses range.

Tibber integration: Per now I only use this for visualization purposes. Highlighting when prices are high or when consumption are high. Plus trending on prices and consumption. 
So there is no magic going on with cutting power to items, lower heating, stop water heating etc according to prices. But it should be pretty straight forward. It would be pretty easy to make a for exmple a phillips HUE lamp to change color accodring to power prices or current power consumption if you wanted so.  

For electrical vehicle and charging when it is cheap - i do believe that the TIbber / Eeesy charging solution is more streamlined and better (I do not have EV, so I have not tested this). 

The Tibber integration has been working great for me - except one returning issue which is propably a programming error in one of my scripts (Scripts from this thread). It seems like the script either crashes or the Tibber API blocks the connection after some time. I don't know what is the problem, but I usually do the following: 
- Deactivate / activate the Tibber script 
- Restart the Logic Machine 
- Manually change the group address value in Logic machine (example just put in a value "1" in consumption and price. 

One or all of these usually solves the problem.
Schneider Wiser (homeLynk), Power Tags, DALI, Multitouch Pro, Panasonic Heating pump, Flexit balansed ventilation, HUE integration, Lemus Speaker system. Tibber integration.
Reply
#25
(11.01.2021, 21:43)stemic01 Wrote:
(07.01.2021, 19:12)Rune Wrote: Jørn and stemic01:

Could you give a short brief here about what you could do with Tibber and LM in combination? Did you get this working with Tibber API? Do you translate the hour prices to KNX GA?

Hi.  
Group addresses: Yes, I translate the Tibber values into Logic Machine Group Addresses. Per now over the bus-range of addresses, but I plan to change this to virtual addresses treduce the traffic load on the KNX bus. If you plan to show tibber values on a KNX visualization panel you might need to stay in the standard group addresses range.

Tibber integration: Per now I only use this for visualization purposes. Highlighting when prices are high or when consumption are high. Plus trending on prices and consumption. 
So there is no magic going on with cutting power to items, lower heating, stop water heating etc according to prices. But it should be pretty straight forward. It would be pretty easy to make a for exmple a phillips HUE lamp to change color accodring to power prices or current power consumption if you wanted so.  

For electrical vehicle and charging when it is cheap - i do believe that the TIbber / Eeesy charging solution is more streamlined and better (I do not have EV, so I have not tested this). 

The Tibber integration has been working great for me - except one returning issue which is propably a programming error in one of my scripts (Scripts from this thread). It seems like the script either crashes or the Tibber API blocks the connection after some time. I don't know what is the problem, but I usually do the following: 
- Deactivate / activate the Tibber script 
- Restart the Logic Machine 
- Manually change the group address value in Logic machine (example just put in a value "1" in consumption and price. 

One or all of these usually solves the problem.

Thank you for your report.

I got help from a colleague to make a script based on this thread and the only option I need now, is the price per hour. It's working now and it is pretty nice. Smile
Reply
#26
(12.01.2021, 20:37)Rune Wrote:
(11.01.2021, 21:43)stemic01 Wrote:
(07.01.2021, 19:12)Rune Wrote: Jørn and stemic01:

Could you give a short brief here about what you could do with Tibber and LM in combination? Did you get this working with Tibber API? Do you translate the hour prices to KNX GA?

Hi.  
Group addresses: Yes, I translate the Tibber values into Logic Machine Group Addresses. Per now over the bus-range of addresses, but I plan to change this to virtual addresses treduce the traffic load on the KNX bus. If you plan to show tibber values on a KNX visualization panel you might need to stay in the standard group addresses range.

Tibber integration: Per now I only use this for visualization purposes. Highlighting when prices are high or when consumption are high. Plus trending on prices and consumption. 
So there is no magic going on with cutting power to items, lower heating, stop water heating etc according to prices. But it should be pretty straight forward. It would be pretty easy to make a for exmple a phillips HUE lamp to change color accodring to power prices or current power consumption if you wanted so.  

For electrical vehicle and charging when it is cheap - i do believe that the TIbber / Eeesy charging solution is more streamlined and better (I do not have EV, so I have not tested this). 

The Tibber integration has been working great for me - except one returning issue which is propably a programming error in one of my scripts (Scripts from this thread). It seems like the script either crashes or the Tibber API blocks the connection after some time. I don't know what is the problem, but I usually do the following: 
- Deactivate / activate the Tibber script 
- Restart the Logic Machine 
- Manually change the group address value in Logic machine (example just put in a value "1" in consumption and price. 

One or all of these usually solves the problem.

Thank you for your report.

I got help from a colleague to make a script based on this thread and the only option I need now, is the price per hour. It's working now and it is pretty nice. Smile

I use this for price per hour, i use it as a scheduled script with 15 minute intervals. Insert token and replace "Strømpris denne time" with your group address or group name, 4 byte floating point datatype, output in "øre" ;o)

Code:
https = require('ssl.https')
json = require('json')
ltn12 = require('ltn12')

token = 'TOKEN HERE' --Token

data = json.encode({
  query = '{viewer {homes {currentSubscription {priceInfo {current {total}}}}}}' --kwh pris med avgifter
})

tbl = {}
res, code = https.request({
  url = 'https://api.tibber.com/v1-beta/gql',
  method = 'POST',
  headers = {
    ['authorization'] = 'Bearer ' .. token,
    ['content-type']  = 'application/json',
    ['content-length'] = #data,
  },
  source = ltn12.source.string(data),
  sink = ltn12.sink.table(tbl),
})
if res and code == 200 then
  resp = table.concat(tbl)
  resp = json.pdecode(resp)
homes = resp.data.viewer.homes
info = homes[ 1 ].currentSubscription.priceInfo.current
  grp.write('Strømpris denne time', info.total*100)
else
  log(res, code)
end

(11.01.2021, 21:43)stemic01 Wrote:
(07.01.2021, 19:12)Rune Wrote: Jørn and stemic01:

Could you give a short brief here about what you could do with Tibber and LM in combination? Did you get this working with Tibber API? Do you translate the hour prices to KNX GA?

Hi.  
Group addresses: Yes, I translate the Tibber values into Logic Machine Group Addresses. Per now over the bus-range of addresses, but I plan to change this to virtual addresses treduce the traffic load on the KNX bus. If you plan to show tibber values on a KNX visualization panel you might need to stay in the standard group addresses range.

Tibber integration: Per now I only use this for visualization purposes. Highlighting when prices are high or when consumption are high. Plus trending on prices and consumption. 
So there is no magic going on with cutting power to items, lower heating, stop water heating etc according to prices. But it should be pretty straight forward. It would be pretty easy to make a for exmple a phillips HUE lamp to change color accodring to power prices or current power consumption if you wanted so.  

For electrical vehicle and charging when it is cheap - i do believe that the TIbber / Eeesy charging solution is more streamlined and better (I do not have EV, so I have not tested this). 

The Tibber integration has been working great for me - except one returning issue which is propably a programming error in one of my scripts (Scripts from this thread). It seems like the script either crashes or the Tibber API blocks the connection after some time. I don't know what is the problem, but I usually do the following: 
- Deactivate / activate the Tibber script 
- Restart the Logic Machine 
- Manually change the group address value in Logic machine (example just put in a value "1" in consumption and price. 

One or all of these usually solves the problem.

I run in to this from time to time as well. I just created a script that checks if value has been updated recently and turns tibber script off and on. Have not had to do anything manual since. I run it as a scheduled script every 3 minutes.

Code:
obj = grp.find('Aktuelt forbruk watt') -- insert group address for consumption
delta = os.time() - obj.updatetime
if delta >= 10 then
  script.disable('Tibber websocket') -- replace with the name of your tibber websocket script
  os.sleep(1)
  script.enable('Tibber websocket') -- replace with the name of your tibber websocket script
  alert('Restartet Tibber script')
end

Best regards, Jørn.
Reply
#27
Trying to wrap my head around to get the data out from my logs and howto extract the data and to write them into groupaddresses

I see the data in scriptlogs, but can't find out howto extract the 24 hours values into seperate GA's

My script:


Code:
https = require('ssl.https')
json = require('json')
ltn12 = require('ltn12')

token = 'MYTOKEN'

data = json.encode({
  query = '{viewer {homes {currentSubscription {priceInfo {today {total startsAt }}}}}}'
})

tbl = {}
res, code = https.request({
  url = 'https://api.tibber.com/v1-beta/gql',
  method = 'POST',
  headers = {
    ['authorization'] = 'Bearer ' .. token,
    ['content-type']  = 'application/json',
    ['content-length'] = #data,
  },
  source = ltn12.source.string(data),
  sink = ltn12.sink.table(tbl),
})

if res and code == 200 then
  resp = table.concat(tbl)
  resp = json.pdecode(resp)
  log(resp.data.viewer.homes)
info = homes[1].currentSubscription.priceInfo.today[1].total
-- Value for hour 0-1
grp.write('33/0/0', info.total*100)
end


Results from my scriptlog:

Tibber API 09.08.2021 22:54:24
* table:
[1]
  * table:
  ["currentSubscription"]
    * table:
    ["priceInfo"]
      * table:
      ["today"]
        * table:
        [1]
          * table:
          ["startsAt"]
            * string: 2021-08-09T00:00:00+02:00
          ["total"]
            * number: 0.7054
        [2]
          * table:
          ["startsAt"]
            * string: 2021-08-09T01:00:00+02:00
          ["total"]
            * number: 0.6915
        [3]
          * table:
          ["startsAt"]
            * string: 2021-08-09T02:00:00+02:00
          ["total"]
            * number: 0.5271
        [4]
          * table:
          ["startsAt"]
            * string: 2021-08-09T03:00:00+02:00
          ["total"]
            * number: 0.4968
        [5]
          * table:
          ["startsAt"]
            * string: 2021-08-09T04:00:00+02:00
          ["total"]
            * number: 0.7097
        [6]
          * table:
          ["startsAt"]
            * string: 2021-08-09T05:00:00+02:00
          ["total"]
            * number: 0.7656
        [7]
          * table:
          ["startsAt"]
            * string: 2021-08-09T06:00:00+02:00
          ["total"]
            * number: 0.8536
        [8]
          * table:
          ["startsAt"]
            * string: 2021-08-09T07:00:00+02:00
          ["total"]
            * number: 0.8735
        [9]
          * table:
          ["startsAt"]
            * string: 2021-08-09T08:00:00+02:00
          ["total"]
            * number: 0.8917
        [10]
          * table:
          ["startsAt"]
            * string: 2021-08-09T09:00:00+02:00
          ["total"]
            * number: 0.8721
        [11]
          * table:
          ["startsAt"]
            * string: 2021-08-09T10:00:00+02:00
          ["total"]
            * number: 0.8418
        [12]
          * table:
          ["startsAt"]
            * string: 2021-08-09T11:00:00+02:00
          ["total"]
            * number: 0.8247
        [13]
          * table:
          ["startsAt"]
            * string: 2021-08-09T12:00:00+02:00
          ["total"]
            * number: 0.8182
        [14]
          * table:
          ["startsAt"]
            * string: 2021-08-09T13:00:00+02:00
          ["total"]
            * number: 0.8108
        [15]
          * table:
          ["startsAt"]
            * string: 2021-08-09T14:00:00+02:00
          ["total"]
            * number: 0.8135
        [16]
          * table:
          ["startsAt"]
            * string: 2021-08-09T15:00:00+02:00
          ["total"]
            * number: 0.8085
        [17]
          * table:
          ["startsAt"]
            * string: 2021-08-09T16:00:00+02:00
          ["total"]
            * number: 0.8252
        [18]
          * table:
          ["startsAt"]
            * string: 2021-08-09T17:00:00+02:00
          ["total"]
            * number: 0.9003
        [19]
          * table:
          ["startsAt"]
            * string: 2021-08-09T18:00:00+02:00
          ["total"]
            * number: 0.9642
        [20]
          * table:
          ["startsAt"]
            * string: 2021-08-09T19:00:00+02:00
          ["total"]
            * number: 0.9705
        [21]
          * table:
          ["startsAt"]
            * string: 2021-08-09T20:00:00+02:00
          ["total"]
            * number: 0.9249
        [22]
          * table:
          ["startsAt"]
            * string: 2021-08-09T21:00:00+02:00
          ["total"]
            * number: 0.913
        [23]
          * table:
          ["startsAt"]
            * string: 2021-08-09T22:00:00+02:00
          ["total"]
            * number: 0.887
        [24]
          * table:
          ["startsAt"]
            * string: 2021-08-09T23:00:00+02:00
          ["total"]
            * number: 0.8531





Scripts errorlog:
Tibber API 09.08.2021 22:55:24
Resident script:29: attempt to index global 'homes' (a nil value)
stack traceback:


Any hints howto proceed?
I wan't to have hourvalue 0-1 in GA 33/0/0, hourvalue 1-2 in GA 33/0/1 and so on...

BR, Odd Egil
Reply
#28
Try this:
Code:
value = resp.data.viewer.homes[1].currentSubscription.priceInfo.today[1].total
grp.write('33/0/0', tonumber(value) * 100)
Reply
#29
(09.08.2021, 23:01)Erwin van der Zwart Wrote: Try this:
Code:
value = resp.data.viewer.homes[1].currentSubscription.priceInfo.today[1].total
grp.write('33/0/0', tonumber(value) * 100)

That did work exactly like I wanted it to!
Tested with the first six values and they are just like in my Tibber App, just that API has one more decimal



Thank you very much
Reply
#30
Hi.
I'm using this script and it works nice, now I want to present the next hour and second next hours price in my visualisation, and maybe my previous hours price.

I now get the price per hour in a scheduled script and grp.write this to a separate GAs, this script is running once a day. But just wanna show the next and second next hours next to the current price in visu.
Can I modify this script to always shoe the next hours price in the same GA? someone that have done the same solution?
Reply
#31
Change token / group addresses as needed:
Code:
https = require('ssl.https')
json = require('json')
ltn12 = require('ltn12')

token = '476c477d8a039529478ebd690d35ddd80e3308ffc49b59c65b142321aee963a4' -- demo token

query = [[
{
  viewer {
    homes {
      currentSubscription {
        priceInfo {
          today {
            total
            startsAt
          }
          tomorrow {
            total
            startsAt
          }
        }
      }
    }
  }
}
]]

data = json.encode({ query = query })

tbl = {}
res, code = https.request({
  url = 'https://api.tibber.com/v1-beta/gql',
  method = 'POST',
  headers = {
    ['authorization'] = 'Bearer ' .. token,
    ['content-type']  = 'application/json',
    ['content-length'] = #data,
  },
  source = ltn12.source.string(data),
  sink = ltn12.sink.table(tbl),
})

if res and code == 200 then
  resp = table.concat(tbl)
  resp = json.pdecode(resp)
  homes = resp.data.viewer.homes

  today = homes[ 1 ].currentSubscription.priceInfo.today or {}
  tomorrow = homes[ 1 ].currentSubscription.priceInfo.tomorrow or {}

  function getprice(hour)
    local entry
    local index = hour + 1

    if index > 24 then
      entry = tomorrow[ index - 24 ]
    else
      entry = today[ index ]
    end

    if entry then
      return entry.total
    end
  end

  hour = os.date('*t').hour

  grp.update('1/1/1', getprice(hour))
  grp.update('1/1/2', getprice(hour + 1))
  grp.update('1/1/3', getprice(hour + 2))
else
  log(res, code)
end
Reply
#32
Tibber seem to have updated their API (see  Tibber Developer).
Has anybody updated the LUA script to read consumption from HAN port via the Tibber API? 

My script has stopped working with the message:

string: connection failed: Websocket Handshake failed: Invalid Sec-Websocket-Accept (expected ******* = got nil)
Reply
#33
(06.01.2023, 10:00)mjaanes Wrote: Tibber seem to have updated their API (see  Tibber Developer).
Has anybody updated the LUA script to read consumption from HAN port via the Tibber API? 

My script has stopped working with the message:

string: connection failed: Websocket Handshake failed: Invalid Sec-Websocket-Accept (expected ******* = got nil)

I have the same problem..
My resident script has  a os.sleep beacause the CPU-load went too high with real-time subscription
Code:
----------------------------

if not client then
    os.sleep(50) -- wait 50 seconds
    number_of_measurements = 0
  ws = require('user.websocket')
  json = require('json')
  token = 'mySecretToken'

--log('starting')
 
  query = [[
  subscription{
    liveMeasurement(homeId:"mySecretHomeId"){
      timestamp
      power
      accumulatedConsumption
          accumulatedConsumptionLastHour
      minPower
      averagePower
      maxPower
    }
  }
  ]]

 
  url = 'wss://api.tibber.com/v1-beta/gql/subscriptions'

  client, err = ws.client('sync', 10)
  res, err = client:connect(url)

  if res then
    client:send(json.encode({
      type = 'connection_init',
      payload = 'token=' .. token,
    }))

    client:send(json.encode({
      id = 2,
      type = 'start',
      payload = {
        query = query
      }
    }))
  else
    log('connection failed: ' .. tostring(err))
    client:close()
    client = nil
  end
else
  data, _, _, opcode, err = client:receive()
 

  if data then
     --log('data= ' ..data)
    data2 = json.pdecode(data)
    if data2 and data2.payload then
    tibber = data2.payload.data.liveMeasurement
     
     
      --log(tibber)
      timestamp_text = tibber.timestamp
      timestamp_text_sub = string.sub(timestamp_text, 12, 19)
            grp.write('32/4/1', (tonumber(tibber.power))/1000)
            grp.write('32/4/2', (tonumber(tibber.accumulatedConsumption)))
            grp.write('32/4/3', (tonumber(tibber.accumulatedConsumptionLastHour)))
            grp.write('32/4/4', (tonumber(tibber.minPower))/1000)
            grp.write('32/4/5', (tonumber(tibber.averagePower))/1000)
            grp.write('32/4/6', (tonumber(tibber.maxPower))/1000)
            grp.write('32/4/7', timestamp_text_sub)
            
    --log('Suksess')
    client:close()
    client = nil
    end
  else
    log('receive failed: ' .. tostring(err))
    client:close()
    client = nil
  end
 
end
Reply
#34
I am having same issues and need some help to get this up running again... Anyone solved this matter? Any help would be very appreciated.
Schneider Wiser (homeLynk), Power Tags, DALI, Multitouch Pro, Panasonic Heating pump, Flexit balansed ventilation, HUE integration, Lemus Speaker system. Tibber integration.
Reply
#35
Use updated user.websocket library attached to this post.

Change token / homeId as needed. Add object updates at line 78. Use grp.checkupdate or grp.checkwrite so that LM is not overloaded with unnecessary duplicate values.

Code:
ws = require('user.websocket')
json = require('json')
http = require('socket.http')
ltn12 = require('ltn12')

token = '12345'

payload = json.encode({ query = '{viewer { websocketSubscriptionUrl } }' })

res = http.request({
  url = 'https://api.tibber.com/v1-beta/gql',
  method = 'POST',
  headers = {
    ['Authorization'] = 'Bearer ' .. token,
    ['Content-Type'] = 'application/json',
    ['Content-Length'] = #payload,
  },
  source = ltn12.source.string(payload),
})

res = json.pdecode(res)

if type(res) == 'table' and res.data then
  url = res.data.viewer.websocketSubscriptionUrl

  client, err = ws.client('sync', 30)
  client.protocol = 'graphql-transport-ws'
  res, err = client:connect(url)

  query = [[
  subscription {
    liveMeasurement(homeId: "a-b-c-d") {
      timestamp
      power
      accumulatedConsumption
      accumulatedCost
      currency
      minPower
      averagePower
      maxPower
    }
  }
  ]]
else
  res = nil
end

if res then
  res, err = client:send(json.encode({
    type = 'connection_init',
    payload = {
      token = token
    }
  }))

  client:receive()

  ts, tu = os.microtime()

  res, err = client:send(json.encode({
    id = ts .. '-' .. tu,
    type = 'subscribe',
    payload = {
      query = query
    }
  }))

  while true do
    data, opcode, _, _, err = client:receive()

    if data then
      if opcode == ws.PING then
        client:send(data, ws.PONG)
      elseif opcode == ws.TEXT then
        data = json.pdecode(data)

        if type(data) == 'table' then
          -- update objects here
        end
      end
    else
      log('receive failed: ' .. tostring(err))
      break
    end
  end
else
  log('connection failed: ' .. tostring(err))
end

if client then
  client:close()
  client = nil
end

Attached Files
.lua   websocket.lua (Size: 13.79 KB / Downloads: 34)
Reply
#36
Thank you admin
It seems to work very well now. I get updates every 2 seconds.
Reply
#37
(10.01.2023, 10:52)admin Wrote: Use updated user.websocket library attached to this post.

Change token / homeId as needed. Add object updates at line 78. Use grp.checkupdate or grp.checkwrite so that LM is not overloaded with unnecessary duplicate values.

Code:
ws = require('user.websocket')
json = require('json')
http = require('socket.http')
ltn12 = require('ltn12')

token = '12345'

payload = json.encode({ query = '{viewer { websocketSubscriptionUrl } }' })

res = http.request({
  url = 'https://api.tibber.com/v1-beta/gql',
  method = 'POST',
  headers = {
    ['Authorization'] = 'Bearer ' .. token,
    ['Content-Type'] = 'application/json',
    ['Content-Length'] = #payload,
  },
  source = ltn12.source.string(payload),
})

res = json.pdecode(res)

if type(res) == 'table' and res.data then
  url = res.data.viewer.websocketSubscriptionUrl

  client, err = ws.client('sync', 30)
  client.protocol = 'graphql-transport-ws'
  res, err = client:connect(url)

  query = [[
  subscription {
    liveMeasurement(homeId: "a-b-c-d") {
      timestamp
      power
      accumulatedConsumption
      accumulatedCost
      currency
      minPower
      averagePower
      maxPower
    }
  }
  ]]
else
  res = nil
end

if res then
  res, err = client:send(json.encode({
    type = 'connection_init',
    payload = {
      token = token
    }
  }))

  client:receive()

  ts, tu = os.microtime()

  res, err = client:send(json.encode({
    id = ts .. '-' .. tu,
    type = 'subscribe',
    payload = {
      query = query
    }
  }))

  while true do
    data, opcode, _, _, err = client:receive()

    if data then
      if opcode == ws.PING then
        client:send(data, ws.PONG)
      elseif opcode == ws.TEXT then
        data = json.pdecode(data)

        if type(data) == 'table' then
          -- update objects here
        end
      end
    else
      log('receive failed: ' .. tostring(err))
      break
    end
  end
else
  log('connection failed: ' .. tostring(err))
end

if client then
  client:close()
  client = nil
end

Hi, the new websocket.lua (or the new GraphQL transport protocol) seem to create a much more unstable connection than before. I get "Websocket receive failed: closed" 3-4 times per minute and I need to restart the resident script every 10 minute. Are the timeouts too short? Response code is 1006. I hope there will be a more stable approach to this  Smile
Reply
#38
Can you send your script with token via PM so we can test locally?
Reply
#39
After updating the firmware on my LM4 to 20230607, I have got problems with my Tibber real-time subscription.
* string: connection failed: Websocket Handshake failed: Invalid Sec-Websocket-Accept (expected 1LdxwNzFf64n31QWSHxXqslHgAI= got nil)

Is the websocket file in post #35 also the right one for the LM4?
Reply
#40
Can you send your token and home ID via PM?
Reply


Forum Jump: