mqttopia 0.1.25 → 0.2.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 +4 -4
- data/.gemrc +8 -0
- data/.rubocop.yml +5 -1
- data/lib/mqttopia/client.rb +93 -26
- data/lib/mqttopia/serializers/trip_point.rb +48 -0
- data/lib/mqttopia/subscriptions/redirect.rb +11 -7
- data/lib/mqttopia/subscriptions/services/base.rb +2 -0
- data/lib/mqttopia/subscriptions/services/trip_metrics.rb +0 -1
- data/lib/mqttopia/subscriptions/services/trip_points.rb +45 -0
- data/lib/mqttopia/topics/services.rb +6 -0
- data/lib/mqttopia/version.rb +1 -1
- data/lib/mqttopia.rb +6 -4
- data/mqttopia.gemspec +1 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c742f5f768390150ffadb94a8c47dc197f95e02c0ff137ba0fee384fbd8f8313
|
4
|
+
data.tar.gz: a8da45694aef218efa74d141979869c264252fba7a1c8fe736728933971c70c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62e6988922837919ecb0943033ceff222a43439fa7041569c76ecb83f6c26fa73c11d601b8bd288e2dd886bb0c20aac5a69ea7cb1a9afd8354f5580010297c5c
|
7
|
+
data.tar.gz: 7b9c96b9748df2ad82e2b17967070dc96afc21e2f9821b0c7ab64da4230328fd50aeefb9e9205c82f3c76497793612b0ce4b3678759cfd3f2b313ba61da17412
|
data/.gemrc
ADDED
data/.rubocop.yml
CHANGED
@@ -28,7 +28,7 @@ Style/ClassVars:
|
|
28
28
|
Layout/LineLength:
|
29
29
|
Max: 120
|
30
30
|
Metrics/ClassLength:
|
31
|
-
Max:
|
31
|
+
Max: 180
|
32
32
|
Metrics/MethodLength:
|
33
33
|
Max: 30
|
34
34
|
Metrics/AbcSize:
|
@@ -38,4 +38,8 @@ Lint/MissingSuper:
|
|
38
38
|
Lint/RescueException:
|
39
39
|
Enabled: false
|
40
40
|
Style/RedundantFetchBlock:
|
41
|
+
Enabled: false
|
42
|
+
Metrics/CyclomaticComplexity:
|
43
|
+
Enabled: false
|
44
|
+
Metrics/PerceivedComplexity:
|
41
45
|
Enabled: false
|
data/lib/mqttopia/client.rb
CHANGED
@@ -7,11 +7,14 @@ require_relative "subscriptions/redirect"
|
|
7
7
|
|
8
8
|
module Mqttopia
|
9
9
|
class Client
|
10
|
-
@
|
10
|
+
@instance_mutex = Mutex.new
|
11
|
+
|
11
12
|
attr_reader :mqtt_client, :debugging, :debugging_topic
|
12
13
|
|
13
14
|
def self.instance
|
14
|
-
@
|
15
|
+
@instance_mutex.synchronize do
|
16
|
+
@instance ||= new
|
17
|
+
end
|
15
18
|
end
|
16
19
|
|
17
20
|
private_class_method :new
|
@@ -28,6 +31,7 @@ module Mqttopia
|
|
28
31
|
username: username,
|
29
32
|
password: password,
|
30
33
|
ssl: ssl,
|
34
|
+
client_id: SecureRandom.hex(3) + "-#{Mqttopia.client_id}",
|
31
35
|
keep_alive: Mqttopia.keep_alive,
|
32
36
|
will_topic: Mqttopia.will_topic,
|
33
37
|
will_retain: Mqttopia.will_retain,
|
@@ -35,20 +39,38 @@ module Mqttopia
|
|
35
39
|
will_qos: Mqttopia.will_qos,
|
36
40
|
ack_timeout: Mqttopia.ack_timeout
|
37
41
|
)
|
42
|
+
@subscriptions = {}
|
43
|
+
@subscription_thread_mutex ||= Mutex.new
|
38
44
|
@debugging = Mqttopia.debugging
|
39
45
|
@debugging_topic = Mqttopia.debugging_topic
|
40
|
-
rescue
|
46
|
+
rescue StandardError => e
|
41
47
|
log_error("initialize", e)
|
42
48
|
end
|
43
49
|
|
44
50
|
def connect
|
45
|
-
|
46
|
-
|
51
|
+
retry_count = 0
|
52
|
+
max_retries = 15
|
53
|
+
|
54
|
+
loop do
|
55
|
+
host = active_host(Mqttopia.hosts)
|
56
|
+
raise ArgumentError, "No hosts available" if host.nil?
|
57
|
+
|
58
|
+
mqtt_client.host = host
|
59
|
+
mqtt_client.connect
|
60
|
+
|
61
|
+
retry_count = 0 # Reset retry count on successful connection
|
62
|
+
break # Exit the loop once connected
|
63
|
+
rescue StandardError => e
|
64
|
+
retry_count += 1
|
65
|
+
disconnect_and_log("connect", e)
|
66
|
+
|
67
|
+
if retry_count >= max_retries
|
68
|
+
log_error("connect", "Terminating connection attempts after #{max_retries} failures")
|
69
|
+
break # Stop retrying after max retries
|
70
|
+
end
|
47
71
|
|
48
|
-
|
49
|
-
|
50
|
-
rescue Exception => e
|
51
|
-
disconnect_and_log("connect", e)
|
72
|
+
sleep(2 * retry_count) # Delay before retrying to prevent rapid retries
|
73
|
+
end
|
52
74
|
end
|
53
75
|
|
54
76
|
def publish(topic, message = nil, qos = 0)
|
@@ -56,27 +78,32 @@ module Mqttopia
|
|
56
78
|
end
|
57
79
|
|
58
80
|
def self.publish(topic, message = nil, qos = 0)
|
81
|
+
# Publishes a message to a topic.
|
82
|
+
#
|
59
83
|
client = new
|
84
|
+
# @param message [String, NilClass] Message to publish, defaults to nil
|
85
|
+
# @param qos [Integer] Quality of service, defaults to 0
|
86
|
+
#
|
87
|
+
# @return [Boolean] True if publish succeeded, false otherwise
|
60
88
|
client.connect
|
61
89
|
|
62
|
-
# publish(topic, payload = '', retain = false, qos = 0)
|
63
90
|
client.mqtt_client.publish(topic, message, false, qos)
|
64
91
|
client.disconnect
|
65
92
|
|
66
93
|
Mqttopia::Logger.debug("\n#{message}\n") if client.debugging
|
67
94
|
true
|
68
|
-
rescue
|
95
|
+
rescue StandardError => e
|
69
96
|
Mqttopia::Logger.error("\nMqttopia::Client -> publish: #{e}\n")
|
70
97
|
false
|
71
98
|
end
|
72
99
|
|
73
|
-
def subscribe(topic, qos = 0, &block)
|
100
|
+
def subscribe(topic, listener_event = nil, qos = 0, &block)
|
74
101
|
return unless ensure_connection
|
75
102
|
raise StandardError, "Blocked Topic" if [debugging_topic].include?(topic)
|
76
103
|
|
77
104
|
mqtt_client.subscribe(topic => qos)
|
78
|
-
|
79
|
-
rescue
|
105
|
+
register_subscription(listener_event, &block)
|
106
|
+
rescue StandardError => e
|
80
107
|
disconnect_and_log("subscribe", e)
|
81
108
|
end
|
82
109
|
|
@@ -104,28 +131,65 @@ module Mqttopia
|
|
104
131
|
nil # Return nil if no reachable host is found
|
105
132
|
end
|
106
133
|
|
107
|
-
def
|
108
|
-
|
109
|
-
mqtt_client.get do |topic, message|
|
110
|
-
response = safe_mqtt_response(topic, message)
|
134
|
+
def register_subscription(listener_event = nil, &block)
|
135
|
+
return unless block_given?
|
111
136
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
137
|
+
@subscriptions[listener_event] = block # Store callback for this topic
|
138
|
+
|
139
|
+
start_subscription_thread unless @subscription_thread
|
140
|
+
end
|
141
|
+
|
142
|
+
def start_subscription_thread
|
143
|
+
@subscription_thread_mutex.synchronize do
|
144
|
+
return if @subscription_thread
|
145
|
+
|
146
|
+
@subscription_thread ||= Thread.new do
|
147
|
+
retry_count = 0
|
148
|
+
max_retries = 15
|
117
149
|
|
118
|
-
|
150
|
+
loop do
|
151
|
+
mqtt_client.get do |topic, message|
|
152
|
+
handle_subscription_message(topic, message)
|
153
|
+
end
|
154
|
+
|
155
|
+
retry_count = 0 # Reset retry count i
|
156
|
+
f successful
|
157
|
+
rescue StandardError => e
|
158
|
+
retry_count += 1
|
159
|
+
log_error("subscription_thread", e)
|
160
|
+
|
161
|
+
if retry_count >= max_retries
|
162
|
+
log_error("subscription_thread", "Terminating after #{max_retries} failed attempts")
|
163
|
+
break # Exit the loop and terminate the thread
|
164
|
+
end
|
165
|
+
|
166
|
+
sleep(2 * retry_count) # Delay before retrying
|
167
|
+
retry # Restart loop if under max retries
|
168
|
+
end
|
119
169
|
end
|
120
170
|
end
|
121
171
|
end
|
122
172
|
|
173
|
+
def handle_subscription_message(topic, message)
|
174
|
+
response = safe_mqtt_response(topic, message)
|
175
|
+
|
176
|
+
if debugging && topic.exclude?(debugging_topic)
|
177
|
+
Mqttopia::Client.publish(debugging_topic, { 'topic_name': topic, 'mqttopia_response': response })
|
178
|
+
end
|
179
|
+
|
180
|
+
return unless response.is_a?(Hash) && response[:event]
|
181
|
+
|
182
|
+
@subscriptions.each do |subscribed_topic, callback|
|
183
|
+
callback.call(response[:data]) if subscribed_topic.to_s == response[:event]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
123
187
|
def safe_mqtt_response(topic_name, raw_payload)
|
124
|
-
Mqttopia::Subscriptions::Redirect.new(
|
188
|
+
::Mqttopia::Subscriptions::Redirect.new(
|
125
189
|
topic_name: topic_name,
|
126
190
|
payload: parse_json(raw_payload)
|
127
191
|
).call
|
128
|
-
rescue
|
192
|
+
rescue StandardError => e
|
129
193
|
log_error("safe_mqtt_response", e) if debugging
|
130
194
|
nil
|
131
195
|
end
|
@@ -149,6 +213,9 @@ module Mqttopia
|
|
149
213
|
end
|
150
214
|
|
151
215
|
def disconnect_and_log(method, error)
|
216
|
+
@subscription_thread_mutex.synchronize do
|
217
|
+
Thread.kill(@subscription_thread) if @subscription_thread
|
218
|
+
end
|
152
219
|
disconnect
|
153
220
|
log_error(method, error)
|
154
221
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mqttopia
|
4
|
+
module Serializers
|
5
|
+
module TripPoint
|
6
|
+
module_function
|
7
|
+
|
8
|
+
KEYS = %i[lat lon ts ctp_id trip_id].freeze
|
9
|
+
|
10
|
+
def serialize(body)
|
11
|
+
return unless valid_point?(body[:point])
|
12
|
+
|
13
|
+
{
|
14
|
+
trip_id: body[:trip_id],
|
15
|
+
point: serialize_point(body[:point]),
|
16
|
+
logged_at: body.dig(:point, :ts)&.to_s,
|
17
|
+
received_at: current_timestamp
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def serialize_point(point)
|
22
|
+
{
|
23
|
+
latitude: point[:lat],
|
24
|
+
longitude: point[:lon],
|
25
|
+
accuracy: point[:acc],
|
26
|
+
altitude: point[:alt],
|
27
|
+
action_type: action_type_mapper(point[:act]),
|
28
|
+
current_trip_point_id: point[:ctp_id]
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid_point?(point)
|
33
|
+
point.is_a?(Hash) && KEYS.all? { |key| point.key?(key) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def current_timestamp
|
37
|
+
DateTime.now.strftime("%Q")
|
38
|
+
end
|
39
|
+
|
40
|
+
def action_type_mapper(action_type)
|
41
|
+
{
|
42
|
+
"0" => "arrival",
|
43
|
+
"1" => "departure"
|
44
|
+
}.fetch(action_type&.to_s) { nil }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative "services/trip_metrics"
|
4
4
|
require_relative "services/test_debug"
|
5
|
+
require_relative "services/trip_points"
|
5
6
|
require_relative "../topics/services"
|
6
7
|
|
7
8
|
module Mqttopia
|
@@ -16,14 +17,17 @@ module Mqttopia
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def call
|
19
|
-
Mqttopia::Topics::Services::KEYS.
|
20
|
-
next unless topic_name.match(value[:regex])
|
20
|
+
Mqttopia::Topics::Services::KEYS.each do |key, value|
|
21
|
+
next unless topic_name.match?(value[:regex])
|
21
22
|
|
22
|
-
return
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
return {
|
24
|
+
event: key&.to_s,
|
25
|
+
data: value[:service].safe_constantize.call(
|
26
|
+
topic_name,
|
27
|
+
payload,
|
28
|
+
{ serializer: value[:serializer] }
|
29
|
+
)
|
30
|
+
}
|
27
31
|
end
|
28
32
|
|
29
33
|
Mqttopia::Logger.warn "No matching topic found for #{topic_name}" if debugging
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model"
|
4
|
+
require_relative "../../serializers/trip_point"
|
5
|
+
require_relative "./base"
|
6
|
+
|
7
|
+
module Mqttopia
|
8
|
+
module Subscriptions
|
9
|
+
module Services
|
10
|
+
class TripPoints < Mqttopia::Subscriptions::Services::Base
|
11
|
+
class TripPointsInvalidError < ArgumentError
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :trip_id, :point, :options, :user_id
|
15
|
+
|
16
|
+
validates :trip_id, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
17
|
+
validates :point, presence: true
|
18
|
+
# validate :point_json_format
|
19
|
+
|
20
|
+
def initialize(trip_id:, point:, options: {})
|
21
|
+
@trip_id = trip_id
|
22
|
+
@point = point
|
23
|
+
@options = options
|
24
|
+
@user_id = options[:user_id]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.call(topic, payload, options = {})
|
28
|
+
trip_id = topic.include?("trips/") && topic.match(%r{trips/[^/]+}) && extract_trip_id(topic)
|
29
|
+
options[:user_id] = topic.include?("user/") && topic.match(%r{user/[^/]+}) && extract_user_id(topic)
|
30
|
+
|
31
|
+
new(trip_id: trip_id, point: payload, options: options).process
|
32
|
+
end
|
33
|
+
|
34
|
+
def process
|
35
|
+
raise TripPointsInvalidError, errors.full_messages.to_sentence unless valid?
|
36
|
+
|
37
|
+
Mqttopia::Serializers::TripPoint.serialize({
|
38
|
+
trip_id: trip_id,
|
39
|
+
point: point
|
40
|
+
})
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -10,6 +10,12 @@ module Mqttopia
|
|
10
10
|
serializer: "Mqttopia::Serializers::TripMetric",
|
11
11
|
regex: %r{\Ailla/trips/(?<trip_id>\d+)/trip_metric/send(?:/user/(?<user_id>\d+))?\z}
|
12
12
|
},
|
13
|
+
trip_point: {
|
14
|
+
topic_name: "illa/trips/:trip_id/driver_trip_point/send/user/:user_id",
|
15
|
+
service: "Mqttopia::Subscriptions::Services::TripPoints",
|
16
|
+
serializer: "Mqttopia::Serializers::TripPoint",
|
17
|
+
regex: %r{\Ailla/trips/(?<trip_id>\d+)/driver_trip_point/send(?:/user/(?<user_id>\d+))?\z}
|
18
|
+
},
|
13
19
|
test_debug: {
|
14
20
|
topic_name: "test",
|
15
21
|
service: "Mqttopia::Subscriptions::Services::TestDebug",
|
data/lib/mqttopia/version.rb
CHANGED
data/lib/mqttopia.rb
CHANGED
@@ -27,22 +27,24 @@ module Mqttopia
|
|
27
27
|
:will_payload,
|
28
28
|
:will_qos,
|
29
29
|
:ack_timeout,
|
30
|
+
:client_id,
|
30
31
|
:debugging,
|
31
32
|
:debugging_topic
|
32
33
|
|
33
34
|
@@version = "3.1.1"
|
34
35
|
@@port = 1883
|
35
36
|
@@ssl = true
|
36
|
-
@@will_topic =
|
37
|
-
@@will_retain =
|
38
|
-
@@will_payload =
|
37
|
+
@@will_topic = nil
|
38
|
+
@@will_retain = false
|
39
|
+
@@will_payload = nil
|
39
40
|
@@will_qos = 0
|
40
|
-
@@keep_alive =
|
41
|
+
@@keep_alive = 15
|
41
42
|
@@ack_timeout = 10
|
42
43
|
@@logger = ::Logger.new($stdout)
|
43
44
|
@@logger_level = :info
|
44
45
|
@@debugging = false
|
45
46
|
@@debugging_topic = "mqttopia/test/debugging"
|
47
|
+
@@client_id = "mqttopia-#{Mqttopia::VERSION}"
|
46
48
|
|
47
49
|
def self.configure
|
48
50
|
yield self
|
data/mqttopia.gemspec
CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
17
17
|
spec.metadata["source_code_uri"] = "https://github.com/go-illa/mqttopia"
|
18
18
|
spec.metadata["changelog_uri"] = "https://github.com/go-illa/mqttopia"
|
19
|
+
spec.metadata["github_repo"] = "https://github.com/go-illa/mqttopia"
|
19
20
|
|
20
21
|
# Specify which files should be added to the gem when it is released.
|
21
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mqttopia
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Illa Tech
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -171,6 +171,7 @@ executables: []
|
|
171
171
|
extensions: []
|
172
172
|
extra_rdoc_files: []
|
173
173
|
files:
|
174
|
+
- ".gemrc"
|
174
175
|
- ".rubocop.yml"
|
175
176
|
- CHANGELOG.md
|
176
177
|
- MQTT_SUBSCRIPTIONS.md
|
@@ -185,10 +186,12 @@ files:
|
|
185
186
|
- lib/mqttopia/logger.rb
|
186
187
|
- lib/mqttopia/serializers/test_debug.rb
|
187
188
|
- lib/mqttopia/serializers/trip_metric.rb
|
189
|
+
- lib/mqttopia/serializers/trip_point.rb
|
188
190
|
- lib/mqttopia/subscriptions/redirect.rb
|
189
191
|
- lib/mqttopia/subscriptions/services/base.rb
|
190
192
|
- lib/mqttopia/subscriptions/services/test_debug.rb
|
191
193
|
- lib/mqttopia/subscriptions/services/trip_metrics.rb
|
194
|
+
- lib/mqttopia/subscriptions/services/trip_points.rb
|
192
195
|
- lib/mqttopia/topics/services.rb
|
193
196
|
- lib/mqttopia/version.rb
|
194
197
|
- lib/tasks/initialize.rake
|
@@ -200,6 +203,7 @@ metadata:
|
|
200
203
|
homepage_uri: https://github.com/go-illa
|
201
204
|
source_code_uri: https://github.com/go-illa/mqttopia
|
202
205
|
changelog_uri: https://github.com/go-illa/mqttopia
|
206
|
+
github_repo: https://github.com/go-illa/mqttopia
|
203
207
|
post_install_message: "\n [MQTTOPIA] Please make sure to run `rails generate
|
204
208
|
mqttopia:install`\n "
|
205
209
|
rdoc_options: []
|