mqttopia 0.1.9

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6dcdb52227984d43667ef297e1740a96329a28931140f2c2760a6446cf5ee31a
4
+ data.tar.gz: 8d1ba1e116657ae18027c2686599476b859600de42fec69c1e938f23a6c89f05
5
+ SHA512:
6
+ metadata.gz: b8ae4365a041299e9e4e4e2e43c75cdb4ca5cf8406fc451e0e95a47ccc834ccf21998c76f909c8bb924922588b0eaecedaaa7689ba9ad8881b3d8d3fa0ff6823
7
+ data.tar.gz: 2bd8bef90f21419e6fdf594eb4c81ad920bb11eac3bbfd74c081280b9d0e866bb32802cda0b90f0b71804521127195af9e6b0e0ca36035093c917e6fd6b7fef6
data/.rubocop.yml ADDED
@@ -0,0 +1,39 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.7
3
+ Include:
4
+ - 'app/**/*.rb'
5
+ - 'lib/**/*.rb'
6
+ Exclude:
7
+ - 'bin/**/*'
8
+ - 'test/**/*'
9
+
10
+ Bundler:
11
+ Severity: error
12
+ Gemspec:
13
+ Severity: error
14
+ Style/StringLiterals:
15
+ Enabled: true
16
+ EnforcedStyle: double_quotes
17
+
18
+ Style/StringLiteralsInInterpolation:
19
+ Enabled: true
20
+ EnforcedStyle: double_quotes
21
+
22
+ Style/Documentation:
23
+ Enabled: false
24
+
25
+ Style/ClassVars:
26
+ Enabled: false
27
+
28
+ Layout/LineLength:
29
+ Max: 120
30
+ Metrics/ClassLength:
31
+ Max: 120
32
+ Metrics/MethodLength:
33
+ Max: 30
34
+ Metrics/AbcSize:
35
+ Enabled: false
36
+ Lint/MissingSuper:
37
+ Enabled: false
38
+ Lint/RescueException:
39
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-09-22
4
+
5
+ - Initial release
@@ -0,0 +1,135 @@
1
+
2
+ # MQTT Subscriptions
3
+
4
+ ## 1. **`trip_metrics` Subscription**
5
+
6
+ This subscription handles trip metrics and serializes the data related to trips, such as GPS location, battery status, and connection types.
7
+
8
+ ### **Purpose:**
9
+ - To monitor the metrics of a trip in real-time, including the trip's ID, GPS data, battery level, location permissions, and notification permissions.
10
+
11
+ ### **Payload Structure:**
12
+ ```json
13
+ {
14
+ "trip_id": 123,
15
+ "metrics": {
16
+ "locPerms": 0,
17
+ "connt": "wf",
18
+ "batt": { "l": 100, "cs": "charging" },
19
+ "battPwOp": 0,
20
+ "pws": 0,
21
+ "notf": 1,
22
+ "gps": 1,
23
+ "nlog": 500,
24
+ "loc": {
25
+ "lat": 29.9585182,
26
+ "lon": 31.0423193,
27
+ "acc": 20,
28
+ "alt": 150.5,
29
+ "ts": 1729074275127 // unix timestamp
30
+ },
31
+ "ts": 1729074279737 // unix timestamp
32
+ }
33
+ }
34
+ ```
35
+
36
+ ### **Serialized Output:**
37
+ ```ruby
38
+ {
39
+ trip_id: 123,
40
+ metrics: {
41
+ location_permission: "BACKGROUND_FINE",
42
+ connection_type: "wf",
43
+ battery: {
44
+ battery_level: 100,
45
+ charging_statuc: "charging",
46
+ opmization_status: 0,
47
+ power_saving_mode: 0
48
+ },
49
+ notification_permission: 1,
50
+ gps: 1,
51
+ location: {
52
+ latitude: 29.9585182,
53
+ longitude: 31.0423193,
54
+ accuracy: 20,
55
+ altitude: 150.5,
56
+ logged_at: 1729074275127 # unix timestamp
57
+ }
58
+ },
59
+ logged_at: 1729074275127, # unix timestamp
60
+ received_at: 1729074275127 # unix timestamp
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ### **Date Types:**
67
+
68
+ - To decode a unix timestamp use the following link [epoch_converter](https://www.epochconverter.com/)
69
+
70
+ ---
71
+
72
+ ### **Usage Example:**
73
+ ```ruby
74
+ $client.subscribe('illa/trips/20/trip_metric/send/user/20') do |response|
75
+ puts "Received trip metrics: #{response}"
76
+ end
77
+
78
+ payload = {
79
+ "trip_id": 123,
80
+ "metrics": {
81
+ "locPerms": 0,
82
+ "connt": "wf",
83
+ "batt": { "l": 100, "cs": "charging" },
84
+ "battPwOp": 0,
85
+ "pws": 0,
86
+ "notf": 1,
87
+ "gps": 1,
88
+ "nlog": 500,
89
+ "loc": {
90
+ "lat": 29.9585182,
91
+ "lon": 31.0423193,
92
+ "acc": 20,
93
+ "alt": 150.5,
94
+ "ts": 1729074275127
95
+ },
96
+ "ts": 1729074279737
97
+ }
98
+ }
99
+ $client.publish('illa/trips/20/trip_metric/send/user/20', payload)
100
+ ```
101
+
102
+ ---
103
+
104
+ ## 2. **`test_debug` Subscription**
105
+
106
+ The `test_debug` subscription is used for testing and debugging purposes, capturing payloads sent on specific MQTT topics.
107
+
108
+ ### **Purpose:**
109
+ - To log and serialize MQTT messages for debugging purposes, capturing both the topic name and payload.
110
+
111
+ ### **Payload Structure:**
112
+ ```json
113
+ {
114
+ "topic_name": "test/topic",
115
+ "payload": { "message": "Hello, MQTT" }
116
+ }
117
+ ```
118
+
119
+ ### **Serialized Output:**
120
+ ```ruby
121
+ {
122
+ topic_name: "test/topic",
123
+ payload: { message: "Hello, MQTT" },
124
+ received_at: 1729074275127 # unix timestamp
125
+ }
126
+ ```
127
+
128
+ ### **Usage Example:**
129
+ ```ruby
130
+ $client.subscribe('test/#') do |response|
131
+ puts "Received debug message: #{response}"
132
+ end
133
+
134
+ $client.publish('test/topic', 'Hello, MQTT')
135
+ ```
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+
2
+ # Mqttopia
3
+
4
+ Mqttopia is a Ruby-based project designed to facilitate interactions with MQTT protocols. It provides a modular architecture for handling MQTT clients, subscriptions, and data serialization, making it easier to integrate MQTT into your applications.
5
+
6
+ ---
7
+
8
+ ## **Features**
9
+
10
+ - Configurable MQTT client with options for hosts, port, SSL, and authentication.
11
+ - Subscription handling for dynamic topics.
12
+ - Data serialization modules to standardize payloads.
13
+ - Custom Rake tasks for initializing configuration files.
14
+
15
+ ---
16
+
17
+ ## **Installation**
18
+
19
+ ### Bundler
20
+
21
+ ```ruby
22
+ gem 'mqttopia', '~> 0.1.0'
23
+ ```
24
+
25
+ ### Manual
26
+
27
+ ```sh
28
+ gem install mqttopia
29
+ ```
30
+
31
+ ---
32
+
33
+ ## **Usage**
34
+
35
+ ### Configuring Mqttopia
36
+
37
+ You can create or update the `mqttopia.rb` file using the included Rake task:
38
+
39
+ ```bash
40
+ bundle exec rails generate mqttopia:install
41
+ ```
42
+
43
+ The generated `mqttopia.rb` will contain:
44
+
45
+ ```ruby
46
+ Mqttopia.configure do |config|
47
+ config.hosts = ["localhost"]
48
+ config.port = 1883
49
+ config.username = "user"
50
+ config.password = "xxxx"
51
+ config.ssl = true
52
+ end
53
+ ```
54
+
55
+ ### Example: Subscribing and Publishing Messages
56
+
57
+ ```ruby
58
+ client = Mqttopia::Client.instance
59
+
60
+ # Subscribe to a topic
61
+ client.subscribe('test/#') do |response|
62
+ puts "Received: #{response}"
63
+ end
64
+
65
+ # Publish a message to a topic
66
+ client.publish('test/topic', 'Hello, MQTT')
67
+ ```
68
+
69
+ ### MQTT subscriptions
70
+
71
+ - [MQTT subscriptions](https://github.com/go-illa/mqttopia/blob/master/MQTT_SUBSCRIPTIONS.md)
72
+
73
+ ---
74
+
75
+ ## **Topic Name Structure**
76
+
77
+ - **send data** : `illa/{entity}/{entity_id}/{operation}/send/user/{user_id}`
78
+ - **ask data** : `illa/{entity}/{entity_id}/{operation}/ask/user/{user_id}`
79
+ - **operation** : `trip_metrics`, `test_debug`, ..etc.
80
+ - **entity**: `trips`
81
+ - `user/{user_id}` is optional
82
+
83
+ ---
84
+
85
+ ## **Rake Tasks**
86
+
87
+ ### Available Tasks
88
+
89
+ - **Initialize Configuration File:**
90
+
91
+ Creates `mqttopia_initialize.rb` with default configurations.
92
+
93
+ ```bash
94
+ bundle exec rake mqttopia:initialize
95
+ ```
96
+
97
+ ---
98
+
99
+ ## **Project Structure**
100
+
101
+ ```
102
+ mqttopia/
103
+ ├── lib/
104
+ │ ├── mqttopia/ # Modules for mqttopia gem
105
+ │ | ├── helpers/ # Helper Modules
106
+ │ | ├── serializers/ # Modules for payload serialization
107
+ │ | ├── subscriptions/ # MQTT Topics's events and services
108
+ │ | └── topics/ # MQTT Topic constants [name, regex, service, serializer]
109
+ │ |── tasks/ # Rake tasks
110
+ │ ├── mqttopia.rb # Main entry point for the gem
111
+ │ ├── client.rb # MQTT client implementation
112
+ │ └── logger.rb # Logging setup
113
+ └── test/ # Minitest-based test suite
114
+ ```
115
+
116
+ ---
117
+
118
+ ## **Contributing**
119
+
120
+ 1. Fork the repository.
121
+ 2. Create a feature branch: `git checkout -b feature-branch-name`
122
+ 3. Commit your changes: `git commit -m "Add new feature"`
123
+ 4. Push the branch: `git push origin feature-branch-name`
124
+ 5. Open a Pull Request.
125
+
126
+ ---
127
+
128
+ ## **License**
129
+
130
+ This project is licensed under the MIT License. See the `LICENSE` file for more information.
131
+
132
+ ---
133
+
134
+ ## **Contact**
135
+
136
+ If you have any questions or need further assistance, feel free to open an issue or contact the maintainer.
137
+
138
+ ---
139
+
140
+ ## **Acknowledgements**
141
+
142
+ Special thanks to all contributors and users who have made this project possible.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.test_files = FileList['test/**/*_test.rb'] # Make sure all test files are loaded
9
+ # t.verbose = true # This will give more output during the test run
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
data/TEST_README.md ADDED
@@ -0,0 +1,57 @@
1
+ # TEST
2
+
3
+ ## Installation
4
+
5
+ ```sh
6
+ gem install mqttopia
7
+ ```
8
+
9
+ ## Testing `test_mqttopia.rb`
10
+
11
+ 1. create a new `test_mqttopia.rb` file
12
+ ```sh
13
+ touch test_mqttopia.rb
14
+ ```
15
+
16
+ 2. file structure and testing scenarios
17
+ ```ruby
18
+ # test_mqttopia.rb
19
+
20
+ # Require the gem's main file
21
+ require 'mqttopia'
22
+
23
+ # Initialize Mqttopia, using your mqtt server credentials
24
+ Mqttopia.configure do |config|
25
+ config.hosts = ["localhost"]
26
+ config.port = 1883
27
+ config.username = 'user'
28
+ config.password = 'xxxx'
29
+ config.ssl = true
30
+ config.debugging = true
31
+ end
32
+
33
+ begin
34
+ # Create a client instance (connect to mqtt server)
35
+ client = Mqttopia::Client.instance
36
+
37
+ # Subscribe to a topic and print received messages
38
+ client.subscribe('test/#') do |response|
39
+ puts "Received PROCESSED message: #{response}"
40
+ end
41
+ rescue StandardError => e
42
+ puts "MQTT CLIENT #{e}"
43
+ end
44
+
45
+ # Publish a message to the topic
46
+ client.publish('test/topic', 'Hello, MQTT')
47
+
48
+ puts 'Listening for new MQTT messages. Press Ctrl+C to exit.'
49
+
50
+ # Keep the script alive to listen indefinitely
51
+ sleep
52
+ ```
53
+
54
+ 3. Run the file
55
+ ```sh
56
+ ruby test_mqttopia.rb
57
+ ```
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module Mqttopia
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("../templates", __dir__)
9
+
10
+ desc "Creates a Mqttopia initializer file for configuration."
11
+
12
+ # Method to generate the initializer
13
+ def copy_initializer
14
+ template "mqttopia_initializer.rb", "config/initializers/mqttopia.rb"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # config/initializers/mqttopia.rb
4
+
5
+ Mqttopia.configure do |config|
6
+ # Set the MQTT hosts
7
+ config.hosts = ["localhost"]
8
+
9
+ # Set the MQTT port
10
+ config.port = 1883
11
+
12
+ # Set the MQTT username
13
+ config.username = "user"
14
+
15
+ # Set the MQTT password
16
+ config.password = "xxxx"
17
+
18
+ # Set the MQTT ssl
19
+ config.ssl = true
20
+
21
+ # Set the MQTT debugging mode
22
+ # config.debugging = true
23
+
24
+ # Set the logger (defaults to STDOUT)
25
+ # config.logger = Logger.new(STDOUT)
26
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mqtt"
4
+ require "json"
5
+ require "socket"
6
+ require_relative "subscriptions/redirect"
7
+
8
+ module Mqttopia
9
+ class Client
10
+ @instance = nil
11
+ attr_reader :mqtt_client, :debugging
12
+
13
+ def self.instance
14
+ @instance ||= new
15
+ end
16
+
17
+ private_class_method :new
18
+
19
+ def initialize(
20
+ username: Mqttopia.username,
21
+ password: Mqttopia.password,
22
+ port: Mqttopia.port,
23
+ ssl: Mqttopia.ssl
24
+ )
25
+ @mqtt_client = MQTT::Client.new(
26
+ port: port,
27
+ username: username,
28
+ password: password,
29
+ ssl: ssl,
30
+ keep_alive: 15,
31
+ ack_timeout: 5
32
+ )
33
+ @debugging = Mqttopia.debugging
34
+ rescue Exception => e
35
+ log_error("initialize", e)
36
+ end
37
+
38
+ def connect
39
+ host = active_host(Mqttopia.hosts)
40
+ raise ArgumentError, "No hosts available" if host.nil?
41
+
42
+ mqtt_client.host = host
43
+ mqtt_client.connect
44
+ rescue Exception => e
45
+ disconnect_and_log("connect", e)
46
+ end
47
+
48
+ def publish(topic, message = nil, qos = 0)
49
+ return unless ensure_connection
50
+
51
+ # publish(topic, payload = '', retain = false, qos = 0)
52
+ mqtt_client.publish(topic, message, false, qos)
53
+ log_debug("Sent message on topic #{topic}: #{message} with QOS #{qos}")
54
+ rescue Exception => e
55
+ disconnect_and_log("publish", e)
56
+ end
57
+
58
+ def subscribe(topic, qos = 0, &block)
59
+ return unless ensure_connection
60
+
61
+ mqtt_client.subscribe(topic => qos)
62
+ create_subscription_thread(qos, &block)
63
+ rescue Exception => e
64
+ disconnect_and_log("subscribe", e)
65
+ end
66
+
67
+ def disconnect
68
+ mqtt_client.disconnect if connected?
69
+ end
70
+
71
+ private
72
+
73
+ def active_host(hosts = Mqttopia.hosts, port = Mqttopia.port, timeout = 10)
74
+ hosts.each do |host|
75
+ Socket.tcp(host, port, connect_timeout: timeout) do |sock|
76
+ sock.close
77
+ return host # Return the first reachable host
78
+ end
79
+ rescue SocketError, Errno::ECONNREFUSED, Errno::ETIMEDOUT, IOError
80
+ next # Ignore unreachable hosts and continue checking
81
+ end
82
+ nil # Return nil if no reachable host is found
83
+ end
84
+
85
+ def create_subscription_thread(qos, &block)
86
+ Thread.new do
87
+ mqtt_client.get do |topic, message|
88
+ log_debug("Received RAW message on topic #{topic}: #{message} with QOS #{qos}")
89
+
90
+ response = safe_mqtt_response(topic, message)
91
+ block.call(response) if response
92
+ end
93
+ end
94
+ end
95
+
96
+ def safe_mqtt_response(topic_name, raw_payload)
97
+ Mqttopia::Subscriptions::Redirect.new(
98
+ topic_name: topic_name,
99
+ payload: parse_json(raw_payload)
100
+ ).call
101
+ rescue Exception => e
102
+ log_error("safe_mqtt_response", e)
103
+ nil
104
+ end
105
+
106
+ def parse_json(payload)
107
+ JSON.parse(payload, symbolize_names: true)
108
+ rescue JSON::ParserError
109
+ payload
110
+ end
111
+
112
+ def ensure_connection
113
+ connected? || connect || connected?
114
+ end
115
+
116
+ def connected?
117
+ mqtt_client.connected?
118
+ end
119
+
120
+ def log_error(method, error)
121
+ Mqttopia::Logger.error("\nMqttopia::Client -> #{method}: #{error_details(error)}\n")
122
+ end
123
+
124
+ def log_debug(message)
125
+ Mqttopia::Logger.debug("\n#{message}\n") if debugging
126
+ end
127
+
128
+ def disconnect_and_log(method, error)
129
+ disconnect
130
+ log_error(method, error)
131
+ end
132
+
133
+ def error_details(error)
134
+ {
135
+ type: error.class.name&.gsub("Mqttopia::Subscriptions::Services::", ""),
136
+ message: error.message
137
+ }
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mqttopia
4
+ module Helpers
5
+ module DataExtractor
6
+ def extract_trip_id(topic_name)
7
+ return unless topic_name.include?("trips/")
8
+
9
+ match_data = topic_name.match(%r{trips/([^/]*)})
10
+
11
+ match_data && !match_data[1].empty? ? match_data[1] : nil
12
+ end
13
+
14
+ def extract_user_id(topic_name)
15
+ return unless topic_name.include?("user/")
16
+
17
+ match_data = topic_name.match(%r{user/([^/]*)})
18
+
19
+ match_data && !match_data[1].empty? ? match_data[1] : nil
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/delegation" # Require ActiveSupport's delegation
4
+
5
+ module Mqttopia
6
+ class Logger
7
+ class << self
8
+ # Initialize the logger
9
+ def logger
10
+ @logger ||= Mqttopia.logger # Default to logging to STDOUT
11
+ end
12
+
13
+ # Allow users to set a custom logger (e.g., logging to a file)
14
+ attr_writer :logger
15
+
16
+ # Simple wrapper for logging debug messages
17
+ delegate :debug, to: :logger
18
+
19
+ # Wrapper for logging info messages
20
+ delegate :info, to: :logger
21
+
22
+ # Wrapper for logging warn messages
23
+ delegate :warn, to: :logger
24
+
25
+ # Wrapper for logging error messages
26
+ delegate :error, to: :logger
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mqttopia
4
+ module Serializers
5
+ module TestDebug
6
+ module_function
7
+
8
+ def serialize(body)
9
+ {
10
+ topic_name: body[:topic_name],
11
+ payload: body[:payload],
12
+ received_at: DateTime.now.strftime("%Q")&.to_s
13
+ }
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mqttopia
4
+ module Serializers
5
+ module TripMetric
6
+ module_function
7
+
8
+ def serialize(body)
9
+ {
10
+ trip_id: body[:trip_id],
11
+ metrics: body[:metrics] && metric_serialize(body[:metrics]),
12
+ logged_at: body[:metrics] && body[:metrics][:ts]&.to_s,
13
+ received_at: DateTime.now.strftime("%Q")&.to_s
14
+ }
15
+ end
16
+
17
+ def metric_serialize(metric)
18
+ {
19
+ location_permission: location_permission_mapper(metric[:locPerms]),
20
+ connection_type: metric[:connt],
21
+ battery: {
22
+ battery_level: metric[:batt] && metric[:batt][:l],
23
+ charging_statuc: metric[:batt] && metric[:batt][:cs],
24
+ opmization_status: metric[:battPwOp],
25
+ power_saving_mode: metric[:pws]
26
+ },
27
+ notification_permission: metric[:notf],
28
+ gps: metric[:gps],
29
+ number_of_trip_logs: metric[:nlog],
30
+ location: metric[:loc] && {
31
+ latitude: metric[:loc][:lat],
32
+ longitude: metric[:loc][:lon],
33
+ accuracy: metric[:loc][:acc],
34
+ altitude: metric[:loc][:alt],
35
+ logged_at: metric[:loc][:ts]&.to_s
36
+ }
37
+ }
38
+ end
39
+
40
+ def location_permission_mapper(location_permission)
41
+ case location_permission
42
+ when 0
43
+ "BACKGROUND_FINE"
44
+ when -1
45
+ "BACKGROUND_COARSE"
46
+ when -2
47
+ "FOREGROUND_FINE"
48
+ when -3
49
+ "FOREGROUND_COARSE"
50
+ when -4
51
+ "DISABLED"
52
+ else
53
+ "UNKNOWN"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "services/trip_metrics"
4
+ require_relative "services/test_debug"
5
+ require_relative "../topics/services"
6
+
7
+ module Mqttopia
8
+ module Subscriptions
9
+ class Redirect
10
+ attr_reader :topic_name, :payload, :debugging
11
+
12
+ def initialize(topic_name:, payload:)
13
+ @topic_name = topic_name
14
+ @payload = payload
15
+ @debugging = Mqttopia.debugging
16
+ end
17
+
18
+ def call
19
+ Mqttopia::Topics::Services::KEYS.each_value do |value|
20
+ next unless topic_name.match(value[:regex])
21
+
22
+ return value[:service].safe_constantize.call(
23
+ topic_name,
24
+ payload,
25
+ { serializer: value[:serializer] }
26
+ )
27
+ end
28
+
29
+ Mqttopia::Logger.warn "No matching topic found for #{topic_name}" if debugging
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mqttopia
4
+ module Subscriptions
5
+ module Services
6
+ class Base
7
+ include ActiveModel::Validations
8
+ extend Mqttopia::Helpers::DataExtractor
9
+
10
+ def self.call(topic, payload, _options = {})
11
+ raise NotImplementedError
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model"
4
+ require_relative "../../serializers/test_debug"
5
+ require_relative "./base"
6
+
7
+ module Mqttopia
8
+ module Subscriptions
9
+ module Services
10
+ class TestDebug < Mqttopia::Subscriptions::Services::Base
11
+ class TestDebugInvalidError < ArgumentError
12
+ end
13
+
14
+ attr_reader :topic_name, :payload, :options
15
+
16
+ validates :topic_name, presence: true
17
+ validates :payload, presence: true
18
+
19
+ def initialize(topic_name:, payload:, options: {})
20
+ @topic_name = topic_name
21
+ @payload = payload
22
+ @options = options
23
+ end
24
+
25
+ def self.call(topic, payload, options = {})
26
+ new(topic_name: topic, payload: payload, options: options).process
27
+ end
28
+
29
+ def process
30
+ raise TestDebugInvalidError, errors.full_messages.to_sentence unless valid?
31
+
32
+ Mqttopia::Serializers::TestDebug.serialize({
33
+ topic_name: topic_name,
34
+ payload: payload
35
+ })
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model"
4
+ require_relative "../../serializers/trip_metric"
5
+ require_relative "../../helpers/data_extractor"
6
+ require_relative "./base"
7
+
8
+ module Mqttopia
9
+ module Subscriptions
10
+ module Services
11
+ class TripMetrics < Mqttopia::Subscriptions::Services::Base
12
+ class TripMetricsInvalidError < ArgumentError
13
+ end
14
+
15
+ attr_reader :trip_id, :metrics, :options, :user_id
16
+
17
+ validates :trip_id, presence: true, numericality: { only_integer: true, greater_than: 0 }
18
+ validates :metrics, presence: true
19
+
20
+ def initialize(trip_id:, metrics:, options: {})
21
+ @trip_id = trip_id
22
+ @metrics = metrics
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, metrics: payload, options: options).process
32
+ end
33
+
34
+ def process
35
+ raise TripMetricsInvalidError, errors.full_messages.to_sentence unless valid?
36
+
37
+ Mqttopia::Serializers::TripMetric.serialize({
38
+ trip_id: trip_id,
39
+ metrics: metrics
40
+ })
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mqttopia
4
+ module Topics
5
+ class Services
6
+ KEYS = {
7
+ trip_metric: {
8
+ topic_name: "illa/trips/:trip_id/trip_metric/send/user/:user_id",
9
+ service: "Mqttopia::Subscriptions::Services::TripMetrics",
10
+ serializer: "Mqttopia::Serializers::TripMetric",
11
+ regex: %r{\Ailla/trips/(?<trip_id>\d+)/trip_metric/send(?:/user/(?<user_id>\d+))?\z}
12
+ },
13
+ test_debug: {
14
+ topic_name: "test",
15
+ service: "Mqttopia::Subscriptions::Services::TestDebug",
16
+ serializer: "Mqttopia::Serializers::TripMetric",
17
+ regex: %r{test/}
18
+ }
19
+ }.freeze
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mqttopia
4
+ VERSION = "0.1.9"
5
+ end
data/lib/mqttopia.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ require_relative "mqttopia/version"
6
+ require_relative "mqttopia/client"
7
+ require_relative "mqttopia/logger"
8
+
9
+ # Load rake tasks
10
+ Dir[File.expand_path("tasks/**/*.rake", __dir__)].each { |task| load task }
11
+
12
+ # Base module for Mqttopia Gem
13
+ module Mqttopia
14
+ class Error < StandardError; end
15
+
16
+ mattr_accessor :hosts, :port, :username, :password, :ssl, :logger, :debugging
17
+
18
+ @@port = 1883
19
+ @@ssl = true
20
+ @@logger = ::Logger.new($stdout)
21
+ @@debugging = false
22
+
23
+ def self.configure
24
+ yield self
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ # lib/tasks/initialize.rake
2
+ require 'rake'
3
+
4
+ namespace :mqttopia do
5
+ desc "Create mqttopia_initialize.rb with default configuration"
6
+ task :initialize do
7
+ file_name = 'mqttopia_initialize.rb'
8
+ content = <<~RUBY
9
+ Mqttopia.configure do |config|
10
+ # Set the MQTT host
11
+ config.hosts = ["localhost"]
12
+
13
+ # Set the MQTT port
14
+ config.port = 1883
15
+
16
+ # Set the MQTT username
17
+ config.username = "user"
18
+
19
+ # Set the MQTT password
20
+ config.password = "xxxx"
21
+
22
+ # Set the MQTT ssl
23
+ config.ssl = true
24
+
25
+ # Set the MQTT debugging mode
26
+ # config.debugging = true
27
+
28
+ # Set the logger (defaults to STDOUT)
29
+ # config.logger = Logger.new(STDOUT)
30
+ end
31
+
32
+ # $client = Mqttopia::Client.instance
33
+ # $client.subscribe('test/#') { |res| puts res }
34
+ # $client.publish('test/topic', 'Hello, MQTT')
35
+ RUBY
36
+
37
+ if File.exist?(file_name)
38
+ puts "Skipping: #{file_name} already exists"
39
+ else
40
+ File.write(file_name, content)
41
+ puts "Created: #{file_name}"
42
+ end
43
+ end
44
+ end
data/mqttopia.gemspec ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/mqttopia/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "mqttopia"
7
+ spec.version = Mqttopia::VERSION
8
+ spec.authors = ["Illa Tech"]
9
+ spec.email = ["tech@illa.com.eg"]
10
+
11
+ spec.summary = "Mqttopia"
12
+ spec.description = "Mqttopia description"
13
+ spec.homepage = "https://github.com/go-illa"
14
+ spec.required_ruby_version = ">= 2.7.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/go-illa/mqttopia"
18
+ spec.metadata["changelog_uri"] = "https://github.com/go-illa/mqttopia"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (File.expand_path(f) == __FILE__) ||
25
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ # Uncomment to register a new dependency of your gem
33
+ spec.add_dependency "activemodel", ">= 4.1"
34
+ spec.add_dependency "json", ">= 1.6"
35
+ spec.add_dependency "logger", ">= 1.4"
36
+ spec.add_dependency "mqtt", ">= 0.6"
37
+ spec.add_dependency "railties", ">= 4.2"
38
+ spec.add_dependency "rake", ">= 11.0"
39
+
40
+ spec.add_development_dependency "brakeman", "~> 6.0"
41
+ spec.add_development_dependency "fasterer", "~> 0.10"
42
+ spec.add_development_dependency "minitest", "~> 5.0"
43
+ spec.add_development_dependency "mocha", "~> 2.0"
44
+ spec.add_development_dependency "yard", "~> 0.9"
45
+
46
+ spec.post_install_message = '
47
+ [MQTTOPIA] Please make sure to run `rails generate mqttopia:install`
48
+ '
49
+ end
data/sig/mqttopia.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Mqttopia
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,223 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mqttopia
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.9
5
+ platform: ruby
6
+ authors:
7
+ - Illa Tech
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-10-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: logger
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mqtt
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: railties
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '4.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '4.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '11.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '11.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: brakeman
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '6.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '6.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: fasterer
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.10'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.10'
125
+ - !ruby/object:Gem::Dependency
126
+ name: minitest
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '5.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '5.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: mocha
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: yard
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.9'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.9'
167
+ description: Mqttopia description
168
+ email:
169
+ - tech@illa.com.eg
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - ".rubocop.yml"
175
+ - CHANGELOG.md
176
+ - MQTT_SUBSCRIPTIONS.md
177
+ - README.md
178
+ - Rakefile
179
+ - TEST_README.md
180
+ - lib/generators/mqttopia/install_generator.rb
181
+ - lib/generators/templates/mqttopia_initializer.rb
182
+ - lib/mqttopia.rb
183
+ - lib/mqttopia/client.rb
184
+ - lib/mqttopia/helpers/data_extractor.rb
185
+ - lib/mqttopia/logger.rb
186
+ - lib/mqttopia/serializers/test_debug.rb
187
+ - lib/mqttopia/serializers/trip_metric.rb
188
+ - lib/mqttopia/subscriptions/redirect.rb
189
+ - lib/mqttopia/subscriptions/services/base.rb
190
+ - lib/mqttopia/subscriptions/services/test_debug.rb
191
+ - lib/mqttopia/subscriptions/services/trip_metrics.rb
192
+ - lib/mqttopia/topics/services.rb
193
+ - lib/mqttopia/version.rb
194
+ - lib/tasks/initialize.rake
195
+ - mqttopia.gemspec
196
+ - sig/mqttopia.rbs
197
+ homepage: https://github.com/go-illa
198
+ licenses: []
199
+ metadata:
200
+ homepage_uri: https://github.com/go-illa
201
+ source_code_uri: https://github.com/go-illa/mqttopia
202
+ changelog_uri: https://github.com/go-illa/mqttopia
203
+ post_install_message: "\n [MQTTOPIA] Please make sure to run `rails generate
204
+ mqttopia:install`\n "
205
+ rdoc_options: []
206
+ require_paths:
207
+ - lib
208
+ required_ruby_version: !ruby/object:Gem::Requirement
209
+ requirements:
210
+ - - ">="
211
+ - !ruby/object:Gem::Version
212
+ version: 2.7.0
213
+ required_rubygems_version: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - ">="
216
+ - !ruby/object:Gem::Version
217
+ version: '0'
218
+ requirements: []
219
+ rubygems_version: 3.5.19
220
+ signing_key:
221
+ specification_version: 4
222
+ summary: Mqttopia
223
+ test_files: []