ce-greenbutton 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +162 -0
- data/Rakefile +2 -0
- data/lib/ce-greenbutton/elements/gb_application_information.rb +17 -0
- data/lib/ce-greenbutton/elements/gb_content.rb +26 -0
- data/lib/ce-greenbutton/elements/gb_data_feed.rb +34 -0
- data/lib/ce-greenbutton/elements/gb_entry.rb +68 -0
- data/lib/ce-greenbutton/elements/gb_interval.rb +21 -0
- data/lib/ce-greenbutton/elements/gb_interval_block.rb +24 -0
- data/lib/ce-greenbutton/elements/gb_interval_reading.rb +22 -0
- data/lib/ce-greenbutton/elements/gb_local_time_parameters.rb +50 -0
- data/lib/ce-greenbutton/elements/gb_reading_type.rb +23 -0
- data/lib/ce-greenbutton/elements/gb_usage_point.rb +20 -0
- data/lib/ce-greenbutton/interpreters/electricity_interpreter.rb +114 -0
- data/lib/ce-greenbutton/model/gb_data.rb +30 -0
- data/lib/ce-greenbutton/model/gb_data_description.rb +41 -0
- data/lib/ce-greenbutton/model/gb_data_reading.rb +23 -0
- data/lib/ce-greenbutton/parser.rb +54 -0
- data/lib/ce-greenbutton/ruby_extensions.rb +23 -0
- data/lib/ce-greenbutton/utils.rb +136 -0
- data/lib/ce-greenbutton/version.rb +3 -0
- data/lib/ce-greenbutton.rb +352 -0
- metadata +160 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
# Copyright (C) 2015 TopCoder Inc., All Rights Reserved.
|
2
|
+
require 'ce-greenbutton/utils'
|
3
|
+
|
4
|
+
# Represents common data for a UsagePoint
|
5
|
+
#
|
6
|
+
# Author TCSASSEMBLER
|
7
|
+
# Version 1.0
|
8
|
+
class GbDataDescription
|
9
|
+
include Utils::Hash
|
10
|
+
|
11
|
+
# Represents the custodian value in the ApplicationInformation structure.
|
12
|
+
attr_accessor :custodian
|
13
|
+
|
14
|
+
# Represents the access_token used to retrieve the GreenButton data.
|
15
|
+
attr_accessor :user_id
|
16
|
+
|
17
|
+
# Represents the commodity value of the corresponding ReadingType structure.
|
18
|
+
attr_accessor :commodity
|
19
|
+
|
20
|
+
# Represents the currency value of the corresponding ReadingType structure.
|
21
|
+
attr_accessor :currency
|
22
|
+
|
23
|
+
# Represents the "uom" value of the corresponding ReadingType structure.
|
24
|
+
attr_accessor :unit_of_measure
|
25
|
+
|
26
|
+
# Represents the "powerOfTenMultiplier" value of the corresponding
|
27
|
+
# ReadingType structure.
|
28
|
+
attr_accessor :power_of_ten_multiplier
|
29
|
+
|
30
|
+
# Represents the last updated date/time
|
31
|
+
attr_accessor :updated
|
32
|
+
|
33
|
+
# An array of GbData
|
34
|
+
attr_accessor :gb_data_array
|
35
|
+
|
36
|
+
# Returns the parsed elements as a hash with key equals the property name and
|
37
|
+
# value equals the parsed value.
|
38
|
+
def _parse
|
39
|
+
to_hash
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright (C) 2015 TopCoder Inc., All Rights Reserved.
|
2
|
+
|
3
|
+
# Represents an IntervalReading structure.
|
4
|
+
#
|
5
|
+
# Author ahmed.seddiq
|
6
|
+
# Version 1.0
|
7
|
+
class GbDataReading
|
8
|
+
|
9
|
+
# Represents the start time (local) value from the IntervalReading ->
|
10
|
+
# timePeriod structure
|
11
|
+
attr_accessor :time_start
|
12
|
+
|
13
|
+
# Represents the duration value from the IntervalReading -> timePeriod
|
14
|
+
# structure
|
15
|
+
attr_accessor :time_duration
|
16
|
+
|
17
|
+
# Represents the value element in the IntervalReading structure
|
18
|
+
attr_accessor :value
|
19
|
+
|
20
|
+
# Represents the cost element in the IntervalReading structure
|
21
|
+
attr_accessor :cost
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Copyright (C) 2015 TopCoder Inc., All Rights Reserved.
|
2
|
+
require 'ce-greenbutton/elements/gb_data_feed'
|
3
|
+
module GreenButton
|
4
|
+
# Public: Defines a parse method. It accepts both strings and io inputs.
|
5
|
+
#
|
6
|
+
# Author ahmed.seddiq
|
7
|
+
# Version: 1.0
|
8
|
+
module Parser
|
9
|
+
# Public: Parses the given input to an instance of GbDataFeed. After parsing
|
10
|
+
# the GbDataFeed.entries will contain a hash of defined GbEntries. The key
|
11
|
+
# of the hash is the "self" link identifies each entry. the "up" links are
|
12
|
+
# also added to the hash with an array value aggregating all sub entries.
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# For example:
|
16
|
+
#
|
17
|
+
# if we have a feed with one UsagePoint, one MeterReading, three IntervalBlocks
|
18
|
+
# the entries hash will be some thing like
|
19
|
+
# {"http://url.of.usage.point.self" => GbEntry(of type UsagePoint)}
|
20
|
+
# {"http://url.of.usage.point.up" => [GbEntry(of type UsagePoint)]}
|
21
|
+
# {"http://url.of.meter.reading.self" => GbEntry(of type MeterReading)}
|
22
|
+
# {"http://url.of.meter.reading.up" => [GbEntry(of type MeterReading)]}
|
23
|
+
# {"http://url.of.interval.block.1.self" => GbEntry(of type IntervalBlock)}
|
24
|
+
# {"http://url.of.interval.block.2.self" => GbEntry(of type IntervalBlock)}
|
25
|
+
# {"http://url.of.interval.block.3.self" => GbEntry(of type IntervalBlock)}
|
26
|
+
# {"http://url.of.interval.block.[1|2|3].up" =>
|
27
|
+
# [GbEntry(of type IntervalBlock), GbEntry(of type IntervalBlock),
|
28
|
+
# GbEntry(of type IntervalBlock)]
|
29
|
+
#
|
30
|
+
def self.parse(input)
|
31
|
+
feed = Parser::GbDataFeed.parse(input)
|
32
|
+
entries = {}
|
33
|
+
feed.entries.each do |entry|
|
34
|
+
entries[entry.self] = entry
|
35
|
+
if entries[entry.up].nil?
|
36
|
+
entries[entry.up] = []
|
37
|
+
end
|
38
|
+
entries[entry.up] << entry
|
39
|
+
entry.link = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
feed.entries = entries
|
43
|
+
feed
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright (C) 2015 TopCoder Inc., All Rights Reserved.
|
2
|
+
|
3
|
+
# Extending the Integer to add the utility method of extracting bits
|
4
|
+
class Integer
|
5
|
+
# Extracts bits in range from: to to:
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
# 10.bits(from:1, to:2) # 1010
|
9
|
+
# # => 1
|
10
|
+
def bits(options)
|
11
|
+
from = options[:from] || 0
|
12
|
+
to = options[:to]
|
13
|
+
val = self
|
14
|
+
unless from.nil?
|
15
|
+
val = val >> from
|
16
|
+
end
|
17
|
+
unless to.nil?
|
18
|
+
val = val & (2 ** (to - from + 1) - 1)
|
19
|
+
end
|
20
|
+
|
21
|
+
val
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# Copyright (C) 2015 TopCoder Inc., All Rights Reserved.
|
2
|
+
require 'ice_cube'
|
3
|
+
require 'ce-greenbutton/ruby_extensions'
|
4
|
+
# Mixins for common functionality.
|
5
|
+
#
|
6
|
+
# Author: ahmed.seddiq
|
7
|
+
# Version: 1.0
|
8
|
+
#
|
9
|
+
module Utils
|
10
|
+
module Hash
|
11
|
+
# Converts instance variables of an object to a hash.
|
12
|
+
#
|
13
|
+
# obj - the object to inspect
|
14
|
+
#
|
15
|
+
# Returns a hash representing the instance variables of the given object.
|
16
|
+
def to_hash
|
17
|
+
if self.instance_variables.length > 0
|
18
|
+
hash = {}
|
19
|
+
self.instance_variables.each do |var|
|
20
|
+
hash[var.to_s.delete('@')] = self.instance_variable_get(var)
|
21
|
+
end
|
22
|
+
hash
|
23
|
+
else
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module DSTRule
|
30
|
+
# Converts the given DST rule to a Time instance indicating the start
|
31
|
+
# time of applying this rule.
|
32
|
+
#
|
33
|
+
# Based on the ESPI schema, The rule string is encoded as follows:
|
34
|
+
#
|
35
|
+
# Bits 0 - 11: seconds 0 - 3599
|
36
|
+
# Bits 12 - 16: hours 0 - 23
|
37
|
+
# Bits 17 - 19: day of the week 0 = not applicable, 1 - 7 (Monday = 1)
|
38
|
+
# Bits:20 - 24: day of the month 0 = not applicable, 1 - 31
|
39
|
+
# Bits: 25 - 27: operator (detailed below)
|
40
|
+
# Bits: 28 - 31: month 1 - 12
|
41
|
+
#
|
42
|
+
# Rule value of 0xFFFFFFFF means rule processing/DST correction is disabled.
|
43
|
+
#
|
44
|
+
# The operators:
|
45
|
+
#
|
46
|
+
# 0: DST starts/ends on the Day of the Month
|
47
|
+
# 1: DST starts/ends on the Day of the Week that is on or after the Day of the Month
|
48
|
+
# 2: DST starts/ends on the first occurrence of the Day of the Week in a month
|
49
|
+
# 3: DST starts/ends on the second occurrence of the Day of the Week in a month
|
50
|
+
# 4: DST starts/ends on the third occurrence of the Day of the Week in a month
|
51
|
+
# 5: DST starts/ends on the forth occurrence of the Day of the Week in a month
|
52
|
+
# 6: DST starts/ends on the fifth occurrence of the Day of the Week in a month
|
53
|
+
# 7: DST starts/ends on the last occurrence of the Day of the Week in a month
|
54
|
+
#
|
55
|
+
# An example: DST starts on third Friday in March at 1:45 AM. The rule...
|
56
|
+
# Seconds: 2700
|
57
|
+
# Hours: 1
|
58
|
+
# Day of Week: 5
|
59
|
+
# Day of Month: 0
|
60
|
+
# Operator: 4
|
61
|
+
# Month: 3
|
62
|
+
#
|
63
|
+
# rule - the dst rule, it can be the dst start rule or dst end rule.
|
64
|
+
# Returns a Time instance representing the starting time of applying the rule
|
65
|
+
#
|
66
|
+
def to_time(rule)
|
67
|
+
if rule.is_a? String
|
68
|
+
# expected to be in hex
|
69
|
+
rule = rule.to_i(16)
|
70
|
+
end
|
71
|
+
if rule == 0xFFFFFFFF
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
|
75
|
+
parts = extract_parts(rule)
|
76
|
+
|
77
|
+
# start a schedule with 1 January of current year
|
78
|
+
current_year = DateTime.now.year
|
79
|
+
end_of_year = Time.new(current_year, 12, 31,23,59,59)
|
80
|
+
date = nil
|
81
|
+
case parts[:operator]
|
82
|
+
when 0
|
83
|
+
raise InvalidDstRuleError, 'day of month must be provided for operator 0' if parts[:day_of_month] == 0
|
84
|
+
date = Time.new(current_year, parts[:month], parts[:day_of_month])
|
85
|
+
when 1
|
86
|
+
raise InvalidDstRuleError, 'day of month must be provided for operator 1' if parts[:day_of_month] == 0
|
87
|
+
raise InvalidDstRuleError, 'day of week must be provided for operator 1' if parts[:day_of_week] == 0
|
88
|
+
# first day_of_week on or after the day of month
|
89
|
+
schedule = IceCube::Schedule.new(Date.new(current_year, parts[:month], parts[:day_of_month]))
|
90
|
+
# It may be in the next month
|
91
|
+
months = [parts[:month], parts[:month] + 1]
|
92
|
+
months = [parts[:month]] if parts[:month] == 12
|
93
|
+
schedule.add_recurrence_rule IceCube::Rule.yearly.until(end_of_year).month_of_year(months).day_of_week(parts[:day_of_week] % 7 => [1, 2, 3, 4, 5])
|
94
|
+
dates = schedule.all_occurrences
|
95
|
+
raise InvalidDstRuleError, "Can't find day of week on or after the given day of month" if dates.length == 0
|
96
|
+
date = dates[0]
|
97
|
+
else
|
98
|
+
raise InvalidDstRuleError, 'day of week must be provided for operators 2..7' if parts[:day_of_week] == 0
|
99
|
+
# Last occurrence of day_of_week in the month
|
100
|
+
order_in_month = parts[:operator] - 1
|
101
|
+
order_in_month = -1 if (parts[:operator] == 7)
|
102
|
+
schedule = IceCube::Schedule.new(Date.new(current_year, 1, 1))
|
103
|
+
schedule.add_recurrence_rule IceCube::Rule.yearly.until(end_of_year).month_of_year(parts[:month]).day_of_week(parts[:day_of_week] % 7 => [order_in_month])
|
104
|
+
dates = schedule.all_occurrences
|
105
|
+
raise InvalidDstRuleError, "Can't find day of week on or after the given day of month" if dates.length == 0
|
106
|
+
date = dates[0]
|
107
|
+
end
|
108
|
+
|
109
|
+
time = date.to_i
|
110
|
+
time += (parts[:hours] * 60 * 60) + parts[:seconds]
|
111
|
+
Time.at(time)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
def extract_parts(rule)
|
116
|
+
parts = {}
|
117
|
+
parts[:seconds] = rule.bits(from: 0, to: 11)
|
118
|
+
parts[:hours] = rule.bits(from: 12, to: 16)
|
119
|
+
parts[:day_of_week] = rule.bits(from: 17, to: 19)
|
120
|
+
parts[:day_of_month] = rule.bits(from: 20, to: 24)
|
121
|
+
parts[:operator] = rule.bits(from: 25, to: 27)
|
122
|
+
parts[:month] = rule.bits(from: 28, to: 31)
|
123
|
+
|
124
|
+
# validate extracted values
|
125
|
+
raise InvalidDstRuleError, 'seconds should be from 0 - 3599' unless parts[:seconds] < 3600
|
126
|
+
raise InvalidDstRuleError, 'hours should be from 0 - 23' unless parts[:hours] < 24
|
127
|
+
raise InvalidDstRuleError, 'day_of_week should be from 0 - 7' unless parts[:day_of_week] < 8
|
128
|
+
raise InvalidDstRuleError, 'day_of_month should be from 0 - 31' unless parts[:day_of_week] < 32
|
129
|
+
raise InvalidDstRuleError, 'month should be from 1 - 12' unless parts[:month].between?(1, 12)
|
130
|
+
parts
|
131
|
+
end
|
132
|
+
|
133
|
+
class InvalidDstRuleError < Exception
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,352 @@
|
|
1
|
+
# Copyright (C) 2015 TopCoder Inc., All Rights Reserved.
|
2
|
+
|
3
|
+
require 'open-uri'
|
4
|
+
require 'date'
|
5
|
+
require 'ce-greenbutton/parser'
|
6
|
+
require 'ce-greenbutton/elements/gb_application_information'
|
7
|
+
require 'ce-greenbutton/elements/gb_entry'
|
8
|
+
|
9
|
+
# The main entry of the module. provides the download_data method that will
|
10
|
+
# download and parse GreenButton data.
|
11
|
+
# For example:
|
12
|
+
# # The following will download and parse all data for subscription
|
13
|
+
# require 'ce-greenbutton'
|
14
|
+
# GreenButton.config(reg_access_token: "your access token"
|
15
|
+
# application_information_url: "http://app_info_url")
|
16
|
+
# gb = GreenButton.download_data('your access token', 'http://greenbutton.data.url')
|
17
|
+
# # => [gb_data_description]
|
18
|
+
#
|
19
|
+
# # The following will download and parse all data for 2013
|
20
|
+
# require 'date'
|
21
|
+
# require 'ce-greenbutton'
|
22
|
+
# GreenButton.config(reg_access_token: "your access token"
|
23
|
+
# application_information_url: "http://app_info_url")
|
24
|
+
# gb = GreenButton.download_data('your access token',
|
25
|
+
# 'http://greenbutton.data.url'), Date.new(2013,1,1), Date.new(2013,12,31))
|
26
|
+
# # => [gb_data_description]
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# Changes for v1.1 (SunShot - Clearly Energy GreenButton Ruby Gem Update)
|
30
|
+
# 1. added new method download_data_ftp
|
31
|
+
# 2. parsing code is refactored to parse_data private method.
|
32
|
+
# 3. download_data method is updated to use the parse_data method.
|
33
|
+
# 4. config method is updated to support ftp options.
|
34
|
+
# 5. check_config method is updated to check ftp options.
|
35
|
+
# 6. added check_arguments_ftp method.
|
36
|
+
#
|
37
|
+
# Author: ahmed.seddiq
|
38
|
+
# Version: 1.1
|
39
|
+
module GreenButton
|
40
|
+
|
41
|
+
# Constant: the known data kind names mapped to the kind value,
|
42
|
+
# the values are retrieved from the GreenButton xml schema.
|
43
|
+
SERVICEKIND_NAMES = {
|
44
|
+
0 => 'electricity',
|
45
|
+
1 => 'gas',
|
46
|
+
2 => 'water',
|
47
|
+
3 => 'time',
|
48
|
+
4 => 'heat',
|
49
|
+
5 => 'refuse',
|
50
|
+
6 => 'sewerage',
|
51
|
+
7 => 'rates',
|
52
|
+
8 => 'tvLicence',
|
53
|
+
9 => 'internet'
|
54
|
+
}
|
55
|
+
|
56
|
+
# Hash used to hold registered GreenButton data interpreters.
|
57
|
+
# Key is the kind value of the data, the value is the interpreter module.
|
58
|
+
@interpreters = {}
|
59
|
+
|
60
|
+
# Public: Gets the interpreter of the given kind.
|
61
|
+
# kind - the kind of the GreenButton Data
|
62
|
+
# Returns the registered interpreter for the given kind, or nil if none found.
|
63
|
+
def self.interpreter(kind)
|
64
|
+
@interpreters[kind]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Downloads and parses the GreenButton data for the given
|
68
|
+
# subscription. It also allows to filter returned readings by date.
|
69
|
+
#
|
70
|
+
# access_token - represents the retail customer access token.
|
71
|
+
# subscription_id - represents the retail customer resourceURI.
|
72
|
+
# interval_start_time - represents the start date to retrieve data.
|
73
|
+
# interval_end_time - represents the end date to retrieve data.
|
74
|
+
#
|
75
|
+
# Examples
|
76
|
+
#
|
77
|
+
# # The following will download and parse all data for 2013
|
78
|
+
# require 'date'
|
79
|
+
# require 'ce-greenbutton'
|
80
|
+
# gb = GreenButton.download_data('688b026c-665f-4994-9139-6b21b13fbeee', 5,
|
81
|
+
# Date.new(2013,1,1), Date.new(2013,12,31))
|
82
|
+
# # => [gb_data_description]
|
83
|
+
#
|
84
|
+
# Returns an array of gb_data_description
|
85
|
+
# Raises ArgumentError if any passed argument is invalid.
|
86
|
+
# Propagates errors from OpenURI for any connection/authentication
|
87
|
+
def self.download_data(access_token, subscription_id,
|
88
|
+
interval_start_time = nil, interval_end_time = nil)
|
89
|
+
check_arguments(access_token, subscription_id, interval_start_time,
|
90
|
+
interval_end_time)
|
91
|
+
|
92
|
+
# Construct the resource url
|
93
|
+
resource_url = subscription_id
|
94
|
+
params = []
|
95
|
+
if interval_start_time
|
96
|
+
params << "published-min=#{interval_start_time.to_s}"
|
97
|
+
end
|
98
|
+
if interval_end_time
|
99
|
+
params << "published-max=#{interval_end_time.to_s}"
|
100
|
+
end
|
101
|
+
resource_url += (resource_url.index('?').nil?? '?':'&')+ params.join('&') if
|
102
|
+
params.length > 0
|
103
|
+
data = open(resource_url,
|
104
|
+
'Authorization' => "Bearer #{access_token}")
|
105
|
+
parse_data(access_token, data) #Return
|
106
|
+
end
|
107
|
+
|
108
|
+
# Public: Downloads and parses the GreenButton data, hosted in a FTP server.
|
109
|
+
# The FTP host, user, password and path is configured through the config
|
110
|
+
# method.
|
111
|
+
# The File name is constructed as follows:
|
112
|
+
# D_{utility_name}_{application_id}_{YYYYMMDDHHMMSS}.XML.
|
113
|
+
#
|
114
|
+
# application_id - represents the GreenButton 3rd party application id.
|
115
|
+
# time - used to construct file name. (optional, defaults
|
116
|
+
# to current time)
|
117
|
+
# utility_name - represents the utility name, used to construct the
|
118
|
+
# XML file name.
|
119
|
+
#
|
120
|
+
# Returns an array of gb_data_description
|
121
|
+
# Raises ArgumentError if any passed argument is invalid.
|
122
|
+
# Propagates errors from OpenURI for any connection/authentication
|
123
|
+
def self.download_data_ftp(application_id, time=Time.now, utility_name)
|
124
|
+
check_arguments_ftp(application_id, time, utility_name)
|
125
|
+
|
126
|
+
# construct the ftp url
|
127
|
+
ftp_url = "ftp://#{@ftp_user}:#{@ftp_password}@#{@ftp_host}/#{@ftp_path}/" +
|
128
|
+
"D_#{utility_name}_#{application_id}_#{time.strftime('%Y%m%d%H%M%S')}.XML"
|
129
|
+
|
130
|
+
parse_data(@ftp_user, open(ftp_url))
|
131
|
+
end
|
132
|
+
|
133
|
+
# Parses the given greenbutton data to the corresponding ruby objects.
|
134
|
+
#
|
135
|
+
# user_id - identifies the owner of the data.
|
136
|
+
# data - The source GreenButton xml data, can be string, or any stream returning
|
137
|
+
# from a call to open(uri)
|
138
|
+
#
|
139
|
+
# Returns the parsed array of gb_data_description
|
140
|
+
private
|
141
|
+
def self.parse_data(user_id, data)
|
142
|
+
# get ApplicationInformation
|
143
|
+
app_info = GreenButton::Parser::GbApplicationInformation.
|
144
|
+
parse(open(@application_information_url,
|
145
|
+
'Authorization' => "Bearer #{@reg_access_token}"))
|
146
|
+
|
147
|
+
feed = GreenButton::Parser.parse(data)
|
148
|
+
gb = []
|
149
|
+
unsupported_kinds = []
|
150
|
+
feed.entries.each_pair do |key, entry|
|
151
|
+
if entry.is_a? GreenButton::Parser::GbEntry
|
152
|
+
if entry.type == 'UsagePoint'
|
153
|
+
usage_point = entry.content.usage_point
|
154
|
+
if @interpreters[usage_point.kind].nil?
|
155
|
+
unsupported_kinds << usage_point.kind
|
156
|
+
else
|
157
|
+
gb_data_description = @interpreters[usage_point.kind]
|
158
|
+
.get_gb_data_description(entry, feed)
|
159
|
+
gb_data_description.custodian = app_info.data_custodian_id
|
160
|
+
gb_data_description.user_id = user_id
|
161
|
+
gb << gb_data_description
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
if gb.length == 0 and unsupported_kinds.length > 0
|
168
|
+
raise InvalidGbDataError, "Received unsupported GreenButton data #{unsupported_kinds.to_s}
|
169
|
+
Only Electricity (kind = 0) is supported."
|
170
|
+
end
|
171
|
+
gb
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
# Public: Registers a new Interpreter for the given data kind.
|
176
|
+
#
|
177
|
+
# kind - represents the kind of GreenButton data.
|
178
|
+
# interpreter_name - the name of the interpreter (String or Symbol)
|
179
|
+
#
|
180
|
+
# Examples
|
181
|
+
#
|
182
|
+
# # To register an interpreter for the gas data (kind = 1)
|
183
|
+
# # implement the interpreter with name
|
184
|
+
# # GreenButton::Interpreters::GasInterpreter
|
185
|
+
# gb = GreenButton.register_interpreter(1, :gas)
|
186
|
+
#
|
187
|
+
# Returns nothing.
|
188
|
+
# Raises ArgumentError if any passed argument is invalid.
|
189
|
+
def self.register_interpreter(kind, interpreter_name)
|
190
|
+
unless kind.is_a?Integer and kind >= 0
|
191
|
+
raise ArgumentError, 'kind must be positive integer'
|
192
|
+
end
|
193
|
+
unless interpreter_name.is_a?Symbol or interpreter_name.is_a?String
|
194
|
+
raise ArgumentError, 'interpreter_name must be symbol or string'
|
195
|
+
end
|
196
|
+
require "ce-greenbutton/interpreters/#{interpreter_name}_interpreter"
|
197
|
+
@interpreters[kind] = GreenButton::Interpreters.const_get(
|
198
|
+
"#{interpreter_name.to_s.capitalize}Interpreter")
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
# Public: configures the GreenButton module. It must be called before usage.
|
203
|
+
#
|
204
|
+
# Supported properties:
|
205
|
+
# reg_access_token: The registration access token, required,
|
206
|
+
# application_information_url: required,
|
207
|
+
# ftp-host: required for download_data_ftp,
|
208
|
+
# ftp-user: required for download_data_ftp,
|
209
|
+
# ftp-password: required for download_data_ftp,
|
210
|
+
# ftp-path: the ftp directory path with no leading or trailing backslashes,
|
211
|
+
# optional for download_data_ftp, defaults to empty string.
|
212
|
+
#
|
213
|
+
# For example:
|
214
|
+
# GreenButton.config(reg_access_token: 'your access token',
|
215
|
+
# application_information_url: 'http://app_info_url',
|
216
|
+
# ftp-host: 'your-ftp-host',
|
217
|
+
# ftp-user: 'ftp-user',
|
218
|
+
# ftp-password: 'ftp-password',
|
219
|
+
# ftp-path: 'path/to/ftp/directory' )
|
220
|
+
#
|
221
|
+
# Returns nothing.
|
222
|
+
#
|
223
|
+
def self.config(options = {})
|
224
|
+
@reg_access_token = options[:reg_access_token] || options['reg_access_token']
|
225
|
+
@application_information_url = options[:application_information_url] ||
|
226
|
+
options['application_information_url']
|
227
|
+
@ftp_host = options[:ftp_host] || options['ftp_host']
|
228
|
+
@ftp_user = options[:ftp_user] || options['ftp_user']
|
229
|
+
@ftp_password = options[:ftp_password] || options['ftp_password']
|
230
|
+
@ftp_path = options[:ftp_path] || options['ftp_path'] || ''
|
231
|
+
end
|
232
|
+
|
233
|
+
# try to load interpreters
|
234
|
+
SERVICEKIND_NAMES.each_pair do |kind, name|
|
235
|
+
begin
|
236
|
+
self.register_interpreter kind, name
|
237
|
+
rescue LoadError
|
238
|
+
# ignored
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Helper method to check arguments' validity of the download_data method.
|
243
|
+
#
|
244
|
+
# access_token - represents the retail customer access token.
|
245
|
+
# subscription_id - represents the retail customer resourceURI.
|
246
|
+
# interval_start_time - represents the start date to retrieve data (optional).
|
247
|
+
# interval_end_time - represents the end date to retrieve data (optional).
|
248
|
+
#
|
249
|
+
# Returns nothing.
|
250
|
+
# Raises ConfigurationError If the GreenButton was not configured by
|
251
|
+
# GreenButton.config prior to usage.
|
252
|
+
# Raises ArgumentError if any passed argument is invalid.
|
253
|
+
private
|
254
|
+
def self.check_arguments(access_token, subscription_id, interval_start_time,
|
255
|
+
interval_end_time)
|
256
|
+
check_config
|
257
|
+
|
258
|
+
unless access_token.is_a? String and access_token.strip.length > 0
|
259
|
+
raise ArgumentError, 'access_token must be a non-empty string.'
|
260
|
+
end
|
261
|
+
unless subscription_id =~ URI::regexp
|
262
|
+
raise ArgumentError, 'subscription_id must be a valid url'
|
263
|
+
end
|
264
|
+
unless interval_start_time.nil? or interval_start_time.is_a? Date
|
265
|
+
raise ArgumentError, 'interval_start_time must be a valid Date object.'
|
266
|
+
end
|
267
|
+
unless interval_end_time.nil? or interval_end_time.is_a? Date
|
268
|
+
raise ArgumentError, 'interval_end_time must be a valid Date object.'
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Helper method to check arguments' validity of the download_data_ftp method.
|
273
|
+
#
|
274
|
+
# application_id - represents the GreenButton 3rd party application id.
|
275
|
+
# time - used to construct file name. (optional, defaults
|
276
|
+
# to current time)
|
277
|
+
# utility_name - represents the utility name, used to construct the
|
278
|
+
# XML file name.
|
279
|
+
#
|
280
|
+
# Returns nothing.
|
281
|
+
# Raises ConfigurationError If the GreenButton was not configured by
|
282
|
+
# GreenButton.config prior to usage.
|
283
|
+
# Raises ArgumentError if any passed argument is invalid.
|
284
|
+
def self.check_arguments_ftp(application_id, time , utility_name)
|
285
|
+
# check module configuration with ftp options.
|
286
|
+
check_config(true)
|
287
|
+
unless application_id.is_a? String and application_id.strip.length > 0
|
288
|
+
raise ArgumentError, 'application_id must be a non-empty string.'
|
289
|
+
end
|
290
|
+
|
291
|
+
unless utility_name.is_a? String and utility_name.strip.length > 0
|
292
|
+
raise ArgumentError, 'utility_name must be a non-empty string.'
|
293
|
+
end
|
294
|
+
|
295
|
+
unless time.is_a? Time
|
296
|
+
raise ArgumentError, 'time must be a Time object.'
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Checks is the module is properly configured.
|
301
|
+
#
|
302
|
+
# ftp - whether to check ftp-related options.
|
303
|
+
#
|
304
|
+
# Raises ConfigurationError if any required configuration is missing.
|
305
|
+
def self.check_config(ftp=false)
|
306
|
+
if @application_information_url.nil?
|
307
|
+
raise ConfigurationError, 'application_information_url is not set, please call
|
308
|
+
GreenButton.config first'
|
309
|
+
end
|
310
|
+
if @reg_access_token.nil?
|
311
|
+
raise ConfigurationError, 'registration access token is not set please call
|
312
|
+
GreenButton.config first'
|
313
|
+
end
|
314
|
+
|
315
|
+
if ftp
|
316
|
+
if @ftp_host.nil?
|
317
|
+
raise ConfigurationError, 'ftp_host is not set, please call
|
318
|
+
GreenButton.config first'
|
319
|
+
end
|
320
|
+
if @ftp_user.nil?
|
321
|
+
raise ConfigurationError, 'ftp_user is not set, please call
|
322
|
+
GreenButton.config first'
|
323
|
+
end
|
324
|
+
if @ftp_password.nil?
|
325
|
+
raise ConfigurationError, 'ftp_password is not set, please call
|
326
|
+
GreenButton.config first'
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
# This error will be raised if the download_data method was called before calling
|
332
|
+
# the GreenButton.config
|
333
|
+
#
|
334
|
+
# Author: ahmed.seddiq
|
335
|
+
# Version: 1.0
|
336
|
+
class ConfigurationError < Exception
|
337
|
+
|
338
|
+
end
|
339
|
+
|
340
|
+
# Will be raised if a parsing or semantic error occurred during the data
|
341
|
+
# parsing/interpretation
|
342
|
+
#
|
343
|
+
# For example, if the data does not contain a LocalTimeParameters structure
|
344
|
+
#
|
345
|
+
# Author: ahmed.seddiq
|
346
|
+
# Version: 1.0
|
347
|
+
class InvalidGbDataError < Exception
|
348
|
+
|
349
|
+
end
|
350
|
+
|
351
|
+
|
352
|
+
end
|