dji_mqtt_connect 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop/rspec.yml +55 -0
- data/.rubocop/strict.yml +7 -0
- data/.rubocop/style.yml +6 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +133 -0
- data/README.md +31 -0
- data/Rakefile +12 -0
- data/lib/dji_mqtt_connect/client.rb +51 -0
- data/lib/dji_mqtt_connect/error.rb +45 -0
- data/lib/dji_mqtt_connect/factories.rb +49 -0
- data/lib/dji_mqtt_connect/marshals/sys/product/status_marshal.rb +57 -0
- data/lib/dji_mqtt_connect/marshals/sys/product/status_reply_marshal.rb +34 -0
- data/lib/dji_mqtt_connect/marshals/thing/product/osd_marshal.rb +69 -0
- data/lib/dji_mqtt_connect/message.rb +13 -0
- data/lib/dji_mqtt_connect/message_marshal.rb +19 -0
- data/lib/dji_mqtt_connect/messages/sys/product/status_message.rb +20 -0
- data/lib/dji_mqtt_connect/messages/sys/product/update_topo_status_message.rb +25 -0
- data/lib/dji_mqtt_connect/messages/sys/product/update_topo_status_reply_message.rb +30 -0
- data/lib/dji_mqtt_connect/messages/thing/product/drone_osd_message.rb +68 -0
- data/lib/dji_mqtt_connect/messages/thing/product/osd_message.rb +17 -0
- data/lib/dji_mqtt_connect/messages/thing/product/remote_osd_message.rb +45 -0
- data/lib/dji_mqtt_connect/railtie.rb +13 -0
- data/lib/dji_mqtt_connect/railties/message_serializer.rb +32 -0
- data/lib/dji_mqtt_connect/topic_repository.rb +41 -0
- data/lib/dji_mqtt_connect/topics/sys/product/status.rb +30 -0
- data/lib/dji_mqtt_connect/topics/sys/product/status_reply.rb +16 -0
- data/lib/dji_mqtt_connect/topics/thing/product/osd.rb +27 -0
- data/lib/dji_mqtt_connect/types.rb +23 -0
- data/lib/dji_mqtt_connect/version.rb +5 -0
- data/lib/dji_mqtt_connect.rb +57 -0
- data/sig/dji_mqtt_connect.rbs +4 -0
- 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
data/.rubocop/rspec.yml
ADDED
@@ -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
|
data/.rubocop/strict.yml
ADDED
data/.rubocop/style.yml
ADDED
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
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,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,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,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)
|
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: []
|