escalator 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c264fb022bd1412ffaed30646286e0a482d4f14c
4
- data.tar.gz: e4db446ef5fe43d1019276831145a95b42dc23a1
3
+ metadata.gz: 97963603ab6ca43c800f1457985665cde1595d64
4
+ data.tar.gz: 2655e9900ca290185648b1b5d5436a8eb2703c25
5
5
  SHA512:
6
- metadata.gz: 71972f246ec5255677e2e8efdc03a3f8c29fcfeeb65054cddca735ba4f726bd69f3a746208458b8b2728ab0e9d66b8671096a85b29dc3aacc68fe8f86033e986
7
- data.tar.gz: 9b80c9f45104c524e82464eba6daaf05e1ecd3504e458bc1a858691d7a67fbffdb729bc8e0779e04dcba164ec567de79a33ef18b79cfcae3680176d12c53373f
6
+ metadata.gz: a023765c76d3fa85ccb6c4c399e3cf57b040d0c94e5bceb0c75890d0fb60b0531ba4d1b95b3732a739b3c098a18d30e0867e8bd42e41a609073a357b8a6fa4ab
7
+ data.tar.gz: 4f772215bb2dca5a8b63e8a18f030fd3e6347ef55f355b8923baea04a049a442838788cabe2bac686b23b86e3c2cc25516237159ca9c20e9f547e5bc9ae3e949
data/Gemfile CHANGED
@@ -4,3 +4,4 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem "test-unit"
7
+ gem "activesupport"
data/Gemfile.lock CHANGED
@@ -1,22 +1,34 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- escalator (0.1.1)
4
+ escalator (0.1.2)
5
5
  thor
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
+ activesupport (5.0.0.1)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (~> 0.7)
13
+ minitest (~> 5.1)
14
+ tzinfo (~> 1.1)
15
+ concurrent-ruby (1.0.2)
16
+ i18n (0.7.0)
17
+ minitest (5.9.1)
10
18
  power_assert (0.2.7)
11
19
  rake (10.5.0)
12
20
  test-unit (3.1.7)
13
21
  power_assert
14
22
  thor (0.19.1)
23
+ thread_safe (0.3.5)
24
+ tzinfo (1.2.2)
25
+ thread_safe (~> 0.1)
15
26
 
16
27
  PLATFORMS
17
28
  ruby
18
29
 
19
30
  DEPENDENCIES
31
+ activesupport
20
32
  bundler (~> 1.11)
21
33
  escalator!
22
34
  rake (~> 10.0)
data/README.md CHANGED
@@ -2,9 +2,99 @@
2
2
 
3
3
  The escalator is a simple abstract ladder for PLC (Programmable Logic Controller).
4
4
 
5
- My aim is to design runnable abstraction ladder which is running on any PLC with same ladder source or binary and prepare full stack tools.
5
+ We aim to design runnable abstraction ladder which is running on any PLC with same ladder source or binary and prepare full stack tools.
6
+
7
+ # Getting started
8
+
9
+ It's required the Ruby environment.
10
+ To prepare Ruby environment, please find web sites.
11
+
12
+ Install Escalator at the command prompt.
13
+
14
+ ```
15
+ $ gem install escalator
16
+ ```
17
+
18
+ # Create an Escalator project
19
+
20
+ At the command prompt, create a new Escalator project.
21
+
22
+ ```
23
+ $ escalator create my_project
24
+ $ cd my_project
25
+ ```
26
+
27
+ Created files are consisted like below the tree.
28
+
29
+ ```
30
+ .
31
+ ├── Rakefile
32
+ ├── asm
33
+ │ └── main.esc
34
+ ├── config
35
+ │ └── plc.yml
36
+ └── plc
37
+ └── mitsubishi
38
+ └── iq-r
39
+ └── r08
40
+ ├── LICENSE
41
+ └── r08.gx3
42
+ ```
43
+
44
+ # Connection configuration
45
+
46
+ ## PLC configuration
47
+
48
+ There is a plc project under the plc directory.
49
+ Launch the one of the plc project which you want to use.
50
+ (Currently we support MITSUBISHI iQ-R R08CUP only.)
51
+
52
+ Configure ethernet connection by the tool which is provided by plc maker.
53
+ Then upload settings and plc program to the plc.
54
+
55
+ ## Escalator configuration
56
+
57
+ There is a configuration file at config/plc.yml.
58
+ Though currently we support MITSUBISHI iQ-R R08CUP only, you only change host to an ip address of your plc.
59
+
60
+ ```
61
+ :plc:
62
+ :cpu: iq-r
63
+ :protocol: mc_protocol
64
+ :host: 192.168.0.1
65
+ :port: 5007
66
+ :program_area: d10000
67
+ :interaction_area: d9998
68
+ ```
69
+ ## Escalator programming
70
+
71
+ Escalator program file is located under the asm directory.
72
+ By default, use asm/main.esc.
73
+ Edit it and programming.
74
+
75
+ Refer [Wiki](https://github.com/ito-soft-design/escalator/wiki/mnemonic) to check mnemonic.
76
+
77
+ ```
78
+ LD X0
79
+ OUT Y0
80
+ LD X1
81
+ OR M0
82
+ ANI X2
83
+ OUT M0
84
+ END
85
+ ```
86
+
87
+ # Transfer the Escalator program
88
+
89
+ At the command prompt, use rake command to upload escalator program to the plc.
90
+ The Escalator program is running immediate after uploaded.
91
+
92
+ ```
93
+ $ rake plc
94
+ ```
95
+
96
+
6
97
 
7
98
  ## License
8
99
 
9
100
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
10
-
data/README_jp.md CHANGED
@@ -5,6 +5,108 @@
5
5
 
6
6
  まずはニーモニックレベルで実現し、その上に分かりやすい形のプログラミングツールを用意できればと考えています。
7
7
 
8
+ # 使い方
9
+
10
+ 使用するにはRubyが実行できる環境が必要です。
11
+ Rubyの実行環境の構築はWebサイト等を検索して構築してください。
12
+
13
+ ## インストール
14
+
15
+ gemでエスカレーターをインストールします。
16
+
17
+ ```
18
+ $ gem install escalator
19
+ ```
20
+
21
+ ## プロジェクト作成
22
+
23
+ エスカレーターをインストールするとescalatorコマンドが使用できる様になります。
24
+ escalatorコマンドでラダーを構成するプロジェクトファイルを作ります。
25
+
26
+ ```
27
+ $ escalator create my_project
28
+ $ cd my_project
29
+ ```
30
+
31
+ ファイルの構成は下の様になっています。
32
+ plc以下にエスカレーターを実行するPLCプロジェクトの雛形があります。
33
+ 現在は三菱電機のiQ-RシリーズR08CPUのみの対応となっています。
34
+ 他メーカーや他の機種は今後追加する予定です。
35
+
36
+
37
+ ```
38
+ .
39
+ ├── Rakefile
40
+ ├── asm
41
+ │   └── main.esc
42
+ ├── config
43
+ │   └── plc.yml
44
+ └── plc
45
+ └── mitsubishi
46
+ └── iq-r
47
+ └── r08
48
+ ├── LICENSE
49
+ └── r08.gx3
50
+ ```
51
+
52
+ ## 通信設定
53
+
54
+ ### PLCの通信設定
55
+
56
+ plc以下のプロジェクトファイルを開いてIPアドレスなど変更します。
57
+ 変更後PLCに設定とプログラムを書き込みます。
58
+
59
+ ### エスカレーターの設定
60
+
61
+ config/plc.ymlファイルで設定します。
62
+
63
+ 現在はiQ-Rのみの対応なので:host: 192.168.0.1の行でPLCに設定したIPアドレスを指定するのみで接続できます。
64
+
65
+ ```
66
+ :plc:
67
+ :cpu: iq-r
68
+ :protocol: mc_protocol
69
+ :host: 192.168.0.1
70
+ :port: 5007
71
+ :program_area: d10000
72
+ :interaction_area: d9998
73
+ ```
74
+
75
+ ## エスカレータープログラム作成
76
+
77
+ エスカレーターのプログラムはasm以下にあります。
78
+ 現在はmain.escファイルから生成します。
79
+
80
+ main.escを編集しプログラムを作成します。
81
+ PLC側の実装がまだ進んでいないので実行できるニーモニックはLD、OUT、AND、ORとその反転程度です。
82
+
83
+ ニーモニックについては[Wiki](https://github.com/ito-soft-design/escalator/wiki/mnemonic)の方を参照してください。
84
+
85
+ ```
86
+ LD X0
87
+ OUT Y0
88
+ LD X1
89
+ OR M0
90
+ ANI X2
91
+ OUT M0
92
+ END
93
+ ```
94
+
95
+ ## プログラムの転送
96
+
97
+ エスカレータープログラムをplcに転送するはrakeコマンドを使用します。
98
+ 転送後プログラムが実行されます。
99
+
100
+ ```
101
+ $ rake plc
102
+ ```
103
+
104
+
105
+ # エスカレーターに関する情報
106
+
107
+ - [一往確認日記 [escalator]](http://diary.itosoft.com/?category=escalator)
108
+ - [Wiki](https://github.com/ito-soft-design/escalator/wiki/)
109
+
8
110
  # ライセンス
9
111
 
10
112
  MIT
data/lib/escalator/asm.rb CHANGED
@@ -32,7 +32,7 @@ module Escalator
32
32
  BIG_ENDIAN = 1
33
33
 
34
34
  def initialize source, endian = nil
35
- @endian = endian || LITTLE_ENDIAN
35
+ @endian = endian || BIG_ENDIAN
36
36
  @lines = []
37
37
  address = 0
38
38
  source.each_line do | line|
data/lib/escalator/cli.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # The MIT License (MIT)
2
- #
2
+ #
3
3
  # Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
- #
4
+ #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining
6
6
  # a copy of this software and associated documentation files (the
7
7
  # "Software"), to deal in the Software without restriction, including
@@ -9,10 +9,10 @@
9
9
  # distribute, sublicense, and/or sell copies of the Software, and to
10
10
  # permit persons to whom the Software is furnished to do so, subject to
11
11
  # the following conditions:
12
- #
12
+ #
13
13
  # The above copyright notice and this permission notice shall be
14
14
  # included in all copies or substantial portions of the Software.
15
- #
15
+ #
16
16
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
17
  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
18
  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -28,13 +28,14 @@ include FileUtils
28
28
 
29
29
  module Escalator
30
30
  class CLI < Thor
31
-
31
+
32
32
  desc "create", "Create a new project"
33
33
  def create(name)
34
34
  template_path = File.join(Escalator_root, "template", "escalator")
35
35
  cp_r template_path, name
36
-
36
+ temlate_plc_path = File.join(Escalator_root, "plc")
37
+ cp_r temlate_plc_path, name
37
38
  end
38
-
39
+
39
40
  end
40
41
  end
@@ -0,0 +1,84 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ require 'active_support/core_ext/string/inflections'
25
+ require "escalator/protocol/protocol"
26
+ require 'escalator/uploader'
27
+ require 'yaml'
28
+
29
+ include Escalator::Protocol::Mitsubishi
30
+
31
+ module Escalator
32
+
33
+
34
+ class EscalatorConfig
35
+
36
+ def self.default
37
+ @config ||= begin
38
+ config_path = File.join "config", "plc.yml"
39
+ h = YAML.load(File.read(config_path)) if File.exist?(config_path)
40
+ new h || {}
41
+ end
42
+ end
43
+
44
+ def initialize options={}
45
+ default = {input: "asm/main.asm", output: "build/main.hex"}
46
+ @config = options.merge default
47
+ end
48
+
49
+ def protocol
50
+ @protocol ||= begin
51
+ plc_info = @config[:plc]
52
+ p = eval("#{plc_info[:protocol].camelize}.new")
53
+ p.host = plc_info[:host] if plc_info[:host]
54
+ p.port = plc_info[:port] if plc_info[:port]
55
+ p.log_level = plc_info[:log_level] if plc_info[:log_level]
56
+ p
57
+ rescue
58
+ nil
59
+ end
60
+ end
61
+
62
+ def uploader
63
+ @uploader ||= begin
64
+ u = Uploader.new
65
+ u.protocol = self.protocol
66
+ u.program_area = u.protocol.device_by_name(@config[:plc][:program_area]) if @config[:plc] && @config[:plc][:program_area]
67
+ u.interaction_area = u.protocol.device_by_name(@config[:plc][:interaction_area]) if @config[:plc] && @config[:plc][:interaction_area]
68
+ u
69
+ end
70
+ end
71
+
72
+ def method_missing(name, *args)
73
+ name = name.to_s unless name.is_a? String
74
+ case name.to_s
75
+ when /(.*)=$/
76
+ @config[$1.to_sym] = args.first
77
+ else
78
+ @config[name.to_sym]
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -1,14 +1,57 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
1
24
  module Escalator
2
25
 
3
26
  class IntelHex
4
27
 
5
28
  attr_reader :offset, :codes
6
29
 
30
+ def self.load path
31
+ offset = 0
32
+ codes = []
33
+ File.read(path).lines.each do |l|
34
+ case l[7,2]
35
+ when "00"
36
+ address = l[3,4].to_i(16)
37
+ offset ||= address
38
+ l.chomp[9..-3].each_char.each_slice(2).each_with_index do |pair, i|
39
+ codes[address - offset + i] = pair.join("").to_i(16)
40
+ end
41
+ end
42
+ end
43
+ new codes, offset
44
+ end
45
+
7
46
  def initialize codes, offset = 0
8
47
  @offset = offset
9
48
  @codes = codes
10
49
  end
11
50
 
51
+ def write_to path
52
+ File.write(path, dump)
53
+ end
54
+
12
55
  def dump
13
56
  addr = offset
14
57
  lines = []
@@ -31,15 +74,6 @@ module Escalator
31
74
  lines.join("\n")
32
75
  end
33
76
 
34
- def gxworks_memory_image
35
- lines = []
36
- @codes.each_slice(8) do |line_codes|
37
- lines << line_codes.join("\t")
38
- end
39
-
40
- lines.join("\n")
41
- end
42
-
43
77
  end
44
78
 
45
79
  end
@@ -0,0 +1,228 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module Escalator
25
+ module Protocol
26
+ module Mitsubishi
27
+
28
+ class McProtocol < Protocol
29
+
30
+ def initialize options={}
31
+ super
32
+ @host = options[:host] || "192.168.0.1"
33
+ @port = options[:port] || 5010
34
+ end
35
+
36
+ def open
37
+ @socket ||= TCPSocket.open(@host, @port)
38
+ end
39
+
40
+ def close
41
+ @socket.close if @socket
42
+ @socket = nil
43
+ end
44
+
45
+ def get_bit_from_device device
46
+ device = device_by_name device
47
+ get_bits_from_device(1, device).first
48
+ end
49
+
50
+ def get_bits_from_device count, device
51
+ device = device_by_name device
52
+ packet = make_packet(body_for_get_bits_from_deivce(count, device))
53
+ @logger.debug("> #{dump_packet packet}")
54
+ open
55
+ @socket.write(packet.pack("c*"))
56
+ @socket.flush
57
+ res = receive
58
+ bits = []
59
+ count.times do |i|
60
+ v = res[11 + i / 2]
61
+ if i % 2 == 0
62
+ bits << ((v >> 4) != 0)
63
+ else
64
+ bits << ((v & 0xf) != 0)
65
+ end
66
+ end
67
+ @logger.debug("get #{device.name} => #{bits}")
68
+ bits
69
+ end
70
+
71
+ def set_bits_to_device bits, device
72
+ device = device_by_name device
73
+ packet = make_packet(body_for_set_bits_to_device(bits, device))
74
+ @logger.debug("> #{dump_packet packet}")
75
+ open
76
+ @socket.write(packet.pack("c*"))
77
+ @socket.flush
78
+ res = receive
79
+ @logger.debug("set #{bits} to:#{device.name}")
80
+ end
81
+
82
+
83
+ def get_word_from_device device
84
+ device = device_by_name device
85
+ get_words_from_device(1, device).first
86
+ end
87
+
88
+ def get_words_from_device(count, device)
89
+ device = device_by_name device
90
+ packet = make_packet(body_for_get_words_from_deivce(count, device))
91
+ @logger.debug("> #{dump_packet packet}")
92
+ open
93
+ @socket.write(packet.pack("c*"))
94
+ @socket.flush
95
+ res = receive
96
+ words = []
97
+ res[11, 2 * count].each_slice(2) do |pair|
98
+ words << pair.pack("c*").unpack("v").first
99
+ end
100
+ @logger.debug("get from: #{device.name} => #{words}")
101
+ words
102
+ end
103
+
104
+ def set_words_to_device words, device
105
+ device = device_by_name device
106
+ packet = make_packet(body_for_set_words_to_device(words, device))
107
+ @logger.debug("> #{dump_packet packet}")
108
+ open
109
+ @socket.write(packet.pack("c*"))
110
+ @socket.flush
111
+ res = receive
112
+ @logger.debug("set #{words} to: #{device.name}")
113
+ end
114
+
115
+
116
+ def device_by_name name
117
+ case name
118
+ when String
119
+ QDevice.new name
120
+ else
121
+ # it may be already QDevice
122
+ name
123
+ end
124
+ end
125
+
126
+
127
+ def receive
128
+ res = []
129
+ len = 0
130
+ begin
131
+ Timeout.timeout(0.1) do
132
+ while true
133
+ c = @socket.read(1)
134
+ next if c.nil? || c == ""
135
+
136
+ res << c.bytes.first
137
+ len = res[7] + res[8] << 8 if res.length >= 9
138
+ break if (len + 9 == res.length)
139
+ end
140
+ end
141
+ rescue Timeout::Error
142
+ end
143
+ @logger.debug("< #{dump_packet res}")
144
+ res
145
+ end
146
+
147
+ private
148
+
149
+ def make_packet body
150
+ header = [0x50, 0x00, 0x00, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00]
151
+ header[7..8] = data_for_short(body.length + 2)
152
+ header + body
153
+ end
154
+
155
+ def body_for_get_bit_from_deivce device
156
+ body_for_get_bits_from_deivce 1, device
157
+ end
158
+
159
+ def body_for_get_bits_from_deivce count, device
160
+ body_for_get_words_from_deivce count, device, false
161
+ end
162
+
163
+ def body_for_get_words_from_deivce count, device, word = true
164
+ body = [0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00]
165
+ body[2] = 1 unless word
166
+ body[4..7] = data_for_device(device)
167
+ body[8..9] = data_for_short count
168
+ body
169
+ end
170
+
171
+
172
+ def body_for_set_bits_to_device bits, device
173
+ body = [0x01, 0x14, 0x01, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00]
174
+ d = device
175
+ bits = [bits] unless bits.is_a? Array
176
+ bits.each_slice(2) do |pair|
177
+ body << (pair.first ? 0x10 : 0x00)
178
+ body[-1] |= (pair.last ? 0x1 : 0x00) if pair.size == 2
179
+ d = d.next_device
180
+ end
181
+ body[4..7] = data_for_device(device)
182
+ body[8..9] = data_for_short bits.size
183
+ body
184
+ end
185
+ alias :body_for_set_bit_to_device :body_for_set_bits_to_device
186
+
187
+ def body_for_set_words_to_device words, device
188
+ body = [0x01, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00]
189
+ d = device
190
+ words = [words] unless words.is_a? Array
191
+ words.each do |v|
192
+ body += data_for_short v
193
+ d = d.next_device
194
+ end
195
+ body[4..7] = data_for_device(device)
196
+ body[8..9] = data_for_short words.size
197
+ body
198
+ end
199
+
200
+ def data_for_device device
201
+ a = data_for_int device.number
202
+ a[3] = device.suffix_code
203
+ a
204
+ end
205
+
206
+ def data_for_short value
207
+ [value].pack("v").unpack("c*")
208
+ end
209
+
210
+ def data_for_int value
211
+ [value].pack("V").unpack("c*")
212
+ end
213
+
214
+ def dump_packet packet
215
+ a = []
216
+ len = packet.length
217
+ bytes = packet.dup
218
+ len.times do |i|
219
+ a << ("0" + bytes[i].to_s(16))[-2, 2]
220
+ end
221
+ "[" + a.join(", ") + "]"
222
+ end
223
+
224
+ end
225
+
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,36 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ $:.unshift File.dirname(__FILE__)
25
+
26
+ module Escalator
27
+ module Protocol
28
+
29
+ require 'socket'
30
+ require 'logger'
31
+ require 'timeout'
32
+ require 'qdevice'
33
+ require 'mc_protocol'
34
+
35
+ end
36
+ end
@@ -0,0 +1,114 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module Escalator
25
+ module Protocol
26
+ module Mitsubishi
27
+
28
+ class QDevice
29
+
30
+ attr_reader :suffix, :number
31
+
32
+ SUFFIXES = %w(SM SD X Y M L F V B D W TS TC TN SS SC SN CS CC CN SB SW S DX DY Z R ZR)
33
+ SUFFIX_CODES = [0x91, 0xa9, 0x9c, 0x9d, 0x90, 0x92, 0x93, 0x94, 0xa0, 0xa8, 0xb4, 0xc1, 0xc0, 0xc2, 0xc7, 0xc6, 0xc8, 0xc4, 0xc3, 0xc5, 0xa1, 0xb5, 0x98, 0xa2, 0xa3, 0xcc ,0xaf, 0xb0]
34
+
35
+ def initialize a, b = nil
36
+ case a
37
+ when Array
38
+ case a.size
39
+ when 4
40
+ @suffix = suffix_for_code(a[3])
41
+ @number = ((a[2] << 8 | a[1]) << 8) | a[0]
42
+ end
43
+ when String
44
+ if b
45
+ @suffix = a.upcase
46
+ @number = b
47
+ else
48
+ if a.length == 12
49
+ @suffix = [a[0,2].to_i(16), a[2,2].to_i(16)].pack "c*"
50
+ @suffix.strip!
51
+ @number = a[4,8].to_i(16)
52
+ elsif /(X|Y)(.+)/i =~ a
53
+ @suffix = $1.upcase
54
+ @number = $2.to_i(p_adic_number)
55
+ else
56
+ /(M|L|S|B|F|T|C|D|W|R)(.+)/i =~ a
57
+ @suffix = $1.upcase
58
+ @number = $2.to_i(p_adic_number)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def p_adic_number
65
+ case @suffix
66
+ when "X", "Y", "B", "W", "SB", "SW", "DX", "DY", "ZR"
67
+ 16
68
+ else
69
+ 10
70
+ end
71
+ end
72
+
73
+ def name
74
+ @suffix + @number.to_s(p_adic_number).upcase
75
+ end
76
+
77
+ def next_device
78
+ d = self.class.new @suffix, @number + 1
79
+ d
80
+ end
81
+
82
+ def bit_device?
83
+ case @suffix
84
+ when "SM", "X", "Y", "M", "L", "F", "V", "B",
85
+ "TS", "TC", "SS", "SC","CS", "CC", "SB", "S", "DX", "DY"
86
+ true
87
+ else
88
+ false
89
+ end
90
+ end
91
+
92
+ def suffix_for_code code
93
+ index = SUFFIX_CODES.index code
94
+ index ? SUFFIXES[index] : nil
95
+ end
96
+
97
+ def suffix_code
98
+ index = SUFFIXES.index suffix
99
+ index ? SUFFIX_CODES[index] : 0
100
+ end
101
+
102
+ def + value
103
+ QDevice.new self.suffix, self.number + value
104
+ end
105
+
106
+ def - value
107
+ QDevice.new self.suffix, [self.number - value, 0].max
108
+ end
109
+
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,78 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ $:.unshift File.dirname(__FILE__)
25
+
26
+ module Escalator
27
+ module Protocol
28
+
29
+ class Protocol
30
+
31
+ attr_accessor :host, :port, :log_level
32
+
33
+ def initialize options={}
34
+ @logger = Logger.new(STDOUT)
35
+ @logger.level = options[:log_level] || Logger::INFO
36
+ end
37
+
38
+ def log_level= level
39
+ @log_level = level.is_a?(String) ? level.to_sym : level
40
+ case @log_level
41
+ when :debug
42
+ @logger.level = Logger::DEBUG
43
+ when :error
44
+ @logger.level = Logger::ERROR
45
+ when :fatal
46
+ @logger.level = Logger::FATAL
47
+ when :info
48
+ @logger.level = Logger::INFO
49
+ when :unknown
50
+ @logger.level = Logger::UNKNOWN
51
+ when :warn
52
+ @logger.level = Logger::WARN
53
+ end
54
+ end
55
+
56
+ # abstract methods
57
+
58
+ def open; end
59
+ def close; end
60
+
61
+ def get_bit_from_device device; end
62
+ def get_bits_from_device count, device; end
63
+ def set_bits_to_device bits, device; end
64
+ def set_bit_to_device bit, device; set_bits_to_device bit, device; end
65
+
66
+ def get_word_from_device device; end
67
+ def get_words_from_device(count, device); end
68
+ def set_words_to_device words, device; end
69
+ def set_word_to_device word, device; set_words_to_device word, device; end
70
+
71
+ def device_by_name name; nil; end
72
+
73
+ end
74
+
75
+ end
76
+ end
77
+
78
+ require 'mitsubishi/mitsubishi'
@@ -23,9 +23,11 @@
23
23
 
24
24
  Escalator_root = File.expand_path(File.join(File.dirname(__FILE__), "../../../"))
25
25
 
26
+ require "escalator/config"
26
27
  require 'escalator/asm'
27
28
  require 'escalator/intel_hex'
28
29
  require 'rake/loaders/makefile'
30
+ require 'fileutils'
29
31
 
30
32
  #directory "build"
31
33
 
@@ -61,6 +63,7 @@ rule %r{^build/.+\.hex} => ['%{^build,asm}X.esc'] do |t|
61
63
  $stderr = STDERR
62
64
  end
63
65
  stream = StringIO.new
66
+ puts "hex #{t.source}"
64
67
  asm = Escalator::Asm.new File.read(t.source)
65
68
  hex = Escalator::IntelHex.new asm.codes
66
69
  File.write(t.name, hex.dump)
@@ -77,51 +80,28 @@ rule %r{^build/.+\.gxwm} => ['%{^build,asm}X.esc'] do |t|
77
80
  $stderr = STDERR
78
81
  end
79
82
  stream = StringIO.new
83
+ puts "gxwm #{t.source}"
80
84
  asm = Escalator::Asm.new File.read(t.source)
81
85
  hex = Escalator::IntelHex.new asm.codes
82
86
  File.write(t.name, hex.gxworks_memory_image)
83
87
  end
84
88
 
85
- =begin
86
- desc "Assemble codes"
87
- task :asm do
88
- # @refer: https://github.com/rsutphin/handbrake.rb/issues/1
89
- begin
90
- $stderr = File.open('hb.log', 'w')
91
- $stdout = $stderr
92
- mkdir_p "build"
93
- ensure
94
- $stdout = STDOUT
95
- $stderr = STDERR
96
- end
97
- dir = "./asm"
98
- stream = StringIO.new
99
- sources = Dir.glob("asm/**/*.esc").each do |filename|
100
- puts "asm #{filename}"
101
- asm = Escalator::Asm.new File.read(filename)
102
- dst = File.join("build", File.basename(filename, ".*") + ".lst")
103
- File.write(dst, asm.dump_line)
104
- end
89
+ desc "Clean all generated files."
90
+ task :clean do
91
+ FileUtils.rm_r "build"
105
92
  end
106
93
 
107
- desc "Make hex codes"
108
- task :hex do
109
- begin
110
- $stderr = File.open('hb.log', 'w')
111
- $stdout = $stderr
112
- mkdir_p "build"
113
- ensure
114
- $stdout = STDOUT
115
- $stderr = STDERR
116
- end
117
- dir = "./asm"
118
- stream = StringIO.new
119
- sources = Dir.glob("asm/**/*.esc").each do |filename|
120
- puts "hex #{filename}"
121
- asm = Escalator::Asm.new File.read(filename)
122
- dst = File.join("build", File.basename(filename, ".*") + ".hex")
123
- hex = Escalator::IntelHex.new asm.codes
124
- File.write(dst, hex.dump)
125
- end
94
+ @config = Escalator::EscalatorConfig.default
95
+
96
+ task :upload => @config.output do
97
+ u = @config.uploader
98
+ u.source = @config.output
99
+ u.upload
100
+ puts "upload #{u.source}"
126
101
  end
127
- =end
102
+
103
+ desc "Install program to PLCs."
104
+ task :plc => :upload do
105
+ end
106
+
107
+ task :default => %w(build/main.lst build/main.hex build/main.gxwm)
@@ -0,0 +1,114 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module Escalator
25
+
26
+ class Uploader
27
+
28
+ attr_accessor :protocol
29
+ attr_accessor :program_area, :interaction_area
30
+ attr_accessor :source
31
+ attr_accessor :data
32
+
33
+ def initialize options={}
34
+ @protocol = options[:protocol] if options[:protocol]
35
+ @program_area = options[:program_area] if options[:program_area]
36
+ @interaction_area = options[:interaction_area] if options[:interaction_area]
37
+ end
38
+
39
+ STOP_PLC_FLAG = 2 # bit 1
40
+ CLEAR_PROGRAM_FLAG = 4 # bit 2 require bit 1 on
41
+
42
+ CYCLE_RUN_FLAG = 2
43
+
44
+
45
+ def upload
46
+ # stop plc
47
+ stop_plc
48
+ clear_program
49
+ write_program
50
+ run_plc
51
+ end
52
+
53
+ def data
54
+ @data ||= begin
55
+ hex = IntelHex.load @source
56
+ hex.codes
57
+ end
58
+ end
59
+
60
+ def word_data
61
+ data.each_slice(2).map do |pair|
62
+ pair.pack("c*").unpack("n*")
63
+ end.flatten
64
+ end
65
+
66
+ private
67
+
68
+ def to_plc_status
69
+ @to_plc_status ||= interaction_area
70
+ end
71
+
72
+ def from_plc_status
73
+ @from_plc_status ||= interaction_area.next_device
74
+ end
75
+
76
+
77
+ def stop_plc
78
+ @protocol.set_word_to_device STOP_PLC_FLAG, to_plc_status
79
+ Timeout.timeout(5) do
80
+ v = @protocol.get_word_from_device from_plc_status
81
+ break if (v & STOP_PLC_FLAG) != 0
82
+ sleep 0.1
83
+ end
84
+ end
85
+
86
+ def clear_program
87
+ @protocol.set_word_to_device STOP_PLC_FLAG | CLEAR_PROGRAM_FLAG, to_plc_status
88
+ Timeout.timeout(5) do
89
+ v = @protocol.get_word_from_device from_plc_status
90
+ break if (v & CLEAR_PROGRAM_FLAG) != 0
91
+ sleep 0.1
92
+ end
93
+ @protocol.set_word_to_device STOP_PLC_FLAG, to_plc_status
94
+ end
95
+
96
+ def run_plc
97
+ @protocol.set_word_to_device 0, to_plc_status
98
+ Timeout.timeout(5) do
99
+ v = @protocol.get_word_from_device from_plc_status
100
+ break if (v & CYCLE_RUN_FLAG) != 0
101
+ sleep 0.1
102
+ end
103
+ end
104
+
105
+ def write_program
106
+ word_data.each_slice(2*1024) do |chunk|
107
+ @protocol.set_words_to_device chunk, @program_area
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+
114
+ end
@@ -22,5 +22,5 @@
22
22
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
 
24
24
  module Escalator
25
- VERSION = "0.1.2"
25
+ VERSION = "0.2.0"
26
26
  end
data/lib/escalator.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # The MIT License (MIT)
2
- #
2
+ #
3
3
  # Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
- #
4
+ #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining
6
6
  # a copy of this software and associated documentation files (the
7
7
  # "Software"), to deal in the Software without restriction, including
@@ -9,10 +9,10 @@
9
9
  # distribute, sublicense, and/or sell copies of the Software, and to
10
10
  # permit persons to whom the Software is furnished to do so, subject to
11
11
  # the following conditions:
12
- #
12
+ #
13
13
  # The above copyright notice and this permission notice shall be
14
14
  # included in all copies or substantial portions of the Software.
15
- #
15
+ #
16
16
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
17
  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
18
  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -25,6 +25,7 @@ require "escalator/version"
25
25
  require "escalator/cli"
26
26
  require "escalator/asm"
27
27
  require "escalator/intel_hex"
28
+ require "escalator/config"
28
29
 
29
30
  Escalator_root = File.expand_path(File.join(File.dirname(__FILE__), ".."))
30
31
 
@@ -32,4 +33,4 @@ module Escalator
32
33
  end
33
34
 
34
35
 
35
- Escalator::CLI.start
36
+ Escalator::CLI.start
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
Binary file
@@ -0,0 +1,7 @@
1
+ :plc:
2
+ :cpu: iq-r
3
+ :protocol: mc_protocol
4
+ :ip: 192.168.0.1
5
+ :port: 5007
6
+ :program_area: d10000
7
+ :interaction_area: d9998
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: escalator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katsuyoshi Ito
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-03 00:00:00.000000000 Z
11
+ date: 2016-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -76,14 +76,23 @@ files:
76
76
  - lib/escalator.rb
77
77
  - lib/escalator/asm.rb
78
78
  - lib/escalator/cli.rb
79
+ - lib/escalator/config.rb
79
80
  - lib/escalator/intel_hex.rb
81
+ - lib/escalator/protocol/mitsubishi/mc_protocol.rb
82
+ - lib/escalator/protocol/mitsubishi/mitsubishi.rb
83
+ - lib/escalator/protocol/mitsubishi/qdevice.rb
84
+ - lib/escalator/protocol/protocol.rb
80
85
  - lib/escalator/tasks/build.rb
86
+ - lib/escalator/uploader.rb
81
87
  - lib/escalator/version.rb
88
+ - plc/mitsubishi/iq-r/r08/LICENSE
89
+ - plc/mitsubishi/iq-r/r08/r08.gx3
82
90
  - sample/escalator/sample1.esc
83
91
  - sample/escalator/sample2.esc
84
92
  - sample/escalator/sample2.png
85
93
  - template/escalator/Rakefile
86
94
  - template/escalator/asm/main.esc
95
+ - template/escalator/config/plc.yml
87
96
  homepage: https://github.com/ito-soft-design/escalator/wiki
88
97
  licenses:
89
98
  - MIT