mqttopia 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
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: []