ce-greenbutton 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,3 @@
1
+ module GreenButton
2
+ VERSION = "0.1.0"
3
+ 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