rsmp 0.45.0 → 0.45.2

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubocop.yaml +1 -1
  3. data/.github/workflows/sus.yaml +1 -1
  4. data/.gitignore +0 -1
  5. data/AGENTS.md +57 -0
  6. data/CHANGELOG.md +12 -1
  7. data/Gemfile +0 -2
  8. data/Gemfile.lock +2 -80
  9. data/README.md +14 -35
  10. data/documentation/configuration.md +22 -21
  11. data/documentation/message_distribution.md +1 -2
  12. data/documentation/tasks.md +1 -2
  13. data/exe/rsmp +1 -2
  14. data/lib/rsmp/cli.rb +33 -3
  15. data/lib/rsmp/convert/export/json_schema/outputs.rb +18 -0
  16. data/lib/rsmp/convert/export/json_schema/values.rb +1 -1
  17. data/lib/rsmp/convert/export/json_schema.rb +14 -6
  18. data/lib/rsmp/schema/core_sxl_resolution.rb +69 -0
  19. data/lib/rsmp/schema/validation.rb +57 -0
  20. data/lib/rsmp/schema.rb +40 -67
  21. data/lib/rsmp/tlc/proxy/control.rb +5 -5
  22. data/lib/rsmp/tlc/proxy/detectors.rb +2 -2
  23. data/lib/rsmp/tlc/proxy/io.rb +4 -4
  24. data/lib/rsmp/tlc/proxy/plans.rb +8 -8
  25. data/lib/rsmp/tlc/proxy/system.rb +2 -2
  26. data/lib/rsmp/version.rb +1 -1
  27. data/schemas/tlc/1.0.10/rsmp.json +2 -1
  28. data/schemas/tlc/1.0.10/sxl.yaml +1 -0
  29. data/schemas/tlc/1.0.10/sxl_index.json +356 -0
  30. data/schemas/tlc/1.0.13/rsmp.json +2 -1
  31. data/schemas/tlc/1.0.13/sxl.yaml +1 -0
  32. data/schemas/tlc/1.0.13/sxl_index.json +436 -0
  33. data/schemas/tlc/1.0.14/rsmp.json +2 -1
  34. data/schemas/tlc/1.0.14/sxl.yaml +1 -0
  35. data/schemas/tlc/1.0.14/sxl_index.json +468 -0
  36. data/schemas/tlc/1.0.15/rsmp.json +2 -1
  37. data/schemas/tlc/1.0.15/sxl.yaml +1 -0
  38. data/schemas/tlc/1.0.15/sxl_index.json +508 -0
  39. data/schemas/tlc/1.0.7/rsmp.json +2 -1
  40. data/schemas/tlc/1.0.7/sxl.yaml +1 -0
  41. data/schemas/tlc/1.0.7/sxl_index.json +356 -0
  42. data/schemas/tlc/1.0.8/rsmp.json +2 -1
  43. data/schemas/tlc/1.0.8/sxl.yaml +1 -0
  44. data/schemas/tlc/1.0.8/sxl_index.json +356 -0
  45. data/schemas/tlc/1.0.9/rsmp.json +2 -1
  46. data/schemas/tlc/1.0.9/sxl.yaml +1 -0
  47. data/schemas/tlc/1.0.9/sxl_index.json +356 -0
  48. data/schemas/tlc/1.1.0/rsmp.json +2 -1
  49. data/schemas/tlc/1.1.0/sxl.yaml +1 -0
  50. data/schemas/tlc/1.1.0/sxl_index.json +572 -0
  51. data/schemas/tlc/1.2.0/rsmp.json +2 -1
  52. data/schemas/tlc/1.2.0/sxl.yaml +1 -0
  53. data/schemas/tlc/1.2.0/sxl_index.json +571 -0
  54. data/schemas/tlc/1.2.1/rsmp.json +2 -1
  55. data/schemas/tlc/1.2.1/sxl.yaml +1 -0
  56. data/schemas/tlc/1.2.1/sxl_index.json +571 -0
  57. data/schemas/tlc/1.3.0/defs/definitions.json +86 -25
  58. data/schemas/tlc/1.3.0/rsmp.json +2 -1
  59. data/schemas/tlc/1.3.0/statuses/S0024.json +2 -1
  60. data/schemas/tlc/1.3.0/sxl.yaml +1 -0
  61. data/schemas/tlc/1.3.0/sxl_index.json +578 -0
  62. metadata +15 -4
  63. data/.github/copilot-instructions.md +0 -33
  64. data/.rspec +0 -1
  65. data/cucumber.yml +0 -1
@@ -0,0 +1,69 @@
1
+ module RSMP
2
+ # Provides JSON Schema validation for RSMP messages across core and SXL versions.
3
+ module Schema
4
+ def self.clear_core_sxl_schemas(type = nil, version = nil)
5
+ @core_sxl_schemas ||= {}
6
+ return @core_sxl_schemas.clear unless type
7
+
8
+ type = type.to_sym
9
+ @core_sxl_schemas.delete_if do |(cached_type, cached_version, _core_version), _schema|
10
+ cached_type == type && (!version || cached_version == version.to_s)
11
+ end
12
+ end
13
+
14
+ def self.schema_core_version(schemas)
15
+ schemas[:core] || schemas['core']
16
+ end
17
+
18
+ def self.validate_resolved_sxl(message, resolved, schemas, options)
19
+ type, version = resolved
20
+ schema = find_core_sxl_schema! type, version, schema_core_version(schemas), options
21
+ validate_using_schema(message, schema)
22
+ end
23
+
24
+ def self.find_core_sxl_schema!(type, version, core_version, options = {})
25
+ raise ArgumentError, 'core version missing' unless core_version
26
+
27
+ version = sanitize_version(version.to_s) if options[:lenient]
28
+ core_version = sanitize_version(core_version.to_s) if options[:lenient]
29
+ find_schema! type, version
30
+ find_schema! :core, core_version
31
+
32
+ key = [type.to_sym, version.to_s, core_version.to_s]
33
+ @core_sxl_schemas ||= {}
34
+ @core_sxl_schemas[key] ||= build_core_sxl_schema(type, version, core_version)
35
+ end
36
+
37
+ def self.build_core_sxl_schema(type, version, core_version)
38
+ schema_path = @schema_paths&.dig(type.to_sym, version.to_s)
39
+ raise UnknownSchemaVersionError, "Unknown schema version #{type} #{version}" unless schema_path
40
+
41
+ file_path = File.join(schema_path, 'rsmp.json')
42
+ JSONSchemer.schema(
43
+ Pathname.new(file_path),
44
+ ref_resolver: core_sxl_ref_resolver(core_version)
45
+ )
46
+ end
47
+
48
+ def self.core_sxl_ref_resolver(core_version)
49
+ proc do |uri|
50
+ if sxl_definitions_ref?(uri)
51
+ JSON.parse(File.read(core_definitions_path(core_version), encoding: 'UTF-8'))
52
+ else
53
+ JSONSchemer::FILE_URI_REF_RESOLVER.call(uri)
54
+ end
55
+ end
56
+ end
57
+
58
+ def self.sxl_definitions_ref?(uri)
59
+ uri.scheme == 'file' && uri.path.end_with?('/defs/definitions.json')
60
+ end
61
+
62
+ def self.core_definitions_path(core_version)
63
+ path = File.join(schema_root_path, 'core', core_version.to_s, 'definitions.json')
64
+ return path if File.exist?(path)
65
+
66
+ raise UnknownSchemaVersionError, "Missing core definitions for RSMP #{core_version}"
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,57 @@
1
+ module RSMP
2
+ # Provides JSON Schema validation for RSMP messages across core and SXL versions.
3
+ module Schema
4
+ def self.core_message_type?(message)
5
+ type = message['type']
6
+ %w[
7
+ MessageAck
8
+ MessageNotAck
9
+ Version
10
+ ComponentList
11
+ AggregatedStatus
12
+ AggregatedStatusRequest
13
+ Watchdog
14
+ ].include?(type)
15
+ end
16
+
17
+ def self.validate_core(message, schemas, options)
18
+ core_version = schemas[:core] || schemas['core']
19
+ raise ArgumentError, 'schemas must include core' unless core_version
20
+
21
+ schema = find_schema! :core, core_version, options
22
+ validate_using_schema(message, schema)
23
+ end
24
+
25
+ def self.validate_sxls(message, schemas, options)
26
+ sxl_schemas = schemas.reject { |type, _version| type.to_sym == :core }
27
+ return [] if sxl_schemas.empty? || core_message_type?(message)
28
+
29
+ resolved = resolve_sxl(message, schemas: schemas, **options)
30
+ return validate_resolved_sxl(message, resolved, schemas, options) if resolved
31
+
32
+ all_errors = []
33
+ sxl_schemas.each do |type, version|
34
+ schema = find_core_sxl_schema! type, version, schema_core_version(schemas), options
35
+ errors = validate_using_schema(message, schema)
36
+ return [] if errors.empty?
37
+
38
+ all_errors.concat errors
39
+ end
40
+ all_errors
41
+ end
42
+
43
+ # Core must pass. SXL-defined messages pass if at least one SXL schema passes.
44
+ def self.validate(message, schemas, options = {})
45
+ raise ArgumentError, 'message missing' unless message
46
+ raise ArgumentError, 'schemas missing' unless schemas
47
+ raise ArgumentError, 'schemas must be a Hash' unless schemas.is_a?(Hash)
48
+ raise ArgumentError, 'schemas cannot be empty' unless schemas.any?
49
+
50
+ errors = validate_core(message, schemas, options)
51
+ errors.concat validate_sxls(message, schemas, options) if errors.empty?
52
+ return nil if errors.empty?
53
+
54
+ errors
55
+ end
56
+ end
57
+ end
data/lib/rsmp/schema.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'json_schemer'
2
2
  require 'json'
3
- require 'yaml'
4
3
 
5
4
  # RSMP (Road Side Message Protocol) schema validation library.
6
5
  module RSMP
@@ -11,13 +10,19 @@ module RSMP
11
10
  def self.setup
12
11
  @schemas = {}
13
12
  @schema_paths = {}
14
- schemas_path = File.expand_path(File.join(__dir__, '..', '..', 'schemas'))
13
+ @sxl_indexes = {}
14
+ @core_sxl_schemas = {}
15
+ schemas_path = schema_root_path
15
16
  Dir.glob("#{schemas_path}/*").select { |f| File.directory? f }.each do |type_path|
16
17
  type = File.basename(type_path).to_sym
17
18
  load_schema_type type, type_path
18
19
  end
19
20
  end
20
21
 
22
+ def self.schema_root_path
23
+ File.expand_path(File.join(__dir__, '..', '..', 'schemas'))
24
+ end
25
+
21
26
  # load an schema from a folder. schemas are organized by version, and contain
22
27
  # json schema files, with the entry point being rsmp.jspon, eg:
23
28
  # tlc
@@ -35,6 +40,8 @@ module RSMP
35
40
  @schemas[type] = {}
36
41
  @schema_paths ||= {}
37
42
  @schema_paths[type] = {}
43
+ clear_sxl_index(type)
44
+ clear_core_sxl_schemas(type)
38
45
  schema_version_paths(type_path).each { |schema_path| load_schema_version(type, schema_path) }
39
46
  end
40
47
 
@@ -53,6 +60,8 @@ module RSMP
53
60
 
54
61
  @schemas[type][version] = JSONSchemer.schema(Pathname.new(file_path))
55
62
  @schema_paths[type][version] = schema_path
63
+ clear_sxl_index(type, version)
64
+ clear_core_sxl_schemas(type, version)
56
65
  end
57
66
 
58
67
  # remove a schema type
@@ -60,6 +69,8 @@ module RSMP
60
69
  type = type.to_sym
61
70
  schemas.delete type
62
71
  @schema_paths&.delete type
72
+ clear_sxl_index(type)
73
+ clear_core_sxl_schemas(type)
63
74
  end
64
75
 
65
76
  # get schemas types
@@ -195,14 +206,7 @@ module RSMP
195
206
  version = sanitize_version version if options[:lenient]
196
207
  find_schema! type, version
197
208
 
198
- path = @schema_paths&.dig(type.to_sym, version)
199
- return {} unless path
200
-
201
- yaml_path = File.join(path, 'sxl.yaml')
202
- return YAML.load_file(yaml_path).fetch('meta', {}) if File.exist?(yaml_path)
203
-
204
- json_path = File.join(path, 'rsmp.json')
205
- File.exist?(json_path) ? JSON.parse(File.read(json_path)) : {}
209
+ sxl_index(type, version).fetch('meta', {})
206
210
  end
207
211
 
208
212
  def self.sxl_prefix(type, version, options = {})
@@ -211,82 +215,51 @@ module RSMP
211
215
 
212
216
  # return a catalogue of statuses for a particular schema type and version
213
217
  # returns a hash of { status_code_id_sym => [arg_name_sym, ...] }
214
- # raises an error if the schema type/version is not found, or has no sxl.yaml
218
+ # raises an error if the schema type/version is not found, or has no status catalogue
215
219
  def self.status_catalogue(type, version)
216
220
  sxl_catalogue(type, version, :statuses).transform_keys(&:to_sym).transform_values do |status|
217
- (status['arguments'] || {}).keys.map(&:to_sym)
221
+ status.fetch('arguments', []).map(&:to_sym)
218
222
  end
219
223
  end
220
224
 
221
225
  def self.sxl_catalogue(type, version, kind)
222
226
  find_schema! type, version
223
- schema_path = @schema_paths&.dig(type.to_sym, version)
224
- yaml_path = File.join(schema_path, 'sxl.yaml') if schema_path
225
- raise "No sxl.yaml for #{type} #{version}" unless yaml_path && File.exist?(yaml_path)
226
-
227
- sxl = RSMP::Convert::Import::YAML.read(yaml_path)
228
- sxl.fetch(kind)
229
- end
230
-
231
- def self.core_message_type?(message)
232
- type = message['type']
233
- %w[
234
- MessageAck
235
- MessageNotAck
236
- Version
237
- ComponentList
238
- AggregatedStatus
239
- AggregatedStatusRequest
240
- Watchdog
241
- ].include?(type)
242
- end
227
+ catalogue = sxl_index(type, version)[kind.to_s]
228
+ raise "No #{kind} catalogue for #{type} #{version}" unless catalogue
243
229
 
244
- def self.validate_core(message, schemas, options)
245
- core_version = schemas[:core] || schemas['core']
246
- raise ArgumentError, 'schemas must include core' unless core_version
247
-
248
- schema = find_schema! :core, core_version, options
249
- validate_using_schema(message, schema)
230
+ catalogue
250
231
  end
251
232
 
252
- def self.validate_sxls(message, schemas, options)
253
- sxl_schemas = schemas.reject { |type, _version| type.to_sym == :core }
254
- return [] if sxl_schemas.empty? || core_message_type?(message)
233
+ def self.clear_sxl_index(type = nil, version = nil)
234
+ @sxl_indexes ||= {}
235
+ return @sxl_indexes.clear unless type
255
236
 
256
- resolved = resolve_sxl(message, schemas: schemas, **options)
257
- if resolved
258
- type, version = resolved
259
- schema = find_schema! type, version, options
260
- return validate_using_schema(message, schema)
237
+ type = type.to_sym
238
+ if version
239
+ @sxl_indexes.delete([type, version.to_s])
240
+ else
241
+ @sxl_indexes.delete_if { |key, _value| key.first == type }
261
242
  end
243
+ end
262
244
 
263
- all_errors = []
264
- sxl_schemas.each do |type, version|
265
- schema = find_schema! type, version, options
266
- errors = validate_using_schema(message, schema)
267
- return [] if errors.empty?
268
-
269
- all_errors.concat errors
270
- end
271
- all_errors
245
+ def self.sxl_index(type, version)
246
+ key = [type.to_sym, version.to_s]
247
+ @sxl_indexes ||= {}
248
+ @sxl_indexes[key] ||= load_sxl_index(type, version)
272
249
  end
273
250
 
274
- # validate using core and optional SXL schemas.
275
- # Core must pass. SXL-defined messages pass if at least one SXL schema passes.
276
- # returns nil if validation succeeds, otherwise returns an array of errors.
277
- def self.validate(message, schemas, options = {})
278
- raise ArgumentError, 'message missing' unless message
279
- raise ArgumentError, 'schemas missing' unless schemas
280
- raise ArgumentError, 'schemas must be a Hash' unless schemas.is_a?(Hash)
281
- raise ArgumentError, 'schemas cannot be empty' unless schemas.any?
251
+ def self.load_sxl_index(type, version)
252
+ schema_path = @schema_paths&.dig(type.to_sym, version.to_s)
253
+ raise UnknownSchemaVersionError, "Unknown schema version #{type} #{version}" unless schema_path
282
254
 
283
- errors = validate_core(message, schemas, options)
284
- errors.concat validate_sxls(message, schemas, options) if errors.empty?
285
- return nil if errors.empty?
255
+ index_path = File.join(schema_path, 'sxl_index.json')
256
+ raise Error, "Missing SXL index #{index_path}" unless File.exist?(index_path)
286
257
 
287
- errors
258
+ JSON.parse(File.read(index_path, encoding: 'UTF-8'))
288
259
  end
289
260
  end
290
261
  end
291
262
 
263
+ require_relative 'schema/core_sxl_resolution'
292
264
  require_relative 'schema/message_resolution'
265
+ require_relative 'schema/validation'
@@ -4,7 +4,7 @@ module RSMP
4
4
  # Command methods for operational control of a remote TLC.
5
5
  # Covers functional position, emergency routes, I/O modes, signal group orders, and system settings.
6
6
  module Control
7
- # M0001 Set functional position (NormalControl, YellowFlash, Dark).
7
+ # M0001 - Set functional position (NormalControl, YellowFlash, Dark).
8
8
  def set_functional_position(status, within:, timeout_minutes: 0)
9
9
  validate_ready 'set functional position'
10
10
  raise 'TLC main component not found' unless main
@@ -17,7 +17,7 @@ module RSMP
17
17
  { collector: collector }
18
18
  end
19
19
 
20
- # M0005 Set or clear an emergency route.
20
+ # M0005 - Set or clear an emergency route.
21
21
  def set_emergency_route(route:, active:, within:)
22
22
  validate_ready 'set emergency route'
23
23
  raise 'TLC main component not found' unless main
@@ -45,7 +45,7 @@ module RSMP
45
45
  send_command_and_collect(command_list, within: within).ok!
46
46
  end
47
47
 
48
- # M0007 Enable or disable fixed-time control.
48
+ # M0007 - Enable or disable fixed-time control.
49
49
  def set_fixed_time(status, within:)
50
50
  validate_ready 'set fixed time'
51
51
  raise 'TLC main component not found' unless main
@@ -70,7 +70,7 @@ module RSMP
70
70
  wait_for_status "fixed time #{status}", confirm_status, timeout: within
71
71
  end
72
72
 
73
- # M0003 Set traffic situation (activate a specific situation number).
73
+ # M0003 - Set traffic situation (activate a specific situation number).
74
74
  def set_traffic_situation(situation, within:)
75
75
  validate_ready 'set traffic situation'
76
76
  raise 'TLC main component not found' unless main
@@ -99,7 +99,7 @@ module RSMP
99
99
  wait_for_status "traffic situation #{situation}", confirm_status, timeout: within
100
100
  end
101
101
 
102
- # M0003 Clear the active traffic situation.
102
+ # M0003 - Clear the active traffic situation.
103
103
  def unset_traffic_situation(within:)
104
104
  validate_ready 'unset traffic situation'
105
105
  raise 'TLC main component not found' unless main
@@ -4,7 +4,7 @@ module RSMP
4
4
  # Command methods for operational control of a remote TLC.
5
5
  # Covers functional position, emergency routes, I/O modes, signal group orders, and system settings.
6
6
  module Detectors
7
- # M0008 Force detector logic to a given mode and status.
7
+ # M0008 - Force detector logic to a given mode and status.
8
8
  # component_id must refer to the detector logic component, not main.
9
9
  def force_detector_logic(component_id, status:, mode:, within:)
10
10
  validate_ready 'force detector logic'
@@ -30,7 +30,7 @@ module RSMP
30
30
  send_command_and_collect(command_list, component: component_id, within: within).ok!
31
31
  end
32
32
 
33
- # M0021 Set the trigger level for traffic counting.
33
+ # M0021 - Set the trigger level for traffic counting.
34
34
  def set_trigger_level(status, within:)
35
35
  validate_ready 'set trigger level'
36
36
  raise 'TLC main component not found' unless main
@@ -4,7 +4,7 @@ module RSMP
4
4
  # Command methods for I/O control of a remote TLC.
5
5
  # Covers detector logic, input/output forcing and setting.
6
6
  module IO
7
- # M0006 Set a single input to a given status.
7
+ # M0006 - Set a single input to a given status.
8
8
  def set_input(input:, status:, within:)
9
9
  validate_ready 'set input'
10
10
  raise 'TLC main component not found' unless main
@@ -30,7 +30,7 @@ module RSMP
30
30
  send_command_and_collect(command_list, within: within).ok!
31
31
  end
32
32
 
33
- # M0013 Set all inputs via a bit-pattern string.
33
+ # M0013 - Set all inputs via a bit-pattern string.
34
34
  def set_inputs(status, within:)
35
35
  validate_ready 'set inputs'
36
36
  raise 'TLC main component not found' unless main
@@ -51,7 +51,7 @@ module RSMP
51
51
  send_command_and_collect(command_list, within: within).ok!
52
52
  end
53
53
 
54
- # M0019 Force an input to a given value.
54
+ # M0019 - Force an input to a given value.
55
55
  def force_input(input:, status:, value:, within:)
56
56
  validate_ready 'force input'
57
57
  raise 'TLC main component not found' unless main
@@ -62,7 +62,7 @@ module RSMP
62
62
  wait_for_status "force input #{input}", confirm_status, timeout: within
63
63
  end
64
64
 
65
- # M0020 Force an output to a given value.
65
+ # M0020 - Force an output to a given value.
66
66
  def force_output(output:, status:, value:, within:)
67
67
  validate_ready 'force output'
68
68
  raise 'TLC main component not found' unless main
@@ -4,7 +4,7 @@ module RSMP
4
4
  # Command methods for signal plans.
5
5
  # Covers time plans, week/day tables, bands, offsets, and cycle times.
6
6
  module Plans
7
- # M0014 Set dynamic bands for a signal plan.
7
+ # M0014 - Set dynamic bands for a signal plan.
8
8
  def set_dynamic_bands(plan:, status:, within:)
9
9
  validate_ready 'set dynamic bands'
10
10
  raise 'TLC main component not found' unless main
@@ -30,7 +30,7 @@ module RSMP
30
30
  send_command_and_collect(command_list, within: within).ok!
31
31
  end
32
32
 
33
- # M0023 Set timeout for dynamic bands.
33
+ # M0023 - Set timeout for dynamic bands.
34
34
  def set_dynamic_bands_timeout(status, within:)
35
35
  validate_ready 'set dynamic bands timeout'
36
36
  raise 'TLC main component not found' unless main
@@ -51,7 +51,7 @@ module RSMP
51
51
  send_command_and_collect(command_list, within: within).ok!
52
52
  end
53
53
 
54
- # M0015 Set offset for a signal plan.
54
+ # M0015 - Set offset for a signal plan.
55
55
  def set_offset(plan:, offset:, within:)
56
56
  validate_ready 'set offset'
57
57
  raise 'TLC main component not found' unless main
@@ -105,7 +105,7 @@ module RSMP
105
105
  wait_for_status("timeplan #{plan_nr}", confirm_status, timeout: within)
106
106
  end
107
107
 
108
- # M0016 Set week table (mapping week days to traffic situations).
108
+ # M0016 - Set week table (mapping week days to traffic situations).
109
109
  def set_week_table(status, within:)
110
110
  validate_ready 'set week table'
111
111
  raise 'TLC main component not found' unless main
@@ -126,7 +126,7 @@ module RSMP
126
126
  send_command_and_collect(command_list, within:).ok!
127
127
  end
128
128
 
129
- # M0017 Set day table (mapping time periods to signal plans).
129
+ # M0017 - Set day table (mapping time periods to signal plans).
130
130
  def set_day_table(status, within:)
131
131
  validate_ready 'set day table'
132
132
  raise 'TLC main component not found' unless main
@@ -147,7 +147,7 @@ module RSMP
147
147
  send_command_and_collect(command_list, within:).ok!
148
148
  end
149
149
 
150
- # M0018 Set cycle time for a signal plan.
150
+ # M0018 - Set cycle time for a signal plan.
151
151
  def set_cycle_time(plan:, cycle_time:, within:)
152
152
  validate_ready 'set cycle time'
153
153
  raise 'TLC main component not found' unless main
@@ -173,7 +173,7 @@ module RSMP
173
173
  send_command_and_collect(command_list, within:).ok!
174
174
  end
175
175
 
176
- # M0010 Order signal start for a signal group component.
176
+ # M0010 - Order signal start for a signal group component.
177
177
  def order_signal_start(component_id, within:)
178
178
  validate_ready 'order signal start'
179
179
 
@@ -193,7 +193,7 @@ module RSMP
193
193
  send_command_and_collect(command_list, component: component_id, within:).ok!
194
194
  end
195
195
 
196
- # M0011 Order signal stop for a signal group component.
196
+ # M0011 - Order signal stop for a signal group component.
197
197
  def order_signal_stop(component_id, within:)
198
198
  validate_ready 'order signal stop'
199
199
 
@@ -4,7 +4,7 @@ module RSMP
4
4
  # Command methods for operational control of a remote TLC.
5
5
  # Covers functional position, emergency routes, I/O modes, signal group orders, and system settings.
6
6
  module System
7
- # M0103 Change security code for a given level.
7
+ # M0103 - Change security code for a given level.
8
8
  # Does not use security_code_for since the codes are passed explicitly.
9
9
  def set_security_code(level:, old_code:, new_code:, within:)
10
10
  validate_ready 'set security code'
@@ -29,7 +29,7 @@ module RSMP
29
29
  send_command_and_collect(command_list, within: within).ok!
30
30
  end
31
31
 
32
- # M0104 Set the clock on the remote TLC. clock must respond to year/month/day/hour/min/sec.
32
+ # M0104 - Set the clock on the remote TLC. clock must respond to year/month/day/hour/min/sec.
33
33
  def set_clock(clock, within:)
34
34
  validate_ready 'set clock'
35
35
  raise 'TLC main component not found' unless main
data/lib/rsmp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RSMP
2
- VERSION = '0.45.0'.freeze
2
+ VERSION = '0.45.2'.freeze
3
3
  end
@@ -70,5 +70,6 @@
70
70
  "$ref" : "alarms/alarms.json"
71
71
  }
72
72
  }
73
- ]
73
+ ],
74
+ "minimum_core_version" : "3.1.2"
74
75
  }
@@ -3,6 +3,7 @@ meta:
3
3
  name: tlc
4
4
  description: Traffic Light Controllers
5
5
  version: 1.0.10
6
+ minimum_core_version: 3.1.2
6
7
  objects:
7
8
  Traffic Light Controller:
8
9
  description: