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.
@@ -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