tef-furcoms 0.1.0 → 0.1.1

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: c612206f7e17733552bcca965b3e2684a5ba17c9
4
- data.tar.gz: 6bde1a400cb7e6f6fedbe93f50a0ae0550830e1e
3
+ metadata.gz: 2d1cff2d52f453c7c95ed031309ad6d24c397f84
4
+ data.tar.gz: d61410a2fe3d4618b8619a658505e56804c6e732
5
5
  SHA512:
6
- metadata.gz: bc921df1642b8f5ac7cea97fae3080f40c107621c9ae70a7e51fb42a21e14416a67ff64a2e3899d594da5e6721f688e3e2a1322875c6488d293d8b2280b8becd
7
- data.tar.gz: 6a1508a0ec5f87fb9c51e43344987f707faa767caebb3095967eba446fe80d3a8dc539a5da8ee9ab0fa9f74d36cf736c1bd5d0200429dc7673ce34905d6babf1
6
+ metadata.gz: 26ca8a0c9c65b4069bcd93a93a351c9a18453ebea0ef2f58fdd8d1c8537507eac7499c56e91e6d246f0bdb720df1e8f8d5d9aa4bd111fde50bb757cc9ce30fd2
7
+ data.tar.gz: 15d07676bf346fc3f096cd681425b9a44948fec6726d5e3cf6aaf0efadc48b8df5b98f850c001704ede71dc5dec35e792e962a4496f477630e82383bdefefd9b
data/README.md CHANGED
@@ -1,6 +1,50 @@
1
1
 
2
2
  # TheElectricFursuit FurComs
3
3
 
4
- This... Is a stub for now. Stay tuned for documentation!
5
- OR!
6
- Head to github.com/Xasin/system-synth-suit/Ruby/VoiceControl and see how this works.
4
+ This gem is the link between a FurComs hardware bus and your ruby code!
5
+ It provides a standardized interface for sending and receiving messages, using
6
+ a large variety of channels to connect to a FurComs bus.
7
+
8
+ In its current state, it provides the following options:
9
+ - A direct FurComs connection via a USB to UART adapter.
10
+ This does not provide arbitration, but is perfect for testing, as
11
+ well as read-only access.
12
+ - Two MQTT Briding classes to connect a Serial FurComs connection
13
+ to a broker, and to access the FurComs bus via MQTT.
14
+
15
+ In upcoming versions there will be other options available, such as
16
+ using a STM32 as translator to the computer, providing true arbitration.
17
+
18
+ ### Quickstart:
19
+
20
+ Let's give a short and sweet example. Here, we will try to connect to a FurComs bus using a USB to UART adapter connected to `/dev/ttyACM0` and send and receive a few messages.
21
+
22
+ ```Ruby
23
+ require 'tef/furcoms.rb'
24
+
25
+ coms_interface = TEF::FurComs::Serial.new('/dev/ttyACM0');
26
+
27
+ coms_interface.on_message /^Topic_Regexp(\d)/ do |data, topic|
28
+ puts "Got some data on #{topic}!"
29
+ end
30
+
31
+ coms_interface.send_message 'Topic_Regexp1234', 'Henlo yes!'
32
+ ```
33
+
34
+ And that's all you need!
35
+
36
+ If you want to use MQTT you'll need to do the following instead:
37
+ ```Ruby
38
+ require 'mqtt/sub_handler.rb' # Comes from the mqtt-sub_handler gem, which is not a dependency of this FurComs gem!
39
+
40
+ require 'tef/furcoms.rb'
41
+
42
+ mqtt = MQTT::SubHandler.new('your.broker.address');
43
+
44
+ coms_interface = TEF::FurComs::SerialToMQTT.new('/dev/ttyACM0', mqtt, 'FurComs/MyTopic/');
45
+ # Leaving out the topic will instead use 'FurComs/DEVNAME', in this case FurComs/ttyACM0
46
+
47
+ coms_over_mqtt = TEF::FurComs::MQTT.new(mqtt, 'FurComs/MyTopic/');
48
+ ```
49
+
50
+ The SerialToMQTT and MQTT classes have the same interface functions as the Serial class, and can be used interchangeably!
@@ -1,3 +1,3 @@
1
1
 
2
- require_relative 'furcoms/SerialToMQTT.rb'
3
- require_relative 'furcoms/MQTT.rb'
2
+ require_relative 'furcoms/serial_to_mqtt.rb'
3
+ require_relative 'furcoms/mqtt.rb'
@@ -0,0 +1,78 @@
1
+
2
+
3
+ # TheElectricFursuits Ruby namespace.
4
+ # @see https://github.com/TheElectricFursuits/
5
+ module TEF
6
+ # Module for all classes related to the FurComs communication bus.
7
+ # @see https://github.com/TheElectricFursuits/tef-FurComs
8
+ module FurComs
9
+ # Base class for a FurComs bus.
10
+ #
11
+ # This base class represents a single, bidirectional connection
12
+ # point to a FurComs bus. It provides function prototypes for sending
13
+ # and receiving messages that shall be overloaded by actual implementations.
14
+ #
15
+ # @author The System (Neira)
16
+ #
17
+ # @see Serial
18
+ # @see MQTT
19
+ class Base
20
+ # Initialize an empty base class.
21
+ #
22
+ # Note that this class cannot be used for communication!
23
+ def initialize()
24
+ @message_procs = [];
25
+ end
26
+
27
+ # Send a message to the FurComs bus.
28
+ #
29
+ # This will send a message onto topic, using the given
30
+ # priority and chip_id (defaulting both to 0).
31
+ #
32
+ # @param topic [String] Topic to send the message onto
33
+ # @param message [String] Binary string of data to send, expected
34
+ # to be ASCII-8 encoded, can contain any character (including null)
35
+ def send_message(topic, message, priority: 0, chip_id: 0) end
36
+
37
+ # Add a message callback.
38
+ #
39
+ # Calling this function with a block will add a callback to received
40
+ # messages. Any new message that is received will be passed to the
41
+ # callback.
42
+ # @param topic_filter [String, RegExp] Optional topic filter to use,
43
+ # either a String (for exact match) or Regexp
44
+ # @yieldparam data [String] Raw data received from the FurComs bus.
45
+ # ASCII-8 encoded, may contain any binary character.
46
+ # @yieldparam topic [String] Topic string that this message was received on.
47
+ def on_message(topic_filter = nil, &block)
48
+ o_msg = { topic: topic_filter, block: block };
49
+ @message_procs << o_msg;
50
+
51
+ o_msg
52
+ end
53
+
54
+ # @private
55
+ # Internal function used merely to cleanly hand out received data
56
+ # to message callbacks.
57
+ private def handout_data(topic, data)
58
+ x_logd("Data received on #{topic}: #{data}")
59
+
60
+ @message_procs.each do |callback|
61
+ begin
62
+ if (filter = callback[:topic])
63
+ if filter.is_a? String
64
+ next unless topic == filter
65
+ elsif filter.is_a? RegExp
66
+ next unless filter.match(topic)
67
+ end
68
+ end
69
+
70
+ callback[:block].call(data, topic)
71
+ rescue => e
72
+ x_logf("Error in callback #{callback[:block]}: #{e}");
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,60 @@
1
+
2
+ require_relative 'base.rb'
3
+
4
+ require 'xasin_logger'
5
+
6
+ module TEF
7
+ module FurComs
8
+ # FurComs MQTT wrapper
9
+ #
10
+ # This class will connect to a MQTT Broker, and use it as a bridge
11
+ # to a FurComs hardware connection.
12
+ # This allows multiple different systems to access different busses remotely,
13
+ # and has the added benefit of allowing Read/Write restrictions using
14
+ # the MQTT built-in authentication.
15
+ class MQTT < Base
16
+ include XasLogger::Mix
17
+
18
+ # Initialize a new FurComs MQTT Bridge.
19
+ #
20
+ # This will subscribe to the topic: topic + 'Received/#', and will
21
+ # being relaying messages to the attached callbacks immediately.
22
+ # Messages it wants to send will be sent to topic + 'Send/#{msg_topic}'
23
+ #
24
+ # If no MQTT to FurComs is attached to the bus, the message will be lost!
25
+ #
26
+ # @param mqtt The MQTT handler. Must support subscribe_to and publish_to.
27
+ # A fitting gem would be mqtt-sub_handler.
28
+ # @param topic [String] Topic base. Must end with /
29
+ def initialize(mqtt, topic = 'FurComs/ttyACM0/')
30
+ super();
31
+
32
+ @mqtt = mqtt;
33
+ @mqtt_topic = topic;
34
+
35
+ @mqtt.subscribe_to topic + 'Received/#' do |data, msg_topic|
36
+ msg_topic = msg_topic.join('/')
37
+ next unless msg_topic =~ /^[\w\s\/]*$/
38
+
39
+ handout_data(msg_topic, data);
40
+ end
41
+
42
+ init_x_log(@mqtt_topic)
43
+ end
44
+
45
+ # (see Base#send_message)
46
+ def send_message(topic, data, priority: 0, chip_id: 0)
47
+ unless topic =~ /^[\w\s\/]*$/
48
+ raise ArgumentError, 'Topic includes invalid characters!'
49
+ end
50
+ if (topic.length + data.length) > 250
51
+ raise ArgumentError, 'Message packet length exceeded!'
52
+ end
53
+
54
+ x_logd("Sending '#{topic}': '#{data}'")
55
+
56
+ @mqtt.publish_to @mqtt_topic + "Send/#{topic}", data
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,29 +1,47 @@
1
1
 
2
- require_relative 'Base.rb'
2
+ require_relative 'base.rb'
3
3
 
4
4
  require 'serialport'
5
5
  require 'xasin_logger'
6
6
 
7
7
  module TEF
8
8
  module FurComs
9
+ # USB-To-Serial FurComs Bridge.
10
+ #
11
+ # This class will handle connecting to a FurComs bus using a standard
12
+ # USB-To-UART adapter, such as an FTDI-Chip, in order to send and
13
+ # receive messages.
14
+ #
15
+ # @note This class provides limited FurComs compatibility. Arbitration
16
+ # handling is not possible, which may cause frequent collisions in busy
17
+ # bus conditions. It is recommended to use a STM32 processor
18
+ # that provides translation between FurComs and the computer.
9
19
  class Serial < Base
10
20
  include XasLogger::Mix
11
21
 
12
- def initialize(port)
22
+ # Initialize a Serial bridge class.
23
+ #
24
+ # This will open the given Serial port and begin reading/writing
25
+ # onto the FurComs bus.
26
+ # @note This class can not provide full arbitration handling. This may
27
+ # cause issues in busy bus conditions!
28
+ # @todo Add graceful handling of controller disconnect/reconnect.
29
+ def initialize(port = '/dev/ttyACM0', baudrate = 115_200)
13
30
  super();
14
31
 
15
32
  @port = SerialPort.new(port);
16
- @port.baud = 115200;
33
+ @port.baud = baudrate;
17
34
  @port.sync = true;
18
35
 
19
36
  start_thread();
20
37
 
21
38
  init_x_log("FurComs #{port}")
22
- x_logi("Ready!");
39
+ x_logi('Ready!');
23
40
  end
24
41
 
25
42
  private def decode_data_string(data)
26
- return if(data.length() < 9)
43
+ return if data.length() < 9
44
+
27
45
  payload = data[8..-1]
28
46
  topic, _sep, payload = payload.partition("\0")
29
47
 
@@ -40,11 +58,12 @@ module TEF
40
58
  c = 0;
41
59
 
42
60
  loop do
43
- c = @port.getbyte
44
- if c == 0
61
+ c = @port.getbyte # Blocks until byte is available on bus.
62
+
63
+ if c.zero? # 0x00 Indicates STOP
45
64
  decode_data_string rx_buffer
46
65
  rx_buffer = ''
47
- elsif had_esc
66
+ elsif had_esc # ESC had been received, decode next byte.
48
67
  case c
49
68
  when 0xDC
50
69
  rx_buffer += "\0"
@@ -52,9 +71,9 @@ module TEF
52
71
  rx_buffer += "\xDB"
53
72
  end
54
73
  had_esc = false
55
- elsif c == 0xDB
74
+ elsif c == 0xDB # SLIP ESC
56
75
  had_esc = true
57
- else
76
+ else # No special treatment needed, add byte.
58
77
  rx_buffer += c.chr
59
78
  end
60
79
  end
@@ -65,7 +84,7 @@ module TEF
65
84
 
66
85
  private def slip_encode_data(data_str)
67
86
  data_str.bytes.map do |b|
68
- if b == 0
87
+ if b.zero?
69
88
  [0xDB, 0xDC]
70
89
  elsif b == 0xDB
71
90
  [0xDB, 0xDD]
@@ -75,6 +94,17 @@ module TEF
75
94
  end.flatten
76
95
  end
77
96
 
97
+ # @private
98
+ # Internal function to append/prepend the neccessary framing
99
+ # to be a FurComs message.
100
+ # This framing is:
101
+ # - 0x00 START
102
+ # - priority and chip_id fields
103
+ # - 0xFF latency
104
+ # - 24-bit arbitration collission map.
105
+ # - 0xFF latency
106
+ # - Modified SLIP-Encoded message data
107
+ # - 0x00 STOP
78
108
  private def generate_furcom_message(priority, chip_id, data_str)
79
109
  priority = ([[-60, priority].max, 60].min + 64) * 2 + 1;
80
110
  chip_id = 0x1 | 0x100 | ((chip_id & 0xEF) << 9) | ((chip_id >> 6) & 0xEF);
@@ -85,12 +115,13 @@ module TEF
85
115
  out_data
86
116
  end
87
117
 
118
+ # (see Base#send_message)
88
119
  def send_message(topic, message, priority: 0, chip_id: 0)
89
120
  unless topic =~ /^[\w\s\/]*$/
90
- raise ArgumentError, "Topic includes invalid characters!"
121
+ raise ArgumentError, 'Topic includes invalid characters!'
91
122
  end
92
123
  if (topic.length + message.length) > 250
93
- raise ArgumentError, "Message packet length exceeded!"
124
+ raise ArgumentError, 'Message packet length exceeded!'
94
125
  end
95
126
 
96
127
  x_logd("Sending '#{topic}': '#{message}'")
@@ -98,7 +129,7 @@ module TEF
98
129
  escaped_str = slip_encode_data "#{topic}\0#{message}"
99
130
  out_data = generate_furcom_message priority, chip_id, escaped_str;
100
131
 
101
- @port.write(out_data.pack("C2S<C*"))
132
+ @port.write(out_data.pack('C2S<C*'))
102
133
 
103
134
  @port.flush
104
135
  end
@@ -0,0 +1,58 @@
1
+
2
+ require_relative 'serial.rb'
3
+
4
+ module TEF
5
+ module FurComs
6
+ # FurComs SerialToMQTT Bridge.
7
+ #
8
+ # This class will extend {FurComs::Serial} with a few internal functions
9
+ # to bridge the FurComs bus connection to MQTT.
10
+ #
11
+ # @note This class suffers from the same restrictions as the {Serial} class.
12
+ # Use a different PC to FurComs connection if possible!
13
+ #
14
+ # @see Serial
15
+ class SerialToMQTT < Serial
16
+ # Initialize a Serial To MQTT bridge instance.
17
+ #
18
+ # This will open the given Serial port and begin reading/writing
19
+ # onto the FurComs bus.
20
+ # It will additionally subscribe to the MQTT 'topic',
21
+ # or 'FurComs/#{topic}/Send/#' using the /dev/DEVICE part of the port
22
+ # argument if topic is left nil.
23
+ # Any message received on this topic will be sent as FurComs messages.
24
+ #
25
+ # Received messages are first handed out to internal callbacks,
26
+ # and will then be sent off to 'FurComs/#{topic}/Received/#{msg_topic}'
27
+ #
28
+ # @note This class can not provide full arbitration handling. This may
29
+ # cause issues in busy bus conditions!
30
+ # @param port [String] Device port to use for the FurComs connection
31
+ # @param mqtt MQTT Handler class. Must support subscribe_to
32
+ # and publish_to
33
+ # @param topic [nil, String] MQTT topic to use for receiving and sending.
34
+ # must end with a '/'
35
+ def initialize(port, mqtt, topic = nil)
36
+ super(port);
37
+
38
+ @mqtt = mqtt;
39
+ @mqtt_topic = topic || "FurComs/#{port.sub('/dev/', '')}/";
40
+
41
+ @mqtt.subscribe_to @mqtt_topic + 'Send/#' do |data, msg_topic|
42
+ msg_topic = msg_topic.join('/')
43
+
44
+ next if data.length + msg_topic.length > 250
45
+ next unless msg_topic =~ /^[\w\s\/]*$/
46
+
47
+ send_message msg_topic, data
48
+ end
49
+ end
50
+
51
+ private def handout_data(topic, data)
52
+ super(topic, data);
53
+
54
+ @mqtt.publish_to @mqtt_topic + "Received/#{topic}", data
55
+ end
56
+ end
57
+ end
58
+ end
metadata CHANGED
@@ -1,46 +1,46 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tef-furcoms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - TheSystem
8
- - Xasin
8
+ - Neira
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
  date: 2020-05-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: serialport
15
+ name: mqtt-sub_handler
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '1.3'
20
+ version: '0.1'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '1.3'
27
+ version: '0.1'
28
28
  - !ruby/object:Gem::Dependency
29
- name: xasin-logger
29
+ name: serialport
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '0.1'
34
+ version: '1.3'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '0.1'
41
+ version: '1.3'
42
42
  - !ruby/object:Gem::Dependency
43
- name: mqtt-sub_handler
43
+ name: xasin-logger
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - "~>"
@@ -75,11 +75,11 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - README.md
77
77
  - lib/tef/furcoms.rb
78
- - lib/tef/furcoms/Base.rb
79
- - lib/tef/furcoms/MQTT.rb
80
- - lib/tef/furcoms/Serial.rb
81
- - lib/tef/furcoms/SerialToMQTT.rb
82
- homepage:
78
+ - lib/tef/furcoms/base.rb
79
+ - lib/tef/furcoms/mqtt.rb
80
+ - lib/tef/furcoms/serial.rb
81
+ - lib/tef/furcoms/serial_to_mqtt.rb
82
+ homepage: https://github.com/TheElectricFursuits/tef-FurComs
83
83
  licenses:
84
84
  - GPL-3.0
85
85
  metadata: {}
@@ -1,48 +0,0 @@
1
-
2
-
3
- module TEF
4
- module FurComs
5
- class Message
6
- attr_reader :priority
7
- attr_reader :source_id
8
-
9
- attr_reader :topic
10
- attr_reader :data
11
-
12
- def initialize()
13
- end
14
- end
15
-
16
- class Base
17
- def initialize()
18
- @message_procs = [];
19
- end
20
-
21
- def send_message(topic, message, priority: 0, chip_id: 0) end
22
-
23
- def on_message(topic_filter = nil, &block)
24
- @message_procs << { topic: topic_filter, block: block };
25
- end
26
-
27
- private def handout_data(topic, data)
28
- x_logd("Data received on #{topic}: #{data}")
29
-
30
- @message_procs.each do |callback|
31
- begin
32
- if filter = callback[:topic]
33
- if filter.is_a? String
34
- next unless topic == filter
35
- elsif filter.is_a? RegExp
36
- next unless filter.match(topic)
37
- end
38
- end
39
-
40
- callback[:block].call(data, topic)
41
- rescue => e
42
- x_logf("Error in callback #{callback[:block]}: #{e}");
43
- end
44
- end
45
- end
46
- end
47
- end
48
- end
@@ -1,41 +0,0 @@
1
-
2
- require_relative 'Base.rb'
3
-
4
- require 'xasin_logger'
5
-
6
- module TEF
7
- module FurComs
8
- class MQTT < Base
9
- include XasLogger::Mix
10
-
11
- def initialize(mqtt, topic = 'FurComs/ttyACM0/')
12
- super();
13
-
14
- @mqtt = mqtt;
15
- @mqtt_topic = topic;
16
-
17
- @mqtt.subscribe_to topic + 'Received/#' do |data, topic|
18
- topic = topic.join('/')
19
- next unless topic =~ /^[\w\s\/]*$/
20
-
21
- handout_data(topic, data);
22
- end
23
-
24
- init_x_log(@mqtt_topic)
25
- end
26
-
27
- def send_message(topic, data, priority: 0, chip_id: 0)
28
- unless topic =~ /^[\w\s\/]*$/
29
- raise ArgumentError, "Topic includes invalid characters!"
30
- end
31
- if (topic.length + data.length) > 250
32
- raise ArgumentError, "Message packet length exceeded!"
33
- end
34
-
35
- x_logd("Sending '#{topic}': '#{data}'")
36
-
37
- @mqtt.publish_to @mqtt_topic + "Send/#{topic}", data
38
- end
39
- end
40
- end
41
- end
@@ -1,30 +0,0 @@
1
-
2
- require_relative 'Serial.rb'
3
-
4
- module TEF
5
- module FurComs
6
- class SerialToMQTT < Serial
7
- def initialize(port, mqtt, topic = nil)
8
- super(port);
9
-
10
- @mqtt = mqtt;
11
- @mqtt_topic = topic || "FurComs/#{port.sub('/dev/', '')}/";
12
-
13
- @mqtt.subscribe_to @mqtt_topic + 'Send/#' do |data, topic|
14
- topic = topic.join('/')
15
-
16
- next if data.length + topic.length > 250
17
- next unless topic =~ /^[\w\s\/]*$/
18
-
19
- self.send_message topic, data
20
- end
21
- end
22
-
23
- def handout_data(topic, data)
24
- super(topic, data);
25
-
26
- @mqtt.publish_to @mqtt_topic + "Received/#{topic}", data
27
- end
28
- end
29
- end
30
- end