rsmp 0.35.2 → 0.38.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 +4 -4
- data/.devcontainer/devcontainer.json +22 -0
- data/.github/copilot-instructions.md +33 -0
- data/.github/workflows/copilot-setup-steps.yml +35 -0
- data/.github/workflows/rspec.yaml +2 -1
- data/.github/workflows/rubocop.yaml +17 -0
- data/.gitignore +7 -7
- data/.rubocop.yml +80 -0
- data/Gemfile +13 -1
- data/Gemfile.lock +73 -35
- data/Rakefile +3 -3
- data/bin/console +2 -4
- data/documentation/tasks.md +4 -4
- data/lib/rsmp/cli.rb +147 -124
- data/lib/rsmp/collect/ack_collector.rb +8 -7
- data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
- data/lib/rsmp/collect/alarm_collector.rb +31 -23
- data/lib/rsmp/collect/alarm_matcher.rb +3 -3
- data/lib/rsmp/collect/collector/logging.rb +17 -0
- data/lib/rsmp/collect/collector/reporting.rb +44 -0
- data/lib/rsmp/collect/collector/status.rb +34 -0
- data/lib/rsmp/collect/collector.rb +69 -150
- data/lib/rsmp/collect/command_matcher.rb +19 -6
- data/lib/rsmp/collect/command_response_collector.rb +7 -7
- data/lib/rsmp/collect/distributor.rb +14 -11
- data/lib/rsmp/collect/filter.rb +31 -15
- data/lib/rsmp/collect/matcher.rb +7 -11
- data/lib/rsmp/collect/queue.rb +4 -4
- data/lib/rsmp/collect/receiver.rb +10 -12
- data/lib/rsmp/collect/state_collector.rb +116 -77
- data/lib/rsmp/collect/status_collector.rb +6 -6
- data/lib/rsmp/collect/status_matcher.rb +17 -7
- data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -37
- data/lib/rsmp/{component.rb → component/component.rb} +15 -15
- data/lib/rsmp/component/component_base.rb +89 -0
- data/lib/rsmp/component/component_proxy.rb +75 -0
- data/lib/rsmp/component/components.rb +63 -0
- data/lib/rsmp/convert/export/json_schema.rb +116 -110
- data/lib/rsmp/convert/import/yaml.rb +21 -18
- data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +5 -6
- data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +2 -1
- data/lib/rsmp/helpers/error.rb +71 -0
- data/lib/rsmp/{inspect.rb → helpers/inspect.rb} +6 -10
- data/lib/rsmp/log/archive.rb +98 -0
- data/lib/rsmp/log/colorization.rb +41 -0
- data/lib/rsmp/log/filtering.rb +54 -0
- data/lib/rsmp/log/logger.rb +206 -0
- data/lib/rsmp/{logging.rb → log/logging.rb} +5 -7
- data/lib/rsmp/message.rb +159 -148
- data/lib/rsmp/{node.rb → node/node.rb} +19 -17
- data/lib/rsmp/node/protocol.rb +37 -0
- data/lib/rsmp/node/site/site.rb +195 -0
- data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
- data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
- data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
- data/lib/rsmp/node/supervisor/supervisor.rb +72 -0
- data/lib/rsmp/{task.rb → node/task.rb} +29 -19
- data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
- data/lib/rsmp/proxy/modules/receive.rb +119 -0
- data/lib/rsmp/proxy/modules/send.rb +76 -0
- data/lib/rsmp/proxy/modules/state.rb +25 -0
- data/lib/rsmp/proxy/modules/tasks.rb +105 -0
- data/lib/rsmp/proxy/modules/versions.rb +69 -0
- data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
- data/lib/rsmp/proxy/proxy.rb +199 -0
- data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
- data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
- data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
- data/lib/rsmp/proxy/site/modules/status.rb +110 -0
- data/lib/rsmp/proxy/site/site_proxy.rb +205 -0
- data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
- data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
- data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
- data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
- data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +178 -0
- data/lib/rsmp/tlc/detector_logic.rb +18 -34
- data/lib/rsmp/tlc/input_states.rb +126 -0
- data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
- data/lib/rsmp/tlc/modules/display.rb +78 -0
- data/lib/rsmp/tlc/modules/helpers.rb +41 -0
- data/lib/rsmp/tlc/modules/inputs.rb +173 -0
- data/lib/rsmp/tlc/modules/modes.rb +253 -0
- data/lib/rsmp/tlc/modules/outputs.rb +30 -0
- data/lib/rsmp/tlc/modules/plans.rb +218 -0
- data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
- data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
- data/lib/rsmp/tlc/modules/system.rb +140 -0
- data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
- data/lib/rsmp/tlc/signal_group.rb +37 -41
- data/lib/rsmp/tlc/signal_plan.rb +14 -11
- data/lib/rsmp/tlc/signal_priority.rb +39 -35
- data/lib/rsmp/tlc/startup_sequence.rb +59 -0
- data/lib/rsmp/tlc/traffic_controller.rb +39 -1006
- data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +86 -49
- data/rsmp.gemspec +24 -30
- metadata +87 -130
- data/lib/rsmp/archive.rb +0 -76
- data/lib/rsmp/collect/message_matchers.rb +0 -0
- data/lib/rsmp/component_base.rb +0 -87
- data/lib/rsmp/component_proxy.rb +0 -57
- data/lib/rsmp/components.rb +0 -65
- data/lib/rsmp/error.rb +0 -71
- data/lib/rsmp/logger.rb +0 -216
- data/lib/rsmp/proxy.rb +0 -695
- data/lib/rsmp/site.rb +0 -188
- data/lib/rsmp/site_proxy.rb +0 -389
- data/lib/rsmp/supervisor.rb +0 -287
- data/lib/rsmp/supervisor_proxy.rb +0 -516
- data/lib/rsmp/tlc/inputs.rb +0 -134
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
# RSMP component base class.
|
|
3
|
+
|
|
4
|
+
class ComponentBase
|
|
5
|
+
include Inspect
|
|
6
|
+
|
|
7
|
+
attr_reader :c_id, :ntsoid, :xnid, :node, :alarms, :statuses,
|
|
8
|
+
:aggregated_status, :aggregated_status_bools, :grouped
|
|
9
|
+
|
|
10
|
+
AGGREGATED_STATUS_KEYS = %i[local_control
|
|
11
|
+
communication_distruption
|
|
12
|
+
high_priority_alarm
|
|
13
|
+
medium_priority_alarm
|
|
14
|
+
low_priority_alarm
|
|
15
|
+
normal
|
|
16
|
+
rest
|
|
17
|
+
not_connected].freeze
|
|
18
|
+
|
|
19
|
+
def initialize(node:, id:, ntsoid: nil, xnid: nil, grouped: false)
|
|
20
|
+
if grouped == false && (ntsoid || xnid)
|
|
21
|
+
raise RSMP::ConfigurationError,
|
|
22
|
+
'ntsoid and xnid are only allowed for grouped objects'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@c_id = id
|
|
26
|
+
@ntsoid = ntsoid
|
|
27
|
+
@xnid = xnid
|
|
28
|
+
@node = node
|
|
29
|
+
@grouped = grouped
|
|
30
|
+
clear_aggregated_status
|
|
31
|
+
@alarms = {}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def now
|
|
35
|
+
node.now
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def clear_alarm_timestamps
|
|
39
|
+
@alarms.each_value(&:clear_timestamp)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_alarm_state(alarm_code)
|
|
43
|
+
@alarms[alarm_code] ||= RSMP::AlarmState.new component: self, code: alarm_code
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def clear_aggregated_status
|
|
47
|
+
@aggregated_status = []
|
|
48
|
+
@aggregated_status_bools = Array.new(8, false)
|
|
49
|
+
@aggregated_status_bools[5] = true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def log(str, options)
|
|
53
|
+
default = { component: c_id }
|
|
54
|
+
@node.log str, default.merge(options)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def set_aggregated_status(status, options = {})
|
|
58
|
+
status = [status] if status.is_a? Symbol
|
|
59
|
+
raise InvalidArgument unless status.is_a? Array
|
|
60
|
+
|
|
61
|
+
input = status & AGGREGATED_STATUS_KEYS
|
|
62
|
+
return unless input != @aggregated_status
|
|
63
|
+
|
|
64
|
+
AGGREGATED_STATUS_KEYS.each_with_index do |key, index|
|
|
65
|
+
@aggregated_status_bools[index] = status.include?(key)
|
|
66
|
+
end
|
|
67
|
+
aggregated_status_changed options
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def aggregated_status_bools=(status)
|
|
71
|
+
raise InvalidArgument unless status.is_a? Array
|
|
72
|
+
raise InvalidArgument unless status.size == 8
|
|
73
|
+
|
|
74
|
+
return unless status != @aggregated_status_bools
|
|
75
|
+
|
|
76
|
+
@aggregated_status = []
|
|
77
|
+
AGGREGATED_STATUS_KEYS.each_with_index do |key, index|
|
|
78
|
+
on = status[index] == true
|
|
79
|
+
@aggregated_status_bools[index] = on
|
|
80
|
+
@aggregated_status << key if on
|
|
81
|
+
end
|
|
82
|
+
aggregated_status_changed
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def aggregated_status_changed(options = {})
|
|
86
|
+
@node.aggregated_status_changed self, options
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
# A proxy to a remote RSMP component.
|
|
3
|
+
class ComponentProxy < ComponentBase
|
|
4
|
+
def initialize(node:, id:, ntsoid: nil, xnid: nil, grouped: false)
|
|
5
|
+
super
|
|
6
|
+
@alarms = {}
|
|
7
|
+
@statuses = {}
|
|
8
|
+
@allow_repeat_updates = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# allow the next status update to be a repeat value
|
|
12
|
+
def allow_repeat_updates(subscribe_list)
|
|
13
|
+
subscribe_list.each do |item|
|
|
14
|
+
sci = item['sCI']
|
|
15
|
+
n = item['n']
|
|
16
|
+
@allow_repeat_updates[sci] ||= Set.new # Set is like an array, but with no duplicates
|
|
17
|
+
@allow_repeat_updates[sci] << n
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Check that were not receiving repeated update values.
|
|
22
|
+
# The check is not performed for item with an update interval.
|
|
23
|
+
def check_repeat_values(message, subscription_list)
|
|
24
|
+
message.attribute('sS').each do |item|
|
|
25
|
+
check_status_item_for_repeats(item, subscription_list)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def check_status_item_for_repeats(item, subscription_list)
|
|
30
|
+
status_code = item['sCI']
|
|
31
|
+
status_name = item['n']
|
|
32
|
+
return if update_rate_set?(subscription_list, status_code, status_name)
|
|
33
|
+
return unless should_check_repeats?(subscription_list, status_code, status_name)
|
|
34
|
+
|
|
35
|
+
new_values = { 's' => item['s'], 'q' => item['q'] }
|
|
36
|
+
old_values = @statuses.dig(status_code, status_name)
|
|
37
|
+
raise RSMP::RepeatedStatusError, "no change for #{status_code} '#{status_name}'" if new_values == old_values
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def update_rate_set?(subscription_list, status_code, status_name)
|
|
41
|
+
urt = subscription_list.dig(c_id, status_code, status_name, 'uRt')
|
|
42
|
+
urt.to_i.positive?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def should_check_repeats?(subscription_list, status_code, status_name)
|
|
46
|
+
soc = subscription_list.dig(c_id, status_code, status_name, 'sOc')
|
|
47
|
+
return false if soc == false
|
|
48
|
+
return false if @allow_repeat_updates[status_code]&.include?(status_name)
|
|
49
|
+
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Store the latest status update values
|
|
54
|
+
def store_status(message)
|
|
55
|
+
message.attribute('sS').each do |item|
|
|
56
|
+
sci = item['sCI']
|
|
57
|
+
n = item['n']
|
|
58
|
+
s = item['s']
|
|
59
|
+
q = item['q']
|
|
60
|
+
@statuses[sci] ||= {}
|
|
61
|
+
@statuses[sci][n] = { 's' => s, 'q' => q }
|
|
62
|
+
|
|
63
|
+
# once a value is received, don't allow the value to be a repeat
|
|
64
|
+
@allow_repeat_updates[sci]&.delete(n)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# handle incoming alarm
|
|
69
|
+
def handle_alarm(message)
|
|
70
|
+
code = message.attribute('aCId')
|
|
71
|
+
alarm = get_alarm_state code
|
|
72
|
+
alarm.update_from_message message
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Things shared between sites and site proxies
|
|
2
|
+
|
|
3
|
+
module RSMP
|
|
4
|
+
module Components
|
|
5
|
+
attr_reader :components, :main
|
|
6
|
+
|
|
7
|
+
def initialize_components
|
|
8
|
+
@components = {}
|
|
9
|
+
@main = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def aggregated_status_changed(component, options = {}); end
|
|
13
|
+
|
|
14
|
+
def setup_components(settings)
|
|
15
|
+
return unless settings
|
|
16
|
+
|
|
17
|
+
check_main_component settings
|
|
18
|
+
settings.each_pair do |type, components_by_type|
|
|
19
|
+
next unless components_by_type
|
|
20
|
+
|
|
21
|
+
components_by_type.each_pair do |id, component_settings|
|
|
22
|
+
component_settings ||= {}
|
|
23
|
+
@components[id] = build_component(id: id, type: type, settings: component_settings)
|
|
24
|
+
@main = @components[id] if type == 'main'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def check_main_component(settings)
|
|
30
|
+
raise ConfigurationError, 'main component must be defined' unless settings['main'] && settings['main'].size >= 1
|
|
31
|
+
return unless settings['main'].size > 1
|
|
32
|
+
|
|
33
|
+
raise ConfigurationError, "only one main component can be defined, found #{settings['main'].keys.join(', ')}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def add_component(component)
|
|
37
|
+
@components[component.c_id] = component
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def infer_component_type(component_id)
|
|
41
|
+
raise UnknownComponent, "Component #{component_id} mising and cannot infer type"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def find_component(component_id, build: true)
|
|
45
|
+
component = @components[component_id]
|
|
46
|
+
return component if component
|
|
47
|
+
|
|
48
|
+
return unless build
|
|
49
|
+
|
|
50
|
+
inferred_type = infer_component_type component_id
|
|
51
|
+
component = inferred_type.new node: self, id: component_id
|
|
52
|
+
@components[component_id] = component
|
|
53
|
+
class_name = component.class.name.split('::').last
|
|
54
|
+
class_name << ' component' unless %w[Component ComponentProxy].include?(class_name)
|
|
55
|
+
log "Added component #{component_id} with the inferred type #{class_name}", level: :debug
|
|
56
|
+
component
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def clear_alarm_timestamps
|
|
60
|
+
@components.each_value(&:clear_alarm_timestamps)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -8,179 +8,187 @@ module RSMP
|
|
|
8
8
|
module Convert
|
|
9
9
|
module Export
|
|
10
10
|
module JSONSchema
|
|
11
|
-
|
|
12
|
-
@@json_options = {
|
|
11
|
+
JSON_OPTIONS = {
|
|
13
12
|
array_nl: "\n",
|
|
14
13
|
object_nl: "\n",
|
|
15
14
|
indent: ' ',
|
|
16
15
|
space_before: ' ',
|
|
17
16
|
space: ' '
|
|
18
|
-
}
|
|
17
|
+
}.freeze
|
|
19
18
|
|
|
20
|
-
def self.output_json
|
|
21
|
-
JSON.generate(item
|
|
19
|
+
def self.output_json(item)
|
|
20
|
+
JSON.generate(item, JSON_OPTIONS)
|
|
22
21
|
end
|
|
23
22
|
|
|
24
|
-
def self.build_value
|
|
23
|
+
def self.build_value(item)
|
|
25
24
|
out = {}
|
|
26
|
-
|
|
27
|
-
if item['description']
|
|
28
|
-
out["description"] = item['description']
|
|
29
|
-
end
|
|
25
|
+
out['description'] = item['description'] if item['description']
|
|
30
26
|
|
|
31
27
|
if item['list']
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
out["$ref"] = "../../../core/3.1.1/definitions.json#/integer_list"
|
|
37
|
-
else
|
|
38
|
-
raise "Error: List of #{item['type']} is not supported: #{item.inspect}"
|
|
39
|
-
end
|
|
28
|
+
build_list_value(out, item)
|
|
29
|
+
else
|
|
30
|
+
build_single_value(out, item)
|
|
31
|
+
end
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
out['pattern'] = /(?-mix:^(#{value_list})(?:,(#{value_list}))*$)/
|
|
44
|
-
end
|
|
33
|
+
out
|
|
34
|
+
end
|
|
45
35
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
def self.build_list_value(out, item)
|
|
37
|
+
case item['type']
|
|
38
|
+
when 'boolean'
|
|
39
|
+
out['$ref'] = '../../../core/3.1.1/definitions.json#/boolean_list'
|
|
40
|
+
when 'integer', 'ordinal', 'unit', 'scale', 'long'
|
|
41
|
+
out['$ref'] = '../../../core/3.1.1/definitions.json#/integer_list'
|
|
49
42
|
else
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
out["type"] = "string"
|
|
53
|
-
when "boolean"
|
|
54
|
-
out["$ref"] = "../../../core/3.1.1/definitions.json#/boolean"
|
|
55
|
-
when "timestamp"
|
|
56
|
-
out["$ref"] = "../../../core/3.1.1/definitions.json#/timestamp"
|
|
57
|
-
when "integer", "ordinal", "unit", "scale", "long"
|
|
58
|
-
out["$ref"] = "../../../core/3.1.1/definitions.json#/integer"
|
|
59
|
-
else
|
|
60
|
-
out["type"] = "string"
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
if item["values"]
|
|
64
|
-
out["enum"] = item["values"].keys.sort
|
|
65
|
-
end
|
|
43
|
+
raise "Error: List of #{item['type']} is not supported: #{item.inspect}"
|
|
44
|
+
end
|
|
66
45
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
46
|
+
if item['values']
|
|
47
|
+
value_list = item['values'].keys.join('|')
|
|
48
|
+
out['pattern'] = /(?-mix:^(#{value_list})(?:,(#{value_list}))*$)/
|
|
70
49
|
end
|
|
71
50
|
|
|
51
|
+
puts "Warning: Pattern not support for lists: #{item.inspect}" if item['pattern']
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.build_single_value(out, item)
|
|
55
|
+
case item['type']
|
|
56
|
+
when 'boolean'
|
|
57
|
+
out['$ref'] = '../../../core/3.1.1/definitions.json#/boolean'
|
|
58
|
+
when 'timestamp'
|
|
59
|
+
out['$ref'] = '../../../core/3.1.1/definitions.json#/timestamp'
|
|
60
|
+
when 'integer', 'ordinal', 'unit', 'scale', 'long'
|
|
61
|
+
out['$ref'] = '../../../core/3.1.1/definitions.json#/integer'
|
|
62
|
+
else # includes 'string', 'base64' and any other types
|
|
63
|
+
out['type'] = 'string'
|
|
64
|
+
end
|
|
72
65
|
|
|
73
|
-
out
|
|
66
|
+
out['enum'] = item['values'].keys.sort if item['values']
|
|
67
|
+
out['pattern'] = item['pattern'] if item['pattern']
|
|
74
68
|
end
|
|
75
69
|
|
|
76
|
-
def self.build_item
|
|
77
|
-
json = {
|
|
70
|
+
def self.build_item(item, property_key: 'v')
|
|
71
|
+
json = { 'allOf' => [{ 'description' => item['description'] }] }
|
|
78
72
|
if item['arguments']
|
|
79
|
-
json[
|
|
80
|
-
item['arguments'].each_pair do |key,argument|
|
|
81
|
-
json[
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
json['allOf'].first['properties'] = { 'n' => { 'enum' => item['arguments'].keys.sort } }
|
|
74
|
+
item['arguments'].each_pair do |key, argument|
|
|
75
|
+
json['allOf'] << {
|
|
76
|
+
'if' => { 'required' => ['n'], 'properties' => { 'n' => { 'const' => key } } },
|
|
77
|
+
'then' => { 'properties' => { property_key => build_value(argument) } }
|
|
84
78
|
}
|
|
85
79
|
end
|
|
86
80
|
end
|
|
87
81
|
json
|
|
88
82
|
end
|
|
89
83
|
|
|
90
|
-
def self.output_alarms
|
|
84
|
+
def self.output_alarms(out, items)
|
|
91
85
|
list = items.keys.sort.map do |key|
|
|
92
86
|
{
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
'if' => { 'required' => ['aCId'], 'properties' => { 'aCId' => { 'const' => key } } },
|
|
88
|
+
'then' => { '$ref' => "#{key}.json" }
|
|
95
89
|
}
|
|
96
90
|
end
|
|
97
91
|
json = {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
'properties' => {
|
|
93
|
+
'aCId' => { 'enum' => items.keys.sort },
|
|
94
|
+
'rvs' => { 'items' => { 'allOf' => list } }
|
|
101
95
|
}
|
|
102
96
|
}
|
|
103
97
|
out['alarms/alarms.json'] = output_json json
|
|
104
|
-
items.each_pair { |key,item| output_alarm out, key, item }
|
|
98
|
+
items.each_pair { |key, item| output_alarm out, key, item }
|
|
105
99
|
end
|
|
106
100
|
|
|
107
|
-
def self.output_alarm
|
|
101
|
+
def self.output_alarm(out, key, item)
|
|
108
102
|
json = build_item item
|
|
109
103
|
out["alarms/#{key}.json"] = output_json json
|
|
110
104
|
end
|
|
111
105
|
|
|
112
|
-
def self.output_statuses
|
|
113
|
-
list = [
|
|
106
|
+
def self.output_statuses(out, items)
|
|
107
|
+
list = [{ 'properties' => { 'sCI' => { 'enum' => items.keys.sort } } }]
|
|
114
108
|
items.keys.sort.each do |key|
|
|
115
109
|
list << {
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
'if' => { 'required' => ['sCI'], 'properties' => { 'sCI' => { 'const' => key } } },
|
|
111
|
+
'then' => { '$ref' => "#{key}.json" }
|
|
118
112
|
}
|
|
119
113
|
end
|
|
120
|
-
json = {
|
|
114
|
+
json = { 'properties' => { 'sS' => { 'items' => { 'allOf' => list } } } }
|
|
121
115
|
out['statuses/statuses.json'] = output_json json
|
|
122
|
-
items.each_pair { |key,item| output_status out, key, item }
|
|
116
|
+
items.each_pair { |key, item| output_status out, key, item }
|
|
123
117
|
end
|
|
124
118
|
|
|
125
|
-
def self.output_status
|
|
126
|
-
json = build_item item, property_key:'s'
|
|
119
|
+
def self.output_status(out, key, item)
|
|
120
|
+
json = build_item item, property_key: 's'
|
|
127
121
|
out["statuses/#{key}.json"] = output_json json
|
|
128
122
|
end
|
|
129
123
|
|
|
130
|
-
def self.output_commands
|
|
131
|
-
list = [
|
|
124
|
+
def self.output_commands(out, items)
|
|
125
|
+
list = [{ 'properties' => { 'cCI' => { 'enum' => items.keys.sort } } }]
|
|
132
126
|
items.keys.sort.each do |key|
|
|
133
127
|
list << {
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
'if' => { 'required' => ['cCI'], 'properties' => { 'cCI' => { 'const' => key } } },
|
|
129
|
+
'then' => { '$ref' => "#{key}.json" }
|
|
136
130
|
}
|
|
137
131
|
end
|
|
138
|
-
json = {
|
|
132
|
+
json = { 'items' => { 'allOf' => list } }
|
|
139
133
|
out['commands/commands.json'] = output_json json
|
|
140
134
|
|
|
141
|
-
json = {
|
|
135
|
+
json = { 'properties' => { 'arg' => { '$ref' => 'commands.json' } } }
|
|
142
136
|
out['commands/command_requests.json'] = output_json json
|
|
143
137
|
|
|
144
|
-
json = {
|
|
138
|
+
json = { 'properties' => { 'rvs' => { '$ref' => 'commands.json' } } }
|
|
145
139
|
out['commands/command_responses.json'] = output_json json
|
|
146
140
|
|
|
147
|
-
items.each_pair { |key,item| output_command out, key, item }
|
|
141
|
+
items.each_pair { |key, item| output_command out, key, item }
|
|
148
142
|
end
|
|
149
143
|
|
|
150
|
-
def self.output_command
|
|
144
|
+
def self.output_command(out, key, item)
|
|
151
145
|
json = build_item item
|
|
152
|
-
json[
|
|
146
|
+
json['allOf'].first['properties']['cO'] = { 'const' => item['command'] }
|
|
153
147
|
out["commands/#{key}.json"] = output_json json
|
|
154
148
|
end
|
|
155
149
|
|
|
156
|
-
def self.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{
|
|
163
|
-
"if" => { "required" => ["type"], "properties" => { "type" => { "const" => "CommandRequest" }}},
|
|
164
|
-
"then" => { "$ref" => "commands/command_requests.json" }
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
"if" => { "required" => ["type"], "properties" => { "type" => { "const" => "CommandResponse" }}},
|
|
168
|
-
"then" => { "$ref" => "commands/command_responses.json" }
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
"if" => { "required" => ["type"], "properties" => { "type" => { "enum" => ["StatusRequest","StatusResponse","StatusSubscribe","StatusUnsubscribe","StatusUpdate"] }}},
|
|
172
|
-
"then" => { "$ref" => "statuses/statuses.json" }
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
"if" => { "required" => ["type"], "properties" => { "type" => { "const" => "Alarm" }}},
|
|
176
|
-
"then" => { "$ref" => "alarms/alarms.json" }
|
|
177
|
-
}
|
|
178
|
-
]
|
|
150
|
+
def self.build_root_schema(meta)
|
|
151
|
+
{
|
|
152
|
+
'name' => meta['name'],
|
|
153
|
+
'description' => meta['description'],
|
|
154
|
+
'version' => meta['version'],
|
|
155
|
+
'allOf' => build_root_refs
|
|
179
156
|
}
|
|
180
|
-
out["sxl.json"] = output_json json
|
|
181
157
|
end
|
|
182
158
|
|
|
183
|
-
def self.
|
|
159
|
+
def self.build_root_refs
|
|
160
|
+
[
|
|
161
|
+
{
|
|
162
|
+
'if' => { 'required' => ['type'], 'properties' => { 'type' => { 'const' => 'CommandRequest' } } },
|
|
163
|
+
'then' => { '$ref' => 'commands/command_requests.json' }
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
'if' => { 'required' => ['type'], 'properties' => { 'type' => { 'const' => 'CommandResponse' } } },
|
|
167
|
+
'then' => { '$ref' => 'commands/command_responses.json' }
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
'if' => { 'required' => ['type'],
|
|
171
|
+
'properties' => {
|
|
172
|
+
'type' => {
|
|
173
|
+
'enum' => %w[StatusRequest StatusResponse StatusSubscribe
|
|
174
|
+
StatusUnsubscribe StatusUpdate]
|
|
175
|
+
}
|
|
176
|
+
} },
|
|
177
|
+
'then' => { '$ref' => 'statuses/statuses.json' }
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
'if' => { 'required' => ['type'], 'properties' => { 'type' => { 'const' => 'Alarm' } } },
|
|
181
|
+
'then' => { '$ref' => 'alarms/alarms.json' }
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def self.output_root(out, meta)
|
|
187
|
+
json = build_root_schema(meta)
|
|
188
|
+
out['sxl.json'] = output_json json
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def self.generate(sxl)
|
|
184
192
|
out = {}
|
|
185
193
|
output_root out, sxl[:meta]
|
|
186
194
|
output_alarms out, sxl[:alarms]
|
|
@@ -189,18 +197,16 @@ module RSMP
|
|
|
189
197
|
out
|
|
190
198
|
end
|
|
191
199
|
|
|
192
|
-
def self.write
|
|
200
|
+
def self.write(sxl, folder)
|
|
193
201
|
out = generate sxl
|
|
194
|
-
out.each_pair do |relative_path,str|
|
|
202
|
+
out.each_pair do |relative_path, str|
|
|
195
203
|
path = File.join(folder, relative_path)
|
|
196
|
-
FileUtils.mkdir_p File.dirname(path)
|
|
197
|
-
file = File.open(path, 'w+')
|
|
204
|
+
FileUtils.mkdir_p File.dirname(path) # create folders if needed
|
|
205
|
+
file = File.open(path, 'w+') # w+ means truncate or create new file
|
|
198
206
|
file.puts str
|
|
199
207
|
end
|
|
200
208
|
end
|
|
201
|
-
|
|
202
209
|
end
|
|
203
|
-
|
|
204
210
|
end
|
|
205
211
|
end
|
|
206
|
-
end
|
|
212
|
+
end
|
|
@@ -8,34 +8,37 @@ module RSMP
|
|
|
8
8
|
module Convert
|
|
9
9
|
module Import
|
|
10
10
|
module YAML
|
|
11
|
-
|
|
12
|
-
def self.read path
|
|
11
|
+
def self.read(path)
|
|
13
12
|
convert ::YAML.load_file(path)
|
|
14
13
|
end
|
|
15
14
|
|
|
16
|
-
def self.parse
|
|
15
|
+
def self.parse(str)
|
|
17
16
|
convert ::YAML.load(str)
|
|
18
17
|
end
|
|
19
18
|
|
|
20
|
-
def self.convert
|
|
21
|
-
sxl =
|
|
22
|
-
meta: {},
|
|
23
|
-
alarms: {},
|
|
24
|
-
statuses: {},
|
|
25
|
-
commands: {}
|
|
26
|
-
}
|
|
27
|
-
|
|
19
|
+
def self.convert(yaml)
|
|
20
|
+
sxl = build_empty_sxl
|
|
28
21
|
sxl[:meta] = yaml['meta']
|
|
22
|
+
merge_objects(sxl, yaml['objects'])
|
|
23
|
+
sxl
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.build_empty_sxl
|
|
27
|
+
{ meta: {}, alarms: {}, statuses: {}, commands: {} }
|
|
28
|
+
end
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
object["commands"].each { |id,item| sxl[:commands][id] = item } if object["commands"]
|
|
30
|
+
def self.merge_objects(sxl, objects)
|
|
31
|
+
objects.each_pair do |_type, object|
|
|
32
|
+
merge_object_items(sxl, object)
|
|
34
33
|
end
|
|
35
|
-
sxl
|
|
36
34
|
end
|
|
37
|
-
end
|
|
38
35
|
|
|
36
|
+
def self.merge_object_items(sxl, object)
|
|
37
|
+
object['alarms']&.each { |id, item| sxl[:alarms][id] = item }
|
|
38
|
+
object['statuses']&.each { |id, item| sxl[:statuses][id] = item }
|
|
39
|
+
object['commands']&.each { |id, item| sxl[:commands][id] = item }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
39
42
|
end
|
|
40
43
|
end
|
|
41
|
-
end
|
|
44
|
+
end
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
require 'time'
|
|
7
7
|
|
|
8
8
|
module RSMP
|
|
9
|
-
|
|
10
9
|
class Clock
|
|
11
10
|
attr_reader :adjustment
|
|
12
11
|
|
|
@@ -14,7 +13,7 @@ module RSMP
|
|
|
14
13
|
@adjustment = 0
|
|
15
14
|
end
|
|
16
15
|
|
|
17
|
-
def set
|
|
16
|
+
def set(target)
|
|
18
17
|
@adjustment = target - Time.now
|
|
19
18
|
end
|
|
20
19
|
|
|
@@ -34,12 +33,12 @@ module RSMP
|
|
|
34
33
|
Time.now.utc
|
|
35
34
|
end
|
|
36
35
|
|
|
37
|
-
def self.to_s
|
|
38
|
-
(time || now).strftime(
|
|
36
|
+
def self.to_s(time = nil)
|
|
37
|
+
(time || now).strftime('%FT%T.%3NZ')
|
|
39
38
|
end
|
|
40
39
|
|
|
41
|
-
def self.parse
|
|
40
|
+
def self.parse(str)
|
|
42
41
|
Time.parse(str)
|
|
43
42
|
end
|
|
44
43
|
end
|
|
45
|
-
end
|
|
44
|
+
end
|