tef-furcoms 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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