kiss-tnc 2.1.3 → 3.0.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
  SHA256:
3
- metadata.gz: 55ae672ec1ac60f99d3a1f00f8dcd50ab5c1d038a0dd3e5ee4bb2b4daecf2c43
4
- data.tar.gz: de6a210464d037b0e4b3abb13d2b965a1dd29d811272aae626eb2ee0a39943c7
3
+ metadata.gz: 61dbec4bf66f1db239d96585ba818fb83bb42f657c24cd01dfa51c6df0ced59d
4
+ data.tar.gz: 3f3ee940d016ba7c7f138cf0f9decc38d5f56b4506d94c51aae78aa60df27add
5
5
  SHA512:
6
- metadata.gz: 0ccc9c74f19bc05acb707d8410d2ec2eaafb1ed9c5fd33ae5af9182e7549f899a3e7e2b71f4f995cfcb4dca799c77ee80f664f2b07c30b6b6aa539cef6230042
7
- data.tar.gz: cacc2a22ac9dcce2989f466402b370077660bca521deb70344716e0c26d399287534f0299c4cb3e4a32c115ca473d91a3dc3968cb15994843e81a67de1ef2df1
6
+ metadata.gz: 99031072302ad853d9129aab307df24246adc382fb6b87f8c246d412fa2e49973bbba4c69df783040418dbfcc5accb7a85e3157c06b98811b986fd500e780afc
7
+ data.tar.gz: d9250c3e5708faa3e2a71336e008111f8b600e31d67e7ab6e211f54de2c6733a0c6fc70562139c940fb307b24e5d727cde5a226d6a4fc5cbd2d837afc01df6de
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ Gemfile.lock
6
6
  .idea/
7
7
  *.gem
8
8
  /public/
9
+ /vendor/
data/CHANGELOG.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.0
4
+ * Moved all elements of AX.25 out into its own library (https://git.qoto.org/digipex/ax25).
5
+ * No longer backwards compatible with older versions.
6
+
3
7
  ## 2.1.3
4
8
 
5
- * Had to revert some of the changes fromt he last release... KissAbstract now uses include again and kind_of should now work (unit test added to confirm).
9
+ * Had to revert some of the changes from the last release... KissAbstract now uses include again and kind_of should now work (unit test added to confirm).
6
10
 
7
11
  ## 2.1.2
8
12
 
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Gem Version](https://badge.fury.io/rb/kiss-tnc.svg)](https://badge.fury.io/rb/kiss-tnc)
2
+
1
3
  # Kiss
2
4
 
3
5
  Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/apex`. To experiment with that code, run `bin/console` for an interactive prompt.
@@ -20,17 +22,18 @@ Or install it yourself as:
20
22
 
21
23
  $ gem install kiss-tnc
22
24
 
23
- ## Usage
24
-
25
- TODO: Write usage instructions here
26
-
27
25
  ## Development
28
26
 
29
27
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
28
 
31
29
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
30
 
31
+ To run tests do the following:
32
+
33
+ ```bash
34
+ bundle exec rspec
35
+ ```
36
+
33
37
  ## Contributing
34
38
 
35
39
  Bug reports and pull requests are welcome on GitHub at https://github.com/Syncleus/kiss-tnc
36
-
data/kiss-tnc.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Kiss::VERSION
9
9
  spec.licenses = ['Apache-2.0']
10
10
  spec.authors = ['Jeffrey Phillips Freeman']
11
- spec.email = ['jeffrey.freeman@syncleus.com']
11
+ spec.email = ['the@jeffreyfreeman.me']
12
12
 
13
13
  spec.summary = %q{Library for KISS communication with TNCs.}
14
14
  spec.description = %q{Ruby library for KISS communication with Terminal Node Controllers.}
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
26
  f.match(%r{^(test|spec|features)/})
27
27
  end
28
- spec.bindir = 'exe'
28
+ spec.bindir = 'bin'
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ['lib']
31
31
 
data/lib/kiss/app_info.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kiss
2
- VERSION = "2.1.3"
2
+ VERSION = "3.0.0"
3
3
  end
data/lib/kiss/kiss.rb CHANGED
@@ -4,6 +4,101 @@ module Kiss
4
4
  module Kiss
5
5
  include Abstractify::Abstract
6
6
 
7
- abstract :write_interface, :read_interface, :connect, :close
7
+ abstract :write_interface, :read_interface, :connect, :close, :read_datagram, :write_generic_command, :write_datagram, :write_tx_delay, :write_persistence, :write_slot_time, :write_tx_tail, :write_full_duplex, :write_set_hardware, :write_exit_kiss_mode
8
+
9
+ ##
10
+ # Reads raw data as a byte array off the underlying stream. This data is
11
+ # before escapaing, or extracting from the frame.
12
+ protected
13
+ def read_interface(*args, **kwargs)
14
+ end
15
+
16
+ ##
17
+ # Writes raw bytes to the underlying stream. The bytes should already begin
18
+ # escaped and framed by this point.
19
+ protected
20
+ def write_interface(data, *args, **kwargs)
21
+ end
22
+
23
+ ##
24
+ # Prepares the underlying connection for reading and writing. What this
25
+ # means depends on the underlying implementation. It may do nothing if
26
+ # there are no initialization steps. Otherwise it will initiate the
27
+ # connection to the TNC and send the initial data needed to get it to
28
+ # enter KISS mode.
29
+ public
30
+ def connect(mode_init=nil, *args, **kwargs)
31
+ end
32
+
33
+ ##
34
+ # Closes the underlying connection and frees up any relevant resources.
35
+ # After the connection is closed read and write opperations will usually
36
+ # fail.
37
+ public
38
+ def close(*args, **kwargs)
39
+ end
40
+
41
+ ##
42
+ # Read and return the next datagram waiting in the buffer. Nil if there
43
+ # is no datagram waiting.
44
+ public
45
+ def read_datagram(*args, **kwargs)
46
+ end
47
+
48
+ ##
49
+ # This method sends any command to the underlying KISS device. All
50
+ # outgoing calls, methods with write_*, ultimately calls this method
51
+ # as the underlying call to KISS.
52
+ public
53
+ def write_generic_command(command, port, value, *args, **kwargs)
54
+ end
55
+
56
+ ##
57
+ # This command nibble should be set anytime a datagram is being sent for transmission.
58
+ public
59
+ def write_datagram(packet_data, port = 0, *args, **kwargs)
60
+ end
61
+
62
+ ##
63
+ # The amount of time to wait between keying the transmitter and beginning to send data (in 10 ms units).
64
+ public
65
+ def write_tx_delay(tx_delay, port = 0, *args, **kwargs)
66
+ end
67
+
68
+ ##
69
+ # The persistence parameter. Persistence=Data*256-1. Used for CSMA.
70
+ public
71
+ def write_persistence(persistence, port = 0, *args, **kwargs)
72
+ end
73
+
74
+ ##
75
+ # Slot time in 10 ms units. Used for CSMA.
76
+ public
77
+ def write_slot_time(slot_time, port = 0, *args, **kwargs)
78
+ end
79
+
80
+ ##
81
+ # The length of time to keep the transmitter keyed after sending the data (in 10 ms units).
82
+ public
83
+ def write_tx_tail(tx_tail, port = 0, *args, **kwargs)
84
+ end
85
+
86
+ ##
87
+ # True to turn on full_duplex, false to turn it off.
88
+ public
89
+ def write_full_duplex(full_duplex, port = 0, *args, **kwargs)
90
+ end
91
+
92
+ ##
93
+ # The meaning of this command is device dependent.
94
+ public
95
+ def write_set_hardware(value, port = 0, *args, **kwargs)
96
+ end
97
+
98
+ ##
99
+ # The meaning of this command is device dependent.
100
+ public
101
+ def write_exit_kiss_mode(*args, **kwargs)
102
+ end
8
103
  end
9
104
  end
@@ -57,138 +57,10 @@ module Kiss
57
57
  end
58
58
 
59
59
  private
60
- def self.extract_path(start, raw_frame)
61
- full_path = []
62
-
63
- (2...start).each do |i|
64
- path = identity_as_string(extract_callsign(raw_frame[i * 7..-1]))
65
- if path and path.length > 0
66
- if raw_frame[i * 7 + 6] & 0x80 != 0
67
- full_path << [path, '*'].join
68
- else
69
- full_path << path
70
- end
71
- end
72
- end
73
- return full_path
74
- end
75
-
76
- private
77
- def self.parse_identity_string(identity_string)
78
- # If we are parsing a spent token then first lets get rid of the astresick suffix.
79
- if identity_string[-1] == '*'
80
- identity_string = identity_string[0..-1]
81
- end
82
-
83
- if identity_string.include? '-'
84
- call_sign, ssid = identity_string.split('-')
85
- else
86
- call_sign = identity_string
87
- ssid = 0
88
- end
89
-
90
- return {:callsign => call_sign, :ssid => ssid.to_i}
91
- end
92
-
93
- private
94
- def self.identity_as_string(identity)
95
- if identity[:ssid] and identity[:ssid] > 0
96
- return [identity[:callsign], identity[:ssid].to_s].join('-')
97
- else
98
- return identity[:callsign]
99
- end
100
- end
101
-
102
- private
103
- def self.extract_callsign(raw_frame)
104
- callsign_as_array = raw_frame[0...6].map { |x| (x >> 1).chr }
105
- callsign = callsign_as_array.join.strip
106
- ssid = (raw_frame[6] >> 1) & 0x0f
107
- ssid = (ssid == nil or ssid == 0 ? nil : ssid)
108
- return {:callsign => callsign, :ssid => ssid}
109
- end
110
-
111
- private
112
- def self.encode_callsign(callsign)
113
- call_sign = callsign[:callsign]
114
-
115
- enc_ssid = (callsign[:ssid] << 1) | 0x60
116
-
117
- if call_sign.include? '*'
118
- call_sign.gsub!(/\*/, '')
119
- enc_ssid |= 0x80
120
- end
121
-
122
- while call_sign.length < 6
123
- call_sign = [call_sign, ' '].join
124
- end
125
-
126
- return call_sign.chars.map { |p| p.ord << 1 } + [enc_ssid]
127
- end
128
-
129
- private
130
- def self.encode_frame(frame)
131
- enc_frame = encode_callsign(parse_identity_string(frame[:destination].to_s)) + encode_callsign(parse_identity_string(frame[:source].to_s))
132
-
133
- frame[:path].each do |hop|
134
- enc_frame += encode_callsign(parse_identity_string(hop.to_s))
135
- end
136
-
137
- return enc_frame[0...-1] + [enc_frame[-1] | 0x01] + [SLOT_TIME] + [0xf0] + frame[:payload].chars.map { |c| c.ord }
138
- end
139
-
140
- private
141
- def self.decode_frame(raw_frame)
142
- frame_len = raw_frame.length
143
-
144
- if frame_len > 16
145
- (0...frame_len - 2).each do |raw_slice|
146
- # Is address field length correct?
147
- if raw_frame[raw_slice] & 0x01 != 0 and ((raw_slice + 1) % 7) == 0
148
- i = (raw_slice.to_f + 1.0) / 7.0
149
- # Less than 2 callsigns?
150
- if 1.0 < i and i < 11.0
151
- if raw_frame[raw_slice + 1] & 0x03 == 0x03 and [0xf0, 0xcf].include? raw_frame[raw_slice + 2]
152
- payload_as_array = raw_frame[raw_slice + 3..-1].map { |b| b.chr }
153
- payload = payload_as_array.join
154
- destination = identity_as_string(extract_callsign(raw_frame))
155
- source = identity_as_string(extract_callsign(raw_frame[7..-1]))
156
- path = extract_path(i.to_i, raw_frame)
157
- return {:source => source, :destination => destination, :path => path, :payload => payload}
158
- end
159
- end
160
- end
161
- end
162
- end
163
- return nil
164
- end
165
-
166
- private
167
- def self.valid_frame(raw_frame)
168
- frame_len = raw_frame.length
169
-
170
- if frame_len > 16
171
- (0...frame_len - 2).each do |raw_slice|
172
- # Is address field length correct?
173
- if raw_frame[raw_slice] & 0x01 != 0 and ((raw_slice + 1) % 7) == 0
174
- i = (raw_slice.to_f + 1.0) / 7.0
175
- # Less than 2 callsigns?
176
- if 1.0 < i and i < 11.0
177
- if raw_frame[raw_slice + 1] & 0x03 == 0x03 and [0xf0, 0xcf].include? raw_frame[raw_slice + 2]
178
- return true
179
- end
180
- end
181
- end
182
- end
183
- end
184
- return false
185
- end
186
-
187
- private
188
- def fill_buffer
60
+ def fill_buffer(*args, **kwargs)
189
61
  new_frames = []
190
62
  read_buffer = []
191
- read_data = read_interface
63
+ read_data = read_interface(*args, **kwargs)
192
64
  while read_data and read_data.length > 0
193
65
  split_data = [[]]
194
66
  read_data.each do |read_byte|
@@ -229,7 +101,7 @@ module Kiss
229
101
  end
230
102
  end
231
103
  # Get anymore data that is waiting
232
- read_data = read_interface
104
+ read_data = read_interface(*args, **kwargs)
233
105
  end
234
106
 
235
107
  new_frames.each do |new_frame|
@@ -245,7 +117,7 @@ module Kiss
245
117
  private
246
118
  def read_bytes(*args, **kwargs)
247
119
  if @frame_buffer.length == 0
248
- fill_buffer
120
+ fill_buffer(*args, **kwargs)
249
121
  end
250
122
 
251
123
  if @frame_buffer.length > 0
@@ -257,48 +129,99 @@ module Kiss
257
129
  end
258
130
  end
259
131
 
260
- private
261
- def write_bytes(frame_bytes, port=0, *args, **kwargs)
262
- kiss_packet = [FEND] + [KissAbstract.command_byte_combine(port, DATA_FRAME)] +
263
- KissAbstract.escape_special_codes(frame_bytes) + [FEND]
132
+ public
133
+ def write_generic_command(command, port, value, *args, **kwargs)
134
+ @lock.synchronize do
135
+ raise ArgumentError.new ("command can not be nil") if command.nil?
136
+ raise RangeError.new("command must be an integer between 0 (inclusive) and 15 (inclusive) instead we got #{command}") if command < 0 or command >= 16 or command&0x0f!=command
137
+ raise ArgumentError.new ("port can not be nil") if port.nil?
138
+ raise RangeError.new("port must be an integer between 0 (inclusive) and 15 (inclusive) instead we got #{port}") if port < 0 or port >= 16 or port&0x0f!=port
264
139
 
265
- write_interface(kiss_packet)
140
+ if value.nil?
141
+ write_interface([FEND] + [KissAbstract.command_byte_combine(port, command)] +[FEND], *args, **kwargs)
142
+ else
143
+ write_interface([FEND] + [KissAbstract.command_byte_combine(port, command)] + KissAbstract.escape_special_codes(value) + [FEND], *args, **kwargs)
144
+ end
145
+ end
266
146
  end
267
147
 
268
- protected
269
- def write_setting(command, value)
270
- write_interface([FEND] + [command] + KissAbstract.escape_special_codes(value) + [FEND])
148
+ public
149
+ def write_datagram(packet_data, port = 0, *args, **kwargs)
150
+ raise TypeError.new("packet_data can not be nil") if packet_data.nil?
151
+ raise TypeError.new("packet_data must be an array-like object that implements each, length, and []") if (not packet_data.respond_to?(:each)) or (not packet_data.respond_to?(:[])) or (not packet_data.respond_to?(:length))
152
+ raise RangeError.new("packet_data must have a length of 1 or more") if packet_data.length <= 0
153
+
154
+ write_generic_command(DATA_FRAME, port, packet_data, *args, **kwargs)
271
155
  end
272
156
 
273
157
  public
274
- def connect(mode_init=nil, *args, **kwargs)
158
+ def write_tx_delay(tx_delay, port = 0, *args, **kwargs)
159
+ raise TypeError.new("tx_delay can not be nil") if tx_delay.nil?
160
+ raise RangeError.new("tx_delay must be an integer value greater than or equal to 0 and less than 256") if tx_delay < 0 and tx_delay&0xff==tx_delay
161
+
162
+ write_generic_command(TX_DELAY, port, tx_delay, *args, **kwargs)
275
163
  end
276
164
 
277
165
  public
278
- def close(*args, **kwargs)
166
+ def write_persistence(persistence, port = 0, *args, **kwargs)
167
+ raise TypeError.new("persistence can not be nil") if persistence.nil?
168
+ raise RangeError.new("persistence must be an integer value greater than or equal to 0 and less than 256") if persistence < 0 and persistence&0xff==persistence
169
+
170
+ write_generic_command(PERSISTENCE, port, persistence, *args, **kwargs)
279
171
  end
280
172
 
281
173
  public
282
- def read(*args, **kwargs)
283
- @lock.synchronize do
284
- frame = self.read_bytes(*args, **kwargs)
285
- if frame and frame.length > 0
286
- return KissAbstract.decode_frame(frame)
287
- else
288
- return nil
289
- end
174
+ def write_slot_time(slot_time, port = 0, *args, **kwargs)
175
+ raise TypeError.new("slot_time can not be nil") if slot_time.nil?
176
+ raise RangeError.new("slot_time must be an integer value greater than or equal to 0 and less than 256") if slot_time < 0 and slot_time&0xff==slot_time
177
+
178
+ write_generic_command(SLOT_TIME, port, slot_time, *args, **kwargs)
179
+ end
180
+
181
+ public
182
+ def write_tx_tail(tx_tail, port = 0, *args, **kwargs)
183
+ raise TypeError.new("tx_tail can not be nil") if tx_tail.nil?
184
+ raise RangeError.new("tx_tail must be an integer value greater than or equal to 0 and less than 256") if tx_tail < 0 and tx_tail&0xff==tx_tail
185
+
186
+ write_generic_command(TX_TAIL, port, tx_tail, *args, **kwargs)
187
+ end
188
+
189
+ public
190
+ def write_full_duplex(full_duplex, port = 0, *args, **kwargs)
191
+ raise TypeError.new("full_duplex can not be nil") if full_duplex.nil?
192
+ raise TypeError.new("full_duplex must be either truthy or falsy") if full_duplex != (!!full_duplex)
193
+
194
+ if full_duplex
195
+ write_generic_command(FULL_DUPLEX, port, 1, *args, **kwargs)
196
+ else
197
+ write_generic_command(FULL_DUPLEX, port, 0, *args, **kwargs)
290
198
  end
291
199
  end
292
200
 
293
201
  public
294
- def write(frame, port=0, *args, **kwargs)
202
+ def write_set_hardware(value, port = 0, *args, **kwargs)
203
+ raise TypeError.new("value can not be nil") if value.nil?
204
+
205
+ write_generic_command(SET_HARDWARE, port, value, *args, **kwargs)
206
+ end
207
+
208
+ public
209
+ def write_exit_kiss_mode(*args, **kwargs)
210
+ write_generic_command(RETURN, 0, nil*args, **kwargs)
211
+ end
212
+
213
+ public
214
+ def connect(mode_init=nil, *args, **kwargs)
215
+ end
216
+
217
+ public
218
+ def close(*args, **kwargs)
219
+ end
220
+
221
+ public
222
+ def read_datagram(*args, **kwargs)
295
223
  @lock.synchronize do
296
- encoded_frame = KissAbstract.encode_frame(frame)
297
- if KissAbstract.valid_frame(encoded_frame)
298
- self.write_bytes(encoded_frame, *args, **kwargs)
299
- else
300
- raise IOError.new("frame was able to be encoded but was determined not to be valid")
301
- end
224
+ return self.read_bytes(*args, **kwargs)
302
225
  end
303
226
  end
304
227
  end
@@ -28,8 +28,8 @@ module Kiss
28
28
  end
29
29
 
30
30
  protected
31
- def read_interface
32
- read_data = @serial.read(@read_bytes)
31
+ def read_interface(*args, **kwargs)
32
+ read_data = @serial.read(@read_bytes, *args, **kwargs)
33
33
  if read_data
34
34
  return read_data.chars.map { |c| c.ord }
35
35
  else
@@ -38,18 +38,18 @@ module Kiss
38
38
  end
39
39
 
40
40
  protected
41
- def write_interface(data)
41
+ def write_interface(data, *args, **kwargs)
42
42
  unless data.is_a? String
43
43
  data = data.map { |b| b.chr }.join
44
44
  end
45
- @serial.write(data)
45
+ @serial.write(data, *args, **kwargs)
46
46
  end
47
47
 
48
48
  public
49
49
  def connect(mode_init=nil, *args, **kwargs)
50
50
  super
51
51
 
52
- @serial = SerialPort.new(@com_port, @baud, @byte_size, @stop_bits, @parity)
52
+ @serial = SerialPort.new(@com_port, @baud, @byte_size, @stop_bits, @parity, *args, **kwargs)
53
53
  @serial.read_timeout = SERIAL_READ_TIMEOUT
54
54
 
55
55
  if mode_init
@@ -58,14 +58,6 @@ module Kiss
58
58
  else
59
59
  @exit_kiss = false
60
60
  end
61
-
62
- # Previous verious defaulted to Xastir-friendly configs. Unfortunately
63
- # those don't work with Bluetooth TNCs, so we're reverting to None.
64
- if kwargs
65
- kwargs.each do |name, value|
66
- write_setting(name, value)
67
- end
68
- end
69
61
  end
70
62
 
71
63
  public
@@ -79,7 +71,7 @@ module Kiss
79
71
  if @serial == nil or @serial.closed?
80
72
  raise IOError.new('Attempting to close before the class has been started.')
81
73
  else
82
- @serial.close
74
+ @serial.close(*args, **kwargs)
83
75
  end
84
76
  end
85
77
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kiss-tnc
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.3
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeffrey Phillips Freeman
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-16 00:00:00.000000000 Z
11
+ date: 2023-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: abstractify
@@ -110,7 +110,7 @@ dependencies:
110
110
  version: '0.9'
111
111
  description: Ruby library for KISS communication with Terminal Node Controllers.
112
112
  email:
113
- - jeffrey.freeman@syncleus.com
113
+ - the@jeffreyfreeman.me
114
114
  executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
@@ -122,7 +122,6 @@ files:
122
122
  - Gemfile
123
123
  - LICENSE
124
124
  - README.md
125
- - README.rdoc
126
125
  - Rakefile
127
126
  - kiss-tnc.gemspec
128
127
  - lib/kiss.rb
@@ -151,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
150
  - !ruby/object:Gem::Version
152
151
  version: '0'
153
152
  requirements: []
154
- rubygems_version: 3.4.17
153
+ rubygems_version: 3.4.19
155
154
  signing_key:
156
155
  specification_version: 4
157
156
  summary: Library for KISS communication with TNCs.
data/README.rdoc DELETED
@@ -1,19 +0,0 @@
1
- = apex - DESCRIBE YOUR GEM
2
-
3
- Author:: Jeffrey Phillips Freeman (freemo@gmail.com)
4
- Copyright:: Copyright (c) 2016 Syncleus
5
-
6
-
7
- DESCRIBE YOUR GEM HERE
8
-
9
- == Links
10
-
11
- * {Source on Github}[https://github.com/Syncleus/kiss-tnc]
12
- * RDoc[LINK TO RDOC.INFO]
13
-
14
- == Install
15
-
16
- == Examples
17
-
18
- == Contributing
19
-