rsmp 0.1.10 → 0.1.19

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.
@@ -23,7 +23,7 @@ module RSMP
23
23
  class MissingWatchdog < Error
24
24
  end
25
25
 
26
- class MissingAcknowledgment < Error
26
+ class MessageRejected < Error
27
27
  end
28
28
 
29
29
  class MissingAttribute < InvalidMessage
@@ -43,4 +43,13 @@ module RSMP
43
43
 
44
44
  class UnknownComponent < Error
45
45
  end
46
+
47
+ class UnknownCommand < Error
48
+ end
49
+
50
+ class UnknownStatus < Error
51
+ end
52
+
53
+ class ConfigurationError < Error
54
+ end
46
55
  end
@@ -27,6 +27,9 @@ module RSMP
27
27
 
28
28
  @@schemas = load_schemas
29
29
 
30
+ def self.make_m_id
31
+ SecureRandom.uuid()
32
+ end
30
33
 
31
34
  def self.parse_attributes json
32
35
  raise ArgumentError unless json
@@ -80,8 +83,12 @@ module RSMP
80
83
  @attributes["mId"]
81
84
  end
82
85
 
86
+ def self.shorten_m_id m_id, length=4
87
+ m_id[0..length-1]
88
+ end
89
+
83
90
  def m_id_short
84
- @attributes["mId"][0..3]
91
+ Message.shorten_m_id @attributes["mId"]
85
92
  end
86
93
 
87
94
  def attribute key
@@ -126,7 +133,7 @@ module RSMP
126
133
 
127
134
  def ensure_message_id
128
135
  # if message id is empty, generate a new one
129
- @attributes["mId"] ||= SecureRandom.uuid()
136
+ @attributes["mId"] ||= Message.make_m_id
130
137
  end
131
138
 
132
139
  def validate sxl=nil
@@ -197,6 +204,22 @@ module RSMP
197
204
  end
198
205
  end
199
206
 
207
+ class AlarmRequest < Message
208
+ def initialize attributes = {}
209
+ super({
210
+ "type" => "Alarm",
211
+ }.merge attributes)
212
+ end
213
+ end
214
+
215
+ class AlarmAcknowledged < Message
216
+ def initialize attributes = {}
217
+ super({
218
+ "type" => "Alarm",
219
+ }.merge attributes)
220
+ end
221
+ end
222
+
200
223
  class Watchdog < Message
201
224
  def initialize attributes = {}
202
225
  super({
@@ -5,19 +5,46 @@
5
5
 
6
6
  module RSMP
7
7
  class Node < Base
8
- attr_reader :archive, :logger, :task
8
+ include Wait
9
+
10
+ attr_reader :archive, :logger, :task, :deferred
9
11
 
10
12
  def initialize options
11
13
  super options
14
+ @task = options[:task]
15
+ @deferred = []
16
+ end
17
+
18
+ def defer item
19
+ @deferred << item
20
+ end
21
+
22
+ def process_deferred
23
+ cloned = @deferred.clone # clone in case do_deferred restarts the current task
24
+ @deferred.clear
25
+ cloned.each do |item|
26
+ do_deferred item
27
+ end
28
+ end
29
+
30
+ def do_deferred item
31
+ end
32
+
33
+ def do_start task
34
+ task.annotate self.class.to_s
35
+ @task = task
36
+ start_action
37
+ idle
12
38
  end
13
39
 
14
40
  def start
15
41
  starting
16
- Async do |task|
17
- task.annotate self.class
18
- @task = task
19
- start_action
20
- idle
42
+ if @task
43
+ do_start @task
44
+ else
45
+ Async do |task|
46
+ do_start task
47
+ end
21
48
  end
22
49
  rescue Errno::EADDRINUSE => e
23
50
  log "Cannot start: #{e.to_s}", level: :error
@@ -6,22 +6,6 @@ module RSMP
6
6
  class Probe
7
7
  attr_reader :condition, :items, :done
8
8
 
9
- # block should send a message and return message just sent
10
- def self.collect_response proxy, options={}, &block
11
- from = proxy.archive.current_index
12
- sent = yield
13
- raise RuntimeError unless sent && sent[:message].is_a?(RSMP::Message)
14
- item = proxy.archive.capture(options.merge(from: from+1, num: 1, with_message: true)) do |item|
15
- ["CommandResponse","StatusResponse","MessageNotAck"].include?(item[:message].type)
16
- end
17
- if item
18
- item[:message]
19
- else
20
- nil
21
- end
22
- end
23
-
24
-
25
9
  def initialize archive
26
10
  raise ArgumentError.new("Archive expected") unless archive.is_a? Archive
27
11
  @archive = archive
@@ -30,6 +14,7 @@ module RSMP
30
14
  end
31
15
 
32
16
  def capture task, options={}, &block
17
+ raise ArgumentError.new("timeout option is missing") unless options[:timeout]
33
18
  @options = options
34
19
  @block = block
35
20
  @num = options[:num]
@@ -37,8 +22,6 @@ module RSMP
37
22
  if options[:earliest]
38
23
  from = find_timestamp_index options[:earliest]
39
24
  backscan from
40
- elsif options[:from]
41
- backscan options[:from]
42
25
  end
43
26
 
44
27
  # if backscan didn't find enough items, then
@@ -62,6 +45,7 @@ module RSMP
62
45
  end
63
46
 
64
47
  def find_timestamp_index earliest
48
+ return 0 if earliest == :start
65
49
  (0..@archive.items.size).bsearch do |i| # use binary search to find item index
66
50
  @archive.items[i][:timestamp] >= earliest
67
51
  end
@@ -105,6 +89,9 @@ module RSMP
105
89
  end
106
90
  return if @options[:level] && item[:level] != @options[:level]
107
91
  return false if @options[:with_message] && !(item[:direction] && item[:message])
92
+ if @options[:component]
93
+ return false if item[:message].attributes['cId'] && item[:message].attributes['cId'] != @options[:component]
94
+ end
108
95
  if @block
109
96
  return false if @block.call(item) == false
110
97
  end
@@ -2,7 +2,9 @@
2
2
 
3
3
  module RSMP
4
4
  class Proxy < Base
5
- attr_reader :state, :archive, :connection_info, :sxl
5
+ include Wait
6
+
7
+ attr_reader :state, :archive, :connection_info, :sxl, :task
6
8
 
7
9
  def initialize options
8
10
  super options
@@ -18,7 +20,7 @@ module RSMP
18
20
  def run
19
21
  start
20
22
  @reader.wait if @reader
21
- stop
23
+ stop unless [:stopped, :stopping].include? @state
22
24
  end
23
25
 
24
26
  def ready?
@@ -115,26 +117,42 @@ module RSMP
115
117
  interval = @settings["timer_interval"] || 1
116
118
  log "Starting #{name} with interval #{interval} seconds", level: :debug
117
119
  @latest_watchdog_received = RSMP.now_object
120
+
118
121
  @timer = @task.async do |task|
119
122
  task.annotate "timer"
123
+ next_time = Time.now.to_f
120
124
  loop do
121
- now = RSMP.now_object
122
- break if timer(now) == false
123
- rescue StandardError => e
124
- log ["#{name} exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
125
+ begin
126
+ now = RSMP.now_object
127
+ timer(now)
128
+ rescue EOFError => e
129
+ log "Timer: Connection closed: #{e}", level: :warning
130
+ rescue IOError => e
131
+ log "Timer: IOError", level: :warning
132
+ rescue Errno::ECONNRESET
133
+ log "Timer: Connection reset by peer", level: :warning
134
+ rescue Errno::EPIPE => e
135
+ log "Timer: Broken pipe", level: :warning
136
+ rescue StandardError => e
137
+ log "Error: #{e}", level: :debug
138
+ #rescue StandardError => e
139
+ # log ["Timer error: #{e}",e.backtrace].flatten.join("\n"), level: :error
140
+ end
125
141
  ensure
126
- task.sleep interval
142
+ next_time += interval
143
+ duration = next_time - Time.now.to_f
144
+ task.sleep duration
127
145
  end
128
146
  end
129
147
  end
130
148
 
131
149
  def timer now
132
- check_watchdog_send_time now
133
- return false if check_ack_timeout now
134
- return false if check_watchdog_timeout now
150
+ watchdog_send_timer now
151
+ check_ack_timeout now
152
+ check_watchdog_timeout now
135
153
  end
136
154
 
137
- def check_watchdog_send_time now
155
+ def watchdog_send_timer now
138
156
  return unless @watchdog_started
139
157
  return if @settings["watchdog_interval"] == :never
140
158
 
@@ -148,8 +166,6 @@ module RSMP
148
166
  send_watchdog now
149
167
  end
150
168
  end
151
- rescue StandardError => e
152
- log ["Watchdog error: #{e}",e.backtrace].flatten.join("\n"), level: :error
153
169
  end
154
170
 
155
171
  def send_watchdog now=nil
@@ -167,24 +183,18 @@ module RSMP
167
183
  if now > latest
168
184
  log "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds", level: :error
169
185
  stop
170
- return true
171
186
  end
172
187
  end
173
- false
174
188
  end
175
189
 
176
190
  def check_watchdog_timeout now
177
-
178
191
  timeout = @settings["watchdog_timeout"]
179
192
  latest = @latest_watchdog_received + timeout
180
193
  left = latest - now
181
- log "Check watchdog, time:#{timeout}, last:#{@latest_watchdog_received}, now: #{now}, latest:#{latest}, left #{left}, fail:#{left<0}", level: :debug
182
194
  if left < 0
183
- log "No Watchdog within #{timeout} seconds, received at #{@latest_watchdog_received}, now is #{now}, diff #{now-latest}", level: :error
195
+ log "No Watchdog within #{timeout} seconds", level: :error
184
196
  stop
185
- return true
186
197
  end
187
- false
188
198
  end
189
199
 
190
200
  def stop_tasks
@@ -212,7 +222,7 @@ module RSMP
212
222
 
213
223
  def buffer_message message
214
224
  # TODO
215
- log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
225
+ #log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
216
226
  end
217
227
 
218
228
  def log_send message, reason=nil
@@ -235,12 +245,13 @@ module RSMP
235
245
  message.validate sxl
236
246
  expect_version_message(message) unless @version_determined
237
247
  process_message message
248
+ process_deferred
238
249
  message
239
250
  rescue InvalidPacket => e
240
- warning "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
251
+ log "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}", level: :warning
241
252
  nil
242
253
  rescue MalformedMessage => e
243
- warning "Received malformed message, #{e.message}", Malformed.new(attributes)
254
+ log "Received malformed message, #{e.message}", message: Malformed.new(attributes), level: :warning
244
255
  # cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
245
256
  nil
246
257
  rescue SchemaError => e
@@ -331,7 +342,7 @@ module RSMP
331
342
  def wait_for_state state, timeout
332
343
  states = [state].flatten
333
344
  return if states.include?(@state)
334
- RSMP::Wait.wait_for(@task,@state_condition,timeout) do |s|
345
+ wait_for(@state_condition,timeout) do |s|
335
346
  states.include?(@state)
336
347
  end
337
348
  @state
@@ -441,27 +452,16 @@ module RSMP
441
452
  def version_acknowledged
442
453
  end
443
454
 
444
- def wait_for_acknowledgement original, timeout, options={}
455
+ def wait_for_acknowledgement original, timeout
445
456
  raise ArgumentError unless original
446
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
447
- message.is_a?(MessageAck) &&
448
- message.attributes["oMId"] == original.m_id
449
- end
450
- end
451
-
452
- def wait_for_not_acknowledged original, timeout
453
- raise ArgumentError unless original
454
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
455
- message.is_a?(MessageNotAck) &&
456
- message.attributes["oMId"] == original.m_id
457
- end
458
- end
459
-
460
- def wait_for_acknowledgements timeout
461
- return if @awaiting_acknowledgement.empty?
462
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
463
- @awaiting_acknowledgement.empty?
457
+ wait_for(@acknowledgement_condition,timeout) do |message|
458
+ if message.is_a?(MessageNotAck) && message.attributes["oMId"] == original.m_id
459
+ raise RSMP::MessageRejected.new(message.attributes['rea'])
460
+ end
461
+ message.is_a?(MessageAck) && message.attributes["oMId"] == original.m_id
464
462
  end
463
+ rescue Async::TimeoutError
464
+ raise RSMP::TimeoutError.new("Acknowledgement for #{original.type} #{original.m_id} not received within #{timeout}s")
465
465
  end
466
466
 
467
467
  def node
@@ -27,6 +27,7 @@ module RSMP
27
27
  { 'ip' => '127.0.0.1', 'port' => 12111 }
28
28
  ],
29
29
  'rsmp_versions' => ['3.1.1','3.1.2','3.1.3','3.1.4'],
30
+ 'sxl' => 'traffic_light_controller',
30
31
  'sxl_version' => '1.0.7',
31
32
  'timer_interval' => 0.1,
32
33
  'watchdog_interval' => 1,
@@ -69,7 +70,7 @@ module RSMP
69
70
  end
70
71
  end
71
72
 
72
- def build_connector settings
73
+ def build_proxy settings
73
74
  SupervisorProxy.new settings
74
75
  end
75
76
 
@@ -80,7 +81,7 @@ module RSMP
80
81
  end
81
82
 
82
83
  def connect_to_supervisor task, supervisor_settings
83
- proxy = build_connector({
84
+ proxy = build_proxy({
84
85
  site: self,
85
86
  task: @task,
86
87
  settings: @site_settings,
@@ -109,7 +110,9 @@ module RSMP
109
110
  if @site_settings["reconnect_interval"] != :no
110
111
  # sleep until waken by reconnect() or the reconnect interval passed
111
112
  proxy.set_state :wait_for_reconnect
112
- task.with_timeout(@site_settings["reconnect_interval"]) { @sleep_condition.wait }
113
+ task.with_timeout(@site_settings["reconnect_interval"]) do
114
+ @sleep_condition.wait
115
+ end
113
116
  else
114
117
  proxy.set_state :cannot_connect
115
118
  break
@@ -140,5 +143,6 @@ module RSMP
140
143
  proxy.stop
141
144
  end
142
145
  end
146
+
143
147
  end
144
148
  end
@@ -13,8 +13,10 @@ module RSMP
13
13
 
14
14
  def setup_components settings
15
15
  return unless settings
16
- settings.each_pair do |id,settings|
17
- @components[id] = build_component(id,settings)
16
+ settings.each_pair do |type,components_by_type|
17
+ components_by_type.each_pair do |id,settings|
18
+ @components[id] = build_component(id:id, type:type, settings:settings)
19
+ end
18
20
  end
19
21
  end
20
22
 
@@ -22,8 +24,8 @@ module RSMP
22
24
  @components[component.c_id] = component
23
25
  end
24
26
 
25
- def build_component id, settings={}
26
- Component.new id: id, node: self, grouped: true
27
+ def build_component id:, type:, settings:{}
28
+ Component.new id:id, node: self, grouped: true
27
29
  end
28
30
 
29
31
  def find_component component_id
@@ -23,6 +23,11 @@ module RSMP
23
23
  start_reader
24
24
  end
25
25
 
26
+ def stop
27
+ log "Closing connection to site", level: :info
28
+ super
29
+ end
30
+
26
31
  def connection_complete
27
32
  super
28
33
  log "Connection to site #{@site_id} established", level: :info
@@ -50,11 +55,15 @@ module RSMP
50
55
  end
51
56
  end
52
57
 
58
+ def process_deferred
59
+ supervisor.process_deferred
60
+ end
61
+
53
62
  def version_accepted message
54
63
  log "Received Version message for site #{@site_id} using RSMP #{@rsmp_version}", message: message, level: :log
55
64
  start_timer
56
65
  acknowledge message
57
- send_version @site_id, @rsmp_version
66
+ send_version @site_id, @settings['rsmp_versions']
58
67
  @version_determined = true
59
68
 
60
69
  if @settings['sites']
@@ -81,7 +90,7 @@ module RSMP
81
90
  component = @components[c_id]
82
91
  if component == nil
83
92
  if @site_settings == nil || @site_settings['components'] == nil
84
- component = build_component c_id
93
+ component = build_component(id:c_id, type:nil)
85
94
  @components[c_id] = component
86
95
  log "Adding component #{c_id} to site #{@site_id}", level: :info
87
96
  else
@@ -123,16 +132,38 @@ module RSMP
123
132
  @supervisor.site_ids_changed
124
133
  end
125
134
 
126
- def request_status component, status_list, timeout=nil
127
- raise NotReady unless @state == :ready
135
+ def fetch_status parent_task, options
136
+ wait_for_status_responses(parent_task,options) do |m_id|
137
+ request_status options.merge(m_id: m_id)
138
+ end
139
+ end
140
+
141
+ # Convert from a short ruby hash:
142
+ # {:S0001=>[:signalgroupstatus, :cyclecounter, :basecyclecounter, :stage]}
143
+ # to an rsmp-style list:
144
+ # [{"sCI"=>"S0001", "n"=>"signalgroupstatus"}, {"sCI"=>"S0001", "n"=>"cyclecounter"}, {"sCI"=>"S0001", "n"=>"basecyclecounter"}, {"sCI"=>"S0001", "n"=>"stage"}]
145
+ #
146
+ # If the input is already an array, just return it
147
+ def convert_status_list list
148
+ return list.clone if list.is_a? Array
149
+ list.map do |status_code_id,names|
150
+ names.map do |name|
151
+ { 'sCI' => status_code_id.to_s, 'n' => name.to_s }
152
+ end
153
+ end.flatten
154
+ end
155
+
156
+ def request_status options
157
+ raise NotReady unless ready?
128
158
  message = RSMP::StatusRequest.new({
129
159
  "ntsOId" => '',
130
160
  "xNId" => '',
131
- "cId" => component,
132
- "sS" => status_list
161
+ "cId" => options[:component],
162
+ "sS" => convert_status_list(options[:status_list]),
163
+ "mId" => options[:m_id]
133
164
  })
134
165
  send_message message
135
- return message, wait_for_status_response(message: message, timeout: timeout)
166
+ message
136
167
  end
137
168
 
138
169
  def process_status_response message
@@ -140,41 +171,26 @@ module RSMP
140
171
  acknowledge message
141
172
  end
142
173
 
143
- def wait_for_status_response options
144
- raise ArgumentError unless options[:message]
145
- item = @archive.capture(@task, options.merge(
146
- type: ['StatusResponse','MessageNotAck'],
147
- with_message: true,
148
- num: 1
149
- )) do |item|
150
- if item[:message].type == 'MessageNotAck'
151
- next item[:message].attribute('oMId') == options[:message].m_id
152
- elsif item[:message].type == 'StatusResponse'
153
- next item[:message].attribute('cId') == options[:message].attribute('cId')
154
- end
155
- end
156
- item[:message] if item
157
- end
158
-
159
- def subscribe_to_status component, status_list, timeout
160
- raise NotReady unless @state == :ready
174
+ def subscribe_to_status component, status_list, options={}
175
+ raise NotReady unless ready?
161
176
  message = RSMP::StatusSubscribe.new({
162
177
  "ntsOId" => '',
163
178
  "xNId" => '',
164
179
  "cId" => component,
165
- "sS" => status_list
180
+ "sS" => convert_status_list(status_list),
181
+ 'mId'=>options[:m_id]
166
182
  })
167
183
  send_message message
168
- return message, wait_for_status_update(component: component, timeout: timeout)
184
+ return message
169
185
  end
170
186
 
171
187
  def unsubscribe_to_status component, status_list
172
- raise NotReady unless @state == :ready
188
+ raise NotReady unless ready?
173
189
  message = RSMP::StatusUnsubscribe.new({
174
190
  "ntsOId" => '',
175
191
  "xNId" => '',
176
192
  "cId" => component,
177
- "sS" => status_list
193
+ "sS" => convert_status_list(status_list)
178
194
  })
179
195
  send_message message
180
196
  message
@@ -185,50 +201,79 @@ module RSMP
185
201
  acknowledge message
186
202
  end
187
203
 
188
- def wait_for_status_update options={}
189
- raise ArgumentError unless options[:component]
190
- item = @archive.capture(@task,options.merge(type: "StatusUpdate", with_message: true, num: 1)) do |item|
204
+ def status_match? query, item
205
+ return false if query[:sCI] && query[:sCI] != item['sCI']
206
+ return false if query[:n] && query[:n] != item['n']
207
+ return false if query[:q] && query[:q] != item['q']
208
+ if query[:s].is_a? Regexp
209
+ return false if query[:s] && item['s'] !~ query[:s]
210
+ else
211
+ return false if query[:s] && item['s'] != query[:s]
212
+ end
213
+ true
214
+ end
215
+
216
+ def wait_for_alarm options={}
217
+ raise ArgumentError.new("component argument is missing") unless options[:component]
218
+ matching_alarm = nil
219
+ item = @archive.capture(@task,options.merge(type: "Alarm", with_message: true, num: 1)) do |item|
191
220
  # TODO check components
192
- found = false
193
- sS = item[:message].attributes['sS']
194
- sS.each do |status|
195
- break if options[:sCI] && options[:sCI] != status['sCI']
196
- break if options[:n] && options[:n] != status['n']
197
- break if options[:q] && options[:q] != status['q']
198
- break if options[:s] && options[:s] != status['s']
199
- found = true
200
- break
201
- end
202
- found
221
+ matching_alarm = nil
222
+ alarm = item[:message]
223
+ next if options[:aCId] && options[:aCId] != alarm.attribute("aCId")
224
+ next if options[:aSp] && options[:aSp] != alarm.attribute("aSp")
225
+ next if options[:aS] && options[:aS] != alarm.attribute("aS")
226
+ matching_alarm = alarm
227
+ break
228
+ end
229
+ if item
230
+ { message: item[:message], status: matching_alarm }
203
231
  end
204
- item[:message] if item
205
232
  end
206
233
 
207
- def send_command component, args
208
- raise NotReady unless @state == :ready
209
- message = RSMP::CommandRequest.new({
234
+ def send_alarm_acknowledgement component, alarm_code
235
+ message = RSMP::AlarmAcknowledged.new({
210
236
  "ntsOId" => '',
211
237
  "xNId" => '',
212
238
  "cId" => component,
213
- "arg" => args
239
+ "aCId" => alarm_code,
240
+ "xACId" => '',
241
+ "xNACId" => '',
242
+ "aSp" => 'Acknowledge'
214
243
  })
215
244
  send_message message
216
245
  message
217
246
  end
218
247
 
219
- def process_command_response message
220
- log "Received #{message.type}", message: message, level: :log
221
- acknowledge message
222
- end
223
-
224
- def wait_for_command_response options
225
- raise ArgumentError unless options[:component]
226
- item = @archive.capture(@task,options.merge(num: 1, type: "CommandResponse", with_message: true)) do |item|
227
- # check component
248
+ def wait_for_alarm_acknowledgement_response options
249
+ raise ArgumentError.new("component argument is missing") unless options[:component]
250
+ item = @archive.capture(@task,options.merge(
251
+ num: 1,
252
+ type: ['AlarmAcknowledgedResponse','MessageNotAck'],
253
+ with_message: true
254
+ )) do |item|
255
+ if item[:message].type == 'MessageNotAck'
256
+ next item[:message].attribute('oMId') == options[:message].m_id
257
+ elsif item[:message].type == 'AlarmAcknowledgedResponse'
258
+ next item[:message].attribute('cId') == options[:message].attribute('cId')
259
+ end
228
260
  end
229
261
  item[:message] if item
230
262
  end
231
263
 
264
+ def send_command component, args, options={}
265
+ raise NotReady unless ready?
266
+ message = RSMP::CommandRequest.new({
267
+ "ntsOId" => '',
268
+ "xNId" => '',
269
+ "cId" => component,
270
+ "arg" => args,
271
+ "mId" => options[:m_id]
272
+ })
273
+ send_message message
274
+ message
275
+ end
276
+
232
277
  def set_watchdog_interval interval
233
278
  @settings["watchdog_interval"] = interval
234
279
  end
@@ -261,6 +306,5 @@ module RSMP
261
306
  site_ids_changed
262
307
  end
263
308
 
264
-
265
309
  end
266
- end
310
+ end