dji_mqtt_connect 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop/rspec.yml +55 -0
  4. data/.rubocop/strict.yml +7 -0
  5. data/.rubocop/style.yml +6 -0
  6. data/.rubocop.yml +20 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +24 -0
  9. data/Gemfile.lock +133 -0
  10. data/README.md +31 -0
  11. data/Rakefile +12 -0
  12. data/lib/dji_mqtt_connect/client.rb +51 -0
  13. data/lib/dji_mqtt_connect/error.rb +45 -0
  14. data/lib/dji_mqtt_connect/factories.rb +49 -0
  15. data/lib/dji_mqtt_connect/marshals/sys/product/status_marshal.rb +57 -0
  16. data/lib/dji_mqtt_connect/marshals/sys/product/status_reply_marshal.rb +34 -0
  17. data/lib/dji_mqtt_connect/marshals/thing/product/osd_marshal.rb +69 -0
  18. data/lib/dji_mqtt_connect/message.rb +13 -0
  19. data/lib/dji_mqtt_connect/message_marshal.rb +19 -0
  20. data/lib/dji_mqtt_connect/messages/sys/product/status_message.rb +20 -0
  21. data/lib/dji_mqtt_connect/messages/sys/product/update_topo_status_message.rb +25 -0
  22. data/lib/dji_mqtt_connect/messages/sys/product/update_topo_status_reply_message.rb +30 -0
  23. data/lib/dji_mqtt_connect/messages/thing/product/drone_osd_message.rb +68 -0
  24. data/lib/dji_mqtt_connect/messages/thing/product/osd_message.rb +17 -0
  25. data/lib/dji_mqtt_connect/messages/thing/product/remote_osd_message.rb +45 -0
  26. data/lib/dji_mqtt_connect/railtie.rb +13 -0
  27. data/lib/dji_mqtt_connect/railties/message_serializer.rb +32 -0
  28. data/lib/dji_mqtt_connect/topic_repository.rb +41 -0
  29. data/lib/dji_mqtt_connect/topics/sys/product/status.rb +30 -0
  30. data/lib/dji_mqtt_connect/topics/sys/product/status_reply.rb +16 -0
  31. data/lib/dji_mqtt_connect/topics/thing/product/osd.rb +27 -0
  32. data/lib/dji_mqtt_connect/types.rb +23 -0
  33. data/lib/dji_mqtt_connect/version.rb +5 -0
  34. data/lib/dji_mqtt_connect.rb +57 -0
  35. data/sig/dji_mqtt_connect.rbs +4 -0
  36. metadata +155 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 286e565c906b968d00df2865b04ab56f7edad331f603f059900d5d2ba0884e5f
4
+ data.tar.gz: 1f1e18bf29b660f9d68a7bc2b3a888a967782ee284872eab52af3c40cc780230
5
+ SHA512:
6
+ metadata.gz: 31e6ff2f90df68713a207cb3e70b8cb9c7aed4a4294ee70b3867b8f4a37390eb3d91931b0f533bac6361e2adac5dc307e3cb8544da762b9a9934949258f03c98
7
+ data.tar.gz: 55f3e73e77128f8f6c97f6fcf9425975eb11497ede3c55db6df57e06dd95e1713aa2c7bdf2adf76766e9d949c2c7133f8d9dc00106bf5a1922e5a4c77e1e0d90
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,55 @@
1
+ require:
2
+ - rubocop-rspec
3
+
4
+ # Disable all cops by default,
5
+ # only enable those defined explcitly in this configuration file
6
+ RSpec:
7
+ Enabled: false
8
+
9
+ RSpec/Focus:
10
+ Enabled: true
11
+
12
+ RSpec/EmptyExampleGroup:
13
+ Enabled: true
14
+
15
+ RSpec/EmptyLineAfterExampleGroup:
16
+ Enabled: true
17
+
18
+ RSpec/EmptyLineAfterFinalLet:
19
+ Enabled: true
20
+
21
+ RSpec/EmptyLineAfterHook:
22
+ Enabled: true
23
+
24
+ RSpec/EmptyLineAfterSubject:
25
+ Enabled: true
26
+
27
+ RSpec/HookArgument:
28
+ Enabled: true
29
+
30
+ RSpec/HooksBeforeExamples:
31
+ Enabled: true
32
+
33
+ RSpec/ImplicitExpect:
34
+ Enabled: true
35
+
36
+ RSpec/IteratedExpectation:
37
+ Enabled: true
38
+
39
+ RSpec/LetBeforeExamples:
40
+ Enabled: true
41
+
42
+ RSpec/MissingExampleGroupArgument:
43
+ Enabled: true
44
+
45
+ RSpec/ReceiveCounts:
46
+ Enabled: true
47
+
48
+ Capybara/CurrentPathExpectation:
49
+ Enabled: true
50
+
51
+ RSpec/FactoryBot/AttributeDefinedStatically:
52
+ Enabled: true
53
+
54
+ RSpec/FactoryBot/CreateList:
55
+ Enabled: true
@@ -0,0 +1,7 @@
1
+ Lint/Debugger: # don't leave binding.pry
2
+ Enabled: true
3
+ Exclude: []
4
+
5
+ RSpec/Focus: # run ALL tests on CI
6
+ Enabled: true
7
+ Exclude: []
@@ -0,0 +1,6 @@
1
+ Style/FrozenStringLiteralComment: # Use frozen literal comments for performance
2
+ Enabled: true
3
+ EnforcedStyle: always
4
+ Include:
5
+ - app/**/*
6
+ - lib/**/*
data/.rubocop.yml ADDED
@@ -0,0 +1,20 @@
1
+ inherit_mode:
2
+ merge:
3
+ - Exclude
4
+
5
+ require:
6
+ - rubocop-rake
7
+ - standard
8
+
9
+ inherit_gem:
10
+ standard: config/base.yml
11
+
12
+ inherit_from:
13
+ - .rubocop/style.yml
14
+ - .rubocop/rspec.yml
15
+ - .rubocop/strict.yml
16
+
17
+ AllCops:
18
+ NewCops: disable
19
+ SuggestExtensions: true
20
+ TargetRubyVersion: 3.1
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-04-14
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in dji_mqtt_connect.gemspec
6
+ gemspec
7
+
8
+ gem "activejob", require: false
9
+
10
+ gem "rake", "~> 13.0"
11
+
12
+ gem "rspec", "~> 3.0"
13
+
14
+ gem "rubocop", "~> 1.21"
15
+
16
+ gem "rubocop-rake"
17
+
18
+ gem "rubocop-rspec"
19
+
20
+ gem "simplecov", require: false
21
+
22
+ gem "standard", "~> 1.0"
23
+
24
+ gem "wisper-rspec"
data/Gemfile.lock ADDED
@@ -0,0 +1,133 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dji_mqtt_connect (0.1.0)
5
+ activesupport (>= 6.0, <= 8)
6
+ dry-struct (~> 1.6)
7
+ dry-transformer (~> 1.0)
8
+ mqtt (~> 0.6.0)
9
+ wisper (~> 2.0)
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ activejob (7.0.4.3)
15
+ activesupport (= 7.0.4.3)
16
+ globalid (>= 0.3.6)
17
+ activesupport (7.0.4.3)
18
+ concurrent-ruby (~> 1.0, >= 1.0.2)
19
+ i18n (>= 1.6, < 2)
20
+ minitest (>= 5.1)
21
+ tzinfo (~> 2.0)
22
+ ast (2.4.2)
23
+ concurrent-ruby (1.2.2)
24
+ diff-lcs (1.5.0)
25
+ docile (1.4.0)
26
+ dry-core (1.0.0)
27
+ concurrent-ruby (~> 1.0)
28
+ zeitwerk (~> 2.6)
29
+ dry-inflector (1.0.0)
30
+ dry-logic (1.5.0)
31
+ concurrent-ruby (~> 1.0)
32
+ dry-core (~> 1.0, < 2)
33
+ zeitwerk (~> 2.6)
34
+ dry-struct (1.6.0)
35
+ dry-core (~> 1.0, < 2)
36
+ dry-types (>= 1.7, < 2)
37
+ ice_nine (~> 0.11)
38
+ zeitwerk (~> 2.6)
39
+ dry-transformer (1.0.1)
40
+ zeitwerk (~> 2.6)
41
+ dry-types (1.7.1)
42
+ concurrent-ruby (~> 1.0)
43
+ dry-core (~> 1.0)
44
+ dry-inflector (~> 1.0)
45
+ dry-logic (~> 1.4)
46
+ zeitwerk (~> 2.6)
47
+ globalid (1.1.0)
48
+ activesupport (>= 5.0)
49
+ i18n (1.12.0)
50
+ concurrent-ruby (~> 1.0)
51
+ ice_nine (0.11.2)
52
+ json (2.6.3)
53
+ language_server-protocol (3.17.0.3)
54
+ minitest (5.18.0)
55
+ mqtt (0.6.0)
56
+ parallel (1.23.0)
57
+ parser (3.2.2.0)
58
+ ast (~> 2.4.1)
59
+ rainbow (3.1.1)
60
+ rake (13.0.6)
61
+ regexp_parser (2.8.0)
62
+ rexml (3.2.5)
63
+ rspec (3.12.0)
64
+ rspec-core (~> 3.12.0)
65
+ rspec-expectations (~> 3.12.0)
66
+ rspec-mocks (~> 3.12.0)
67
+ rspec-core (3.12.2)
68
+ rspec-support (~> 3.12.0)
69
+ rspec-expectations (3.12.2)
70
+ diff-lcs (>= 1.2.0, < 2.0)
71
+ rspec-support (~> 3.12.0)
72
+ rspec-mocks (3.12.5)
73
+ diff-lcs (>= 1.2.0, < 2.0)
74
+ rspec-support (~> 3.12.0)
75
+ rspec-support (3.12.0)
76
+ rubocop (1.50.2)
77
+ json (~> 2.3)
78
+ parallel (~> 1.10)
79
+ parser (>= 3.2.0.0)
80
+ rainbow (>= 2.2.2, < 4.0)
81
+ regexp_parser (>= 1.8, < 3.0)
82
+ rexml (>= 3.2.5, < 4.0)
83
+ rubocop-ast (>= 1.28.0, < 2.0)
84
+ ruby-progressbar (~> 1.7)
85
+ unicode-display_width (>= 2.4.0, < 3.0)
86
+ rubocop-ast (1.28.0)
87
+ parser (>= 3.2.1.0)
88
+ rubocop-capybara (2.17.1)
89
+ rubocop (~> 1.41)
90
+ rubocop-performance (1.16.0)
91
+ rubocop (>= 1.7.0, < 2.0)
92
+ rubocop-ast (>= 0.4.0)
93
+ rubocop-rake (0.6.0)
94
+ rubocop (~> 1.0)
95
+ rubocop-rspec (2.20.0)
96
+ rubocop (~> 1.33)
97
+ rubocop-capybara (~> 2.17)
98
+ ruby-progressbar (1.13.0)
99
+ simplecov (0.22.0)
100
+ docile (~> 1.1)
101
+ simplecov-html (~> 0.11)
102
+ simplecov_json_formatter (~> 0.1)
103
+ simplecov-html (0.12.3)
104
+ simplecov_json_formatter (0.1.4)
105
+ standard (1.27.0)
106
+ language_server-protocol (~> 3.17.0.2)
107
+ rubocop (~> 1.50.2)
108
+ rubocop-performance (~> 1.16.0)
109
+ tzinfo (2.0.6)
110
+ concurrent-ruby (~> 1.0)
111
+ unicode-display_width (2.4.2)
112
+ wisper (2.0.1)
113
+ wisper-rspec (1.1.0)
114
+ zeitwerk (2.6.7)
115
+
116
+ PLATFORMS
117
+ x86_64-darwin-21
118
+ x86_64-linux
119
+
120
+ DEPENDENCIES
121
+ activejob
122
+ dji_mqtt_connect!
123
+ rake (~> 13.0)
124
+ rspec (~> 3.0)
125
+ rubocop (~> 1.21)
126
+ rubocop-rake
127
+ rubocop-rspec
128
+ simplecov
129
+ standard (~> 1.0)
130
+ wisper-rspec
131
+
132
+ BUNDLED WITH
133
+ 2.4.6
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # DjiMqttConnect
2
+
3
+ Manages MQTT Connections with DJI Drones
4
+
5
+ See also: https://developer.dji.com/doc/cloud-api-tutorial/en/server-api-reference/mqtt/topic-definition.html
6
+
7
+ ## Installation
8
+
9
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Development
24
+
25
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
+
27
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sphere-drones/dji_mqtt_connect.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mqtt"
4
+
5
+ module DjiMqttConnect
6
+ # Client for communicating with DJI Cloud API MQTT Messages
7
+ # see: https://developer.dji.com/doc/cloud-api-tutorial/en/server-api-reference/mqtt/topic-definition.html
8
+ class Client
9
+ def self.build(mqtt_address, username:, password:, &block)
10
+ mqtt_url = URI.parse(mqtt_address)
11
+ mqtt_host = mqtt_url.host
12
+ mqtt_port = mqtt_url.port.to_i
13
+ mqtt_scheme = mqtt_url.scheme
14
+
15
+ DjiMqttConnect.logger.tagged(name) do
16
+ DjiMqttConnect.logger.info("Connecting to #{mqtt_scheme}://#{mqtt_host}:#{mqtt_port}...")
17
+
18
+ mqtt_client = MQTT::Client.connect(
19
+ host: mqtt_host,
20
+ port: mqtt_port,
21
+ client_id: DjiMqttConnect.client_id,
22
+ username: username,
23
+ password: password,
24
+ ssl: !%w[tcp mqtt ws].include?(mqtt_scheme)
25
+ )
26
+ new(mqtt_client)
27
+ end
28
+ end
29
+
30
+ def initialize(mqtt_client)
31
+ @mqtt_client = mqtt_client
32
+ end
33
+
34
+ # Handles topic sys/product/#{pid}/status
35
+ def sys_product_status_topic
36
+ Sys::Product::StatusTopicRepository.new(self)
37
+ end
38
+
39
+ # Handles topic sys/product/#{pid}/status_reply
40
+ def sys_product_status_reply_topic
41
+ Sys::Product::StatusReplyTopicRepository.new(self)
42
+ end
43
+
44
+ # Handles topic thing/product/#{pid}/osd
45
+ def thing_product_osd_topic
46
+ Thing::Product::OsdTopicRepository.new(self)
47
+ end
48
+
49
+ attr_reader :mqtt_client
50
+ end
51
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ # Represents an Error caused when communicatng via MQTT
5
+ class Error < StandardError
6
+ attr_reader :wrapped_exception
7
+
8
+ def initialize(error, message = nil)
9
+ @wrapped_exception, msg = error_and_message(error, message)
10
+ super(msg)
11
+ end
12
+
13
+ def backtrace
14
+ if @wrapped_exception
15
+ @wrapped_exception.backtrace
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def inspect
22
+ if @wrapped_exception
23
+ inner = +""
24
+ inner += ": #{message}," if !message.empty? && @wrapped_exception.message != message
25
+ inner << " wrapped=#{@wrapped_exception.inspect}"
26
+ %(#<#{self.class}#{inner}>)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # Pulls out potential parent exception and response hash.
35
+ def error_and_message(exc, message)
36
+ return [exc, message || exc.message] if exc.respond_to?(:backtrace)
37
+
38
+ [nil, exc.to_s]
39
+ end
40
+ end
41
+
42
+ # Indicates a problem parsing a response
43
+ class ParseError < Error
44
+ end
45
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ # Factories for building example messages
5
+ # Do not include this in your production code
6
+ module Factories
7
+ def build_device_online_update_topo_message
8
+ message_data = {
9
+ type: 98,
10
+ sub_type: 0,
11
+ device_secret: "secret",
12
+ nonce: "nonce",
13
+ version: 1,
14
+ sub_devices: [
15
+ {
16
+ sn: "drone001",
17
+ type: 116,
18
+ sub_type: 0,
19
+ index: "A",
20
+ device_secret: "secret",
21
+ nonce: "nonce",
22
+ version: 1
23
+ }
24
+ ]
25
+ }
26
+
27
+ Sys::Product::UpdateTopoStatusMessage.new(
28
+ _method: "update_topo",
29
+ tid: "12345678-1234-1234-1234-A00000000001",
30
+ bid: "12345678-1234-1234-1234-B00000000001",
31
+ timestamp: 1234567890123,
32
+ data: message_data,
33
+ _data: message_data.deep_stringify_keys
34
+ )
35
+ end
36
+
37
+ def build_update_topo_status_reply_message
38
+ Sys::Product::UpdateTopoStatusReplyMessage.new(
39
+ _method: "update_topo",
40
+ tid: "3093aad5-bb98-5026-685a-cea540eb9267",
41
+ bid: "479533e0-cb01-933c-3758-22f2a82abeb8",
42
+ timestamp: 1679380233000,
43
+ data: {
44
+ result: 0
45
+ }
46
+ )
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-transformer"
4
+ require "json"
5
+
6
+ module DjiMqttConnect
7
+ module Sys::Product
8
+ class StatusMarshal < MessageMarshal
9
+ # Rename pesky `method` argument to `_method` and makes a copy of the raw data
10
+ class AttributeTransformer < Dry::Transformer::Pipe
11
+ import Dry::Transformer::HashTransformations
12
+
13
+ define! do
14
+ symbolize_keys
15
+
16
+ copy_keys data: :_data
17
+ rename_keys method: :_method
18
+ end
19
+ end
20
+
21
+ # Attempts to look a the method attribute, and builds a specific Message class for the message
22
+ def load(raw_message)
23
+ # Parse the message from JSON
24
+ parsed_message = JSON.parse(raw_message)
25
+
26
+ # Transform the message
27
+ transformed_message = attribute_transformer.call(parsed_message)
28
+
29
+ # Build an instance of the class, or a generic message from the current class
30
+ message_class = message_class_for_parsed_message(parsed_message)
31
+ message_class.new transformed_message
32
+ rescue JSON::ParserError => e
33
+ raise ParseError.new(e, "Unable to parse message as JSON")
34
+ rescue Dry::Struct::Error => e
35
+ raise ParseError.new(e, "Unexpected #{message_class} payload")
36
+ end
37
+
38
+ private
39
+
40
+ def attribute_transformer
41
+ @attribute_transformer ||= AttributeTransformer.new
42
+ end
43
+
44
+ def message_class_for_parsed_message(parsed_message)
45
+ # update_topo => UpdateTopo
46
+ classified_method = ActiveSupport::Inflector.classify(parsed_message["method"])
47
+
48
+ # UpdateTopo => DjiMqttConnect::Sys::Product::UpdateTopoStatusMessage
49
+ module_prefix, joiner, class_suffix = StatusMessage.name.rpartition("::")
50
+ message_class_name = [module_prefix, joiner, classified_method, class_suffix].join
51
+
52
+ # Constantize an instance of the message-specific class, or fallback to a generic message
53
+ ActiveSupport::Inflector.safe_constantize(message_class_name) || StatusMessage
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-transformer"
4
+ require "json"
5
+
6
+ module DjiMqttConnect
7
+ module Sys::Product
8
+ class StatusReplyMarshal < MessageMarshal
9
+ # Removes the `_data` attribute from output
10
+ class HashTransformer < Dry::Transformer::Pipe
11
+ import Dry::Transformer::HashTransformations
12
+
13
+ define! do
14
+ rename_keys _method: :method
15
+ end
16
+ end
17
+
18
+ # Converts a message for transmission via MQTT
19
+ def dump(message)
20
+ # Fix up the hash representation
21
+ transformed_message = hash_transformer.call(message)
22
+
23
+ # Convert the transformed message into JSON
24
+ JSON.generate(transformed_message)
25
+ end
26
+
27
+ private
28
+
29
+ def hash_transformer
30
+ @hash_transformer ||= HashTransformer.new
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-transformer"
4
+ require "json"
5
+
6
+ module DjiMqttConnect
7
+ module Thing::Product
8
+ class OsdMarshal < MessageMarshal
9
+ # Make a copy of the raw data
10
+ class AttributeTransformer < Dry::Transformer::Pipe
11
+ import Dry::Transformer::Conditional
12
+ import Dry::Transformer::HashTransformations
13
+
14
+ define! do
15
+ copy_keys "data" => "_data"
16
+
17
+ map_value "data" do
18
+ guard ->(data) { data&.key?("wireless_link") } do
19
+ # Translate wireless_link 4g keys (from remote payload)
20
+ map_value "wireless_link" do
21
+ rename_keys "4g_freq_band" => "_4g_freq_band",
22
+ "4g_gnd_quality" => "_4g_gnd_quality",
23
+ "4g_link_state" => "_4g_link_state",
24
+ "4g_quality" => "_4g_quality",
25
+ "4g_uav_quality" => "_4g_uav_quality"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ # Parse message from Device > Cloud Server
33
+ def load(raw_message)
34
+ parsed_message = JSON.parse(raw_message)
35
+
36
+ message_data = parsed_message.fetch("data", {})
37
+ message_class = if drone_osd_data_attributes.all? { |attribute| message_data.key?(attribute.to_s) }
38
+ DroneOsdMessage
39
+ elsif remote_osd_data_attributes.all? { |attribute| message_data.key?(attribute.to_s) }
40
+ RemoteOsdMessage
41
+ else
42
+ OsdMessage
43
+ end
44
+
45
+ transformed_message = attribute_transformer.call(parsed_message)
46
+
47
+ message_class.new(transformed_message)
48
+ rescue JSON::ParserError => e
49
+ raise ParseError.new(e, "Unable to parse message as JSON")
50
+ rescue Dry::Struct::Error => e
51
+ raise ParseError.new(e, "Invalid #{message_class} payload")
52
+ end
53
+
54
+ private
55
+
56
+ def attribute_transformer
57
+ @attribute_transformer ||= AttributeTransformer.new
58
+ end
59
+
60
+ def drone_osd_data_attributes
61
+ @drone_osd_data_attributes ||= DjiMqttConnect::Thing::Product::DroneOsdMessage::Data.schema.keys.map(&:name)
62
+ end
63
+
64
+ def remote_osd_data_attributes
65
+ @remote_osd_data_attributes ||= DjiMqttConnect::Thing::Product::RemoteOsdMessage::Data.schema.keys.map(&:name)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-struct"
4
+
5
+ module DjiMqttConnect
6
+ class Message < Dry::Struct
7
+ transform_keys(&:to_sym)
8
+
9
+ def to_s
10
+ self.class.name
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ class MessageMarshal
5
+ class << self
6
+ delegate :load, :dump, to: :new
7
+ end
8
+
9
+ # Parse message loaded from a topic
10
+ def load(_raw_string)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ # Serialize message for sending via a topic
15
+ def dump(_message)
16
+ raise NotImplementedError
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ module Sys::Product
5
+ # {"tid":"3093aad5-bb98-5026-685a-cea540eb9267","bid":"479533e0-cb01-933c-3758-22f2a82abeb8","timestamp":1679380232398,"method":"update_topo","data":{"domain":2,"type":144,"sub_type":0,"device_secret":"0d180c84ddbe76ee32ad2e48eab3c994","nonce":"83ad8038363b4d5387cbf106d1517718","version":1,"sub_devices":[]}}
6
+ class StatusMessage < DjiMqttConnect::Message
7
+ attribute :tid, Types::UUID
8
+ attribute :bid, Types::UUID
9
+ attribute :timestamp, Types::Timestamp
10
+
11
+ attribute :_data, Types::Hash
12
+ attribute :_method, Types::String
13
+
14
+ def to_s
15
+ # Include data method for Generic messages
16
+ instance_of?(StatusMessage) ? "#{super}[#{_method}]" : super
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ module Sys::Product
5
+ class UpdateTopoStatusMessage < StatusMessage
6
+ attribute :data do
7
+ attribute :type, Types::Integer
8
+ attribute :sub_type, Types::Integer
9
+ attribute :device_secret, Types::String
10
+ attribute? :domain, Types::Integer
11
+ attribute :nonce, Types::String
12
+ attribute :version, Types::Integer
13
+ attribute :sub_devices, Types::Array do
14
+ attribute :sn, Types::String
15
+ attribute :type, Types::Integer
16
+ attribute :sub_type, Types::Integer
17
+ attribute :index, Types::String
18
+ attribute :device_secret, Types::String
19
+ attribute :nonce, Types::String
20
+ attribute :version, Types::Integer
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ module Sys::Product
5
+ class UpdateTopoStatusReplyMessage < Message
6
+ # Builds a reply for an update_topo status message
7
+ # a result of 0 means we approve of this action
8
+ def self.build_for(update_topo_status_message, result: 0)
9
+ new(
10
+ _method: update_topo_status_message._method,
11
+ tid: update_topo_status_message.tid,
12
+ bid: update_topo_status_message.bid,
13
+ timestamp: Time.now.strftime("%s%L").to_i,
14
+ data: {
15
+ result: result
16
+ }
17
+ )
18
+ end
19
+
20
+ attribute :_method, Types::String
21
+
22
+ attribute :tid, Types::String
23
+ attribute :bid, Types::String
24
+ attribute :timestamp, Types::Integer
25
+ attribute :data do
26
+ attribute :result, Types::Integer
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ module Thing::Product
5
+ class DroneOsdMessage < OsdMessage
6
+ attribute :data do
7
+ attribute :attitude_head, Types::JSON::Decimal
8
+ attribute :attitude_pitch, Types::JSON::Decimal
9
+ attribute :attitude_roll, Types::JSON::Decimal
10
+ attribute :battery do
11
+ attribute :batteries, Types::Array do
12
+ attribute :capacity_percent, Types::Integer
13
+ attribute :firmware_version, Types::String
14
+ attribute :index, Types::Integer
15
+ attribute :loop_times, Types::Integer
16
+ attribute :sn, Types::String
17
+ attribute :sub_type, Types::Integer
18
+ attribute :temperature, Types::JSON::Decimal
19
+ attribute :type, Types::Integer
20
+ attribute :voltage, Types::Integer
21
+ end
22
+ attribute :capacity_percent, Types::Integer
23
+ attribute :landing_power, Types::Integer
24
+ attribute :remain_flight_time, Types::Integer
25
+ attribute :return_home_power, Types::Integer
26
+ end
27
+ attribute :distance_limit_status do
28
+ attribute :distance_limit, Types::Integer
29
+ attribute :state, Types::Integer
30
+ end
31
+ attribute :elevation, Types::JSON::Decimal
32
+ attribute :firmware_version, Types::String
33
+ attribute :gear, Types::Integer
34
+ attribute :height, Types::JSON::Decimal
35
+ attribute :height_limit, Types::JSON::Decimal
36
+ attribute :home_distance, Types::JSON::Decimal
37
+ attribute :horizontal_speed, Types::JSON::Decimal
38
+ attribute :latitude, Types::Latitude
39
+ attribute :longitude, Types::Longitude
40
+ attribute :mode_code, Types::Integer
41
+ attribute :position_state do
42
+ attribute :gps_number, Types::Integer
43
+ attribute :is_fixed, Types::Integer
44
+ attribute :quality, Types::Integer
45
+ attribute :rtk_number, Types::Integer
46
+ end
47
+ attribute :storage do
48
+ attribute :total, Types::Integer
49
+ attribute :used, Types::Integer
50
+ end
51
+ attribute :total_flight_distance, Types::Integer
52
+ attribute :total_flight_time, Types::Integer
53
+ attribute :track_id, Types::String
54
+ attribute :vertical_speed, Types::JSON::Decimal
55
+ attribute :wind_direction, Types::JSON::Decimal
56
+ attribute :wind_speed, Types::JSON::Decimal
57
+
58
+ def latitude?
59
+ !Types::NullInteger.valid?(latitude)
60
+ end
61
+
62
+ def longitude?
63
+ !Types::NullInteger.valid?(longitude)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-transformer"
4
+
5
+ module DjiMqttConnect
6
+ module Thing::Product
7
+ class OsdMessage < Message
8
+ attribute :tid, Types::UUID
9
+ attribute :bid, Types::UUID
10
+ attribute :timestamp, Types::Timestamp
11
+ attribute :gateway, Types::String
12
+
13
+ # Backup of received data
14
+ attribute :_data, Types::Hash
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ module Thing::Product
5
+ class RemoteOsdMessage < OsdMessage
6
+ attribute :data do
7
+ attribute :capacity_percent, Types::Integer
8
+ attribute :latitude, Types::Latitude
9
+ attribute :live_status do
10
+ attribute :live_time, Types::Integer
11
+ attribute :live_trendline, Types::Integer
12
+ attribute :video_id, Types::String
13
+ attribute :video_quality, Types::Integer
14
+ end
15
+ attribute :longitude, Types::Longitude
16
+ attribute :transmission_signal_quality, Types::Integer
17
+ attribute :wireless_link do
18
+ attribute :_4g_freq_band, Types::JSON::Decimal
19
+ attribute :_4g_gnd_quality, Types::Integer
20
+ attribute :_4g_link_state, Types::Integer
21
+ attribute :_4g_quality, Types::Integer
22
+ attribute :_4g_uav_quality, Types::Integer
23
+ attribute :dongle_number, Types::Integer
24
+ attribute :link_workmode, Types::Integer
25
+ attribute :sdr_freq_band, Types::JSON::Decimal
26
+ attribute :sdr_link_state, Types::Integer
27
+ attribute :sdr_quality, Types::Integer
28
+ end
29
+ attribute :wireless_link_state do
30
+ attribute :download_quality, Types::Integer
31
+ attribute :frequency_band, Types::Integer
32
+ attribute :upward_quality, Types::Integer
33
+ end
34
+
35
+ def latitude?
36
+ !Types::NullInteger.valid?(latitude)
37
+ end
38
+
39
+ def longitude?
40
+ !Types::NullInteger.valid?(longitude)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ class Railtie < Rails::Railtie
5
+ # Allow ActiveJob to serialize DjiMqttConnect::Message objects
6
+ config.active_job.custom_serializers << Railties::MessageSerializer
7
+
8
+ # Replace default logger with Rails logger
9
+ initializer "dji_mqtt_connect.configure_logger" do
10
+ DjiMqttConnect.logger = Rails.logger
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ module Railties
5
+ class MessageSerializer < ActiveJob::Serializers::ObjectSerializer
6
+ def serialize(message)
7
+ stringified_hash = message.to_h.deep_stringify_keys
8
+
9
+ # Strip the extra data element
10
+ stringified_hash.delete("_data")
11
+
12
+ super(
13
+ "type" => message.class.name,
14
+ "payload" => stringified_hash
15
+ )
16
+ end
17
+
18
+ def deserialize(hash)
19
+ # Rebuild the message, re-insert the data hash
20
+ message_class = hash["type"].constantize
21
+ message_payload = hash["payload"].merge("_data" => {})
22
+ message_class.new(message_payload)
23
+ end
24
+
25
+ private
26
+
27
+ def klass
28
+ DjiMqttConnect::Message
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "wisper"
4
+
5
+ module DjiMqttConnect
6
+ class TopicRepository
7
+ include Wisper::Publisher
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :client
16
+
17
+ def logger
18
+ @logger ||= DjiMqttConnect.logger.tagged(self.class.name.sub(/Repository\z/, ""))
19
+ end
20
+
21
+ def listen_to_topic(topic, &block)
22
+ logger.info("Listening to #{topic}...")
23
+
24
+ client.mqtt_client.get(topic, &block)
25
+ rescue Interrupt
26
+ # Allow safe exiting after interrupts
27
+ logger.info("Disconnected")
28
+ end
29
+
30
+ def publish_to_topic(topic, message, marshal:)
31
+ logger.info("Publishing #{message.class} to #{topic}")
32
+
33
+ message_data = marshal.dump(message)
34
+ logger.debug(message_data)
35
+
36
+ client.mqtt_client.publish(topic, message_data)
37
+
38
+ broadcast(:publish_to_topic, topic, message)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ module Sys::Product
5
+ class StatusTopicRepository < TopicRepository
6
+ STATUS_TOPIC_REGEX = /\Asys\/product\/(?<device_identifier>.+)\/status\z/
7
+
8
+ def listen!
9
+ listen_to_topic("sys/product/+/status") do |topic, raw_message|
10
+ logger.debug(raw_message)
11
+
12
+ matched_topic = STATUS_TOPIC_REGEX.match(topic)
13
+ raise Error, "Unknown topic: #{topic}" unless matched_topic
14
+
15
+ device_identifier = matched_topic[:device_identifier]
16
+ message = status_marshal.load(raw_message)
17
+
18
+ logger.info("Received #{message} from #{device_identifier}")
19
+ broadcast(message._method.to_sym, device_identifier, message)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def status_marshal
26
+ @status_marshal ||= StatusMarshal.new
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ module Sys::Product
5
+ class StatusReplyTopicRepository < TopicRepository
6
+ # Publishes a message to device_identifier on the "sys/product/+/status_reply" topic.
7
+ def publish_to_device(device_identifier, status_reply_message)
8
+ publish_to_topic(
9
+ "sys/product/#{device_identifier}/status_reply",
10
+ status_reply_message,
11
+ marshal: StatusReplyMarshal
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ module Thing::Product
5
+ class OsdTopicRepository < TopicRepository
6
+ OSD_TOPIC_REGEX = /\Athing\/product\/(?<device_identifier>.+)\/osd\z/
7
+
8
+ def listen!
9
+ listen_to_topic("thing/product/+/osd") do |topic, raw_message|
10
+ logger.debug(raw_message)
11
+
12
+ matched_topic = OSD_TOPIC_REGEX.match(topic)
13
+ raise Error, "Unknown topic: #{topic}" unless matched_topic
14
+
15
+ device_identifier = matched_topic[:device_identifier]
16
+ message = OsdMarshal.load(raw_message)
17
+
18
+ logger.info("Received #{message} from #{device_identifier}")
19
+
20
+ # Build event name and broadcast (i.e. ::RemoteOsdMessage => remote_osd_update)
21
+ event_name = message.class.name.demodulize.sub(/Message\z/, "Update").underscore.to_sym
22
+ broadcast(event_name, device_identifier, message)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-types"
4
+
5
+ module DjiMqttConnect
6
+ # Types binding for dry-types
7
+ module Types
8
+ include Dry::Types()
9
+
10
+ # DJI does not like null, so it returns Integer 0 instead of a Decimal...
11
+ NullInteger = Types::Strict::Integer.constrained(eql: 0)
12
+
13
+ # Not a Strict UUID format... turns out DJI doesn't follow one
14
+ UUID = Strict::String.constrained(format: /\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\z/i)
15
+
16
+ # Just make sure Timestamps are positive
17
+ Timestamp = Types::Integer.constrained(gteq: 0)
18
+
19
+ # lat/lng coordinates
20
+ Latitude = NullInteger | Types::JSON::Decimal.constrained(gteq: -90, lteq: 90)
21
+ Longitude = NullInteger | Types::JSON::Decimal.constrained(gteq: -180, lteq: 180)
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DjiMqttConnect
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "mqtt"
5
+
6
+ require_relative "dji_mqtt_connect/version"
7
+ require_relative "dji_mqtt_connect/error"
8
+
9
+ module DjiMqttConnect
10
+ autoload :Client, "dji_mqtt_connect/client"
11
+ autoload :Message, "dji_mqtt_connect/message"
12
+ autoload :MessageMarshal, "dji_mqtt_connect/message_marshal"
13
+ autoload :TopicRepository, "dji_mqtt_connect/topic_repository"
14
+ autoload :Types, "dji_mqtt_connect/types"
15
+
16
+ module Sys
17
+ module Product
18
+ # Messages
19
+ autoload :StatusMessage, "dji_mqtt_connect/messages/sys/product/status_message"
20
+ autoload :UpdateTopoStatusMessage, "dji_mqtt_connect/messages/sys/product/update_topo_status_message"
21
+ autoload :UpdateTopoStatusReplyMessage, "dji_mqtt_connect/messages/sys/product/update_topo_status_reply_message"
22
+
23
+ # Topics
24
+ autoload :StatusTopicRepository, "dji_mqtt_connect/topics/sys/product/status"
25
+ autoload :StatusReplyTopicRepository, "dji_mqtt_connect/topics/sys/product/status_reply"
26
+
27
+ # Marshals
28
+ autoload :StatusMarshal, "dji_mqtt_connect/marshals/sys/product/status_marshal"
29
+ autoload :StatusReplyMarshal, "dji_mqtt_connect/marshals/sys/product/status_reply_marshal"
30
+ end
31
+ end
32
+
33
+ module Thing
34
+ module Product
35
+ # Messages
36
+ autoload :OsdMessage, "dji_mqtt_connect/messages/thing/product/osd_message"
37
+ autoload :DroneOsdMessage, "dji_mqtt_connect/messages/thing/product/drone_osd_message"
38
+ autoload :RemoteOsdMessage, "dji_mqtt_connect/messages/thing/product/remote_osd_message"
39
+
40
+ # Topics
41
+ autoload :OsdTopicRepository, "dji_mqtt_connect/topics/thing/product/osd"
42
+
43
+ # Marshals
44
+ autoload :OsdMarshal, "dji_mqtt_connect/marshals/thing/product/osd_marshal"
45
+ end
46
+ end
47
+
48
+ # Railties
49
+ module Railties
50
+ autoload :MessageSerializer, "dji_mqtt_connect/railties/message_serializer"
51
+ end
52
+
53
+ cattr_accessor :client_id, default: MQTT::Client.generate_client_id(name)
54
+ cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout)).tap { |logger| logger.level = Logger::INFO }
55
+ end
56
+
57
+ require "dji_mqtt_connect/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,4 @@
1
+ module DjiMqttConnect
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dji_mqtt_connect
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sphere Drones
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-05-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ - - "<="
21
+ - !ruby/object:Gem::Version
22
+ version: '8'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '6.0'
30
+ - - "<="
31
+ - !ruby/object:Gem::Version
32
+ version: '8'
33
+ - !ruby/object:Gem::Dependency
34
+ name: dry-struct
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.6'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.6'
47
+ - !ruby/object:Gem::Dependency
48
+ name: dry-transformer
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: mqtt
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.6.0
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.6.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: wisper
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '2.0'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '2.0'
89
+ description:
90
+ email:
91
+ - projects@spheregroup.com.au
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - ".rspec"
97
+ - ".rubocop.yml"
98
+ - ".rubocop/rspec.yml"
99
+ - ".rubocop/strict.yml"
100
+ - ".rubocop/style.yml"
101
+ - CHANGELOG.md
102
+ - Gemfile
103
+ - Gemfile.lock
104
+ - README.md
105
+ - Rakefile
106
+ - lib/dji_mqtt_connect.rb
107
+ - lib/dji_mqtt_connect/client.rb
108
+ - lib/dji_mqtt_connect/error.rb
109
+ - lib/dji_mqtt_connect/factories.rb
110
+ - lib/dji_mqtt_connect/marshals/sys/product/status_marshal.rb
111
+ - lib/dji_mqtt_connect/marshals/sys/product/status_reply_marshal.rb
112
+ - lib/dji_mqtt_connect/marshals/thing/product/osd_marshal.rb
113
+ - lib/dji_mqtt_connect/message.rb
114
+ - lib/dji_mqtt_connect/message_marshal.rb
115
+ - lib/dji_mqtt_connect/messages/sys/product/status_message.rb
116
+ - lib/dji_mqtt_connect/messages/sys/product/update_topo_status_message.rb
117
+ - lib/dji_mqtt_connect/messages/sys/product/update_topo_status_reply_message.rb
118
+ - lib/dji_mqtt_connect/messages/thing/product/drone_osd_message.rb
119
+ - lib/dji_mqtt_connect/messages/thing/product/osd_message.rb
120
+ - lib/dji_mqtt_connect/messages/thing/product/remote_osd_message.rb
121
+ - lib/dji_mqtt_connect/railtie.rb
122
+ - lib/dji_mqtt_connect/railties/message_serializer.rb
123
+ - lib/dji_mqtt_connect/topic_repository.rb
124
+ - lib/dji_mqtt_connect/topics/sys/product/status.rb
125
+ - lib/dji_mqtt_connect/topics/sys/product/status_reply.rb
126
+ - lib/dji_mqtt_connect/topics/thing/product/osd.rb
127
+ - lib/dji_mqtt_connect/types.rb
128
+ - lib/dji_mqtt_connect/version.rb
129
+ - sig/dji_mqtt_connect.rbs
130
+ homepage: https://github.com/sphere-drones/dji_mqtt_connect
131
+ licenses: []
132
+ metadata:
133
+ homepage_uri: https://github.com/sphere-drones/dji_mqtt_connect
134
+ source_code_uri: https://github.com/sphere-drones/dji_mqtt_connect
135
+ changelog_uri: https://github.com/sphere-drones/dji_mqtt_connect/main/CHANGELOG.md
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: 3.1.0
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubygems_version: 3.4.12
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: Manages MQTT Connections with DJI Drones
155
+ test_files: []