dji_mqtt_connect 0.1.1.4 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop/rspec.yml +2 -2
- data/Gemfile.lock +20 -9
- data/dji_mqtt_connect.gemspec +48 -0
- data/lib/dji_mqtt_connect/factories.rb +65 -0
- data/lib/dji_mqtt_connect/marshals/thing/product/osd_marshal.rb +17 -3
- data/lib/dji_mqtt_connect/messages/thing/product/airport_organization_get_requests_message.rb +15 -0
- data/lib/dji_mqtt_connect/messages/thing/product/airport_organization_get_requests_reply_message.rb +34 -0
- data/lib/dji_mqtt_connect/messages/thing/product/dock_osd_message.rb +199 -0
- data/lib/dji_mqtt_connect/messages/thing/product/drone_osd_message.rb +114 -10
- data/lib/dji_mqtt_connect/messages/thing/product/remote_osd_message.rb +4 -9
- data/lib/dji_mqtt_connect/messages/thing/product/storage_config_get_requests_message.rb +14 -0
- data/lib/dji_mqtt_connect/messages/thing/product/storage_config_get_requests_reply_message.rb +44 -0
- data/lib/dji_mqtt_connect/mixins/latitude_conditional.rb +12 -0
- data/lib/dji_mqtt_connect/mixins/longitude_conditional.rb +12 -0
- data/lib/dji_mqtt_connect/mixins/temperature_conditional.rb +12 -0
- data/lib/dji_mqtt_connect/topics/sys/product/status.rb +8 -1
- data/lib/dji_mqtt_connect/topics/thing/product/osd.rb +8 -3
- data/lib/dji_mqtt_connect/topics/thing/product/requests.rb +8 -1
- data/lib/dji_mqtt_connect/types.rb +3 -0
- data/lib/dji_mqtt_connect/utils/message_sanitizer.rb +90 -0
- data/lib/dji_mqtt_connect/utils/message_schema_generator.rb +39 -0
- data/lib/dji_mqtt_connect/utils/mqttx_fixture_data_generator.rb +68 -0
- data/lib/dji_mqtt_connect/version.rb +1 -1
- data/lib/dji_mqtt_connect.rb +15 -0
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 824a4b8942fc65705e25588a32df620e49602573b1eb14ed6f1b2c34c98023cd
|
4
|
+
data.tar.gz: 7f9dc5c06e40c04862baed3c41b8342da77a9fbedb083ab7432b9173180ebce1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58ccec055d15866882fd93521c5d312719012c10e8778e18b99191c2bb48a7173ac0283f7f18daf21fb950d0472c02b3cb861cd1d07b0da0584e62278d62554c
|
7
|
+
data.tar.gz: ac4f30aa186b37fa04dbadbc49d813648c08ca031809a7c3b73d97fb0d6f7489cc680d74e0a3a83015ebcf77d26d25386c7f8dbecdb4e04e337308bc3a1f7a6c
|
data/.rubocop/rspec.yml
CHANGED
@@ -48,8 +48,8 @@ RSpec/ReceiveCounts:
|
|
48
48
|
Capybara/CurrentPathExpectation:
|
49
49
|
Enabled: true
|
50
50
|
|
51
|
-
|
51
|
+
FactoryBot/AttributeDefinedStatically:
|
52
52
|
Enabled: true
|
53
53
|
|
54
|
-
|
54
|
+
FactoryBot/CreateList:
|
55
55
|
Enabled: true
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dji_mqtt_connect (0.1.
|
4
|
+
dji_mqtt_connect (0.1.2)
|
5
5
|
activesupport (>= 6.0, <= 8)
|
6
6
|
dry-struct (~> 1.6)
|
7
7
|
dry-transformer (~> 1.0)
|
@@ -46,15 +46,16 @@ GEM
|
|
46
46
|
zeitwerk (~> 2.6)
|
47
47
|
globalid (1.1.0)
|
48
48
|
activesupport (>= 5.0)
|
49
|
-
i18n (1.
|
49
|
+
i18n (1.13.0)
|
50
50
|
concurrent-ruby (~> 1.0)
|
51
51
|
ice_nine (0.11.2)
|
52
52
|
json (2.6.3)
|
53
53
|
language_server-protocol (3.17.0.3)
|
54
|
+
lint_roller (1.0.0)
|
54
55
|
minitest (5.18.0)
|
55
56
|
mqtt (0.6.0)
|
56
57
|
parallel (1.23.0)
|
57
|
-
parser (3.2.2.
|
58
|
+
parser (3.2.2.1)
|
58
59
|
ast (~> 2.4.1)
|
59
60
|
rainbow (3.1.1)
|
60
61
|
rake (13.0.6)
|
@@ -66,7 +67,7 @@ GEM
|
|
66
67
|
rspec-mocks (~> 3.12.0)
|
67
68
|
rspec-core (3.12.2)
|
68
69
|
rspec-support (~> 3.12.0)
|
69
|
-
rspec-expectations (3.12.
|
70
|
+
rspec-expectations (3.12.3)
|
70
71
|
diff-lcs (>= 1.2.0, < 2.0)
|
71
72
|
rspec-support (~> 3.12.0)
|
72
73
|
rspec-mocks (3.12.5)
|
@@ -83,18 +84,21 @@ GEM
|
|
83
84
|
rubocop-ast (>= 1.28.0, < 2.0)
|
84
85
|
ruby-progressbar (~> 1.7)
|
85
86
|
unicode-display_width (>= 2.4.0, < 3.0)
|
86
|
-
rubocop-ast (1.28.
|
87
|
+
rubocop-ast (1.28.1)
|
87
88
|
parser (>= 3.2.1.0)
|
88
|
-
rubocop-capybara (2.
|
89
|
+
rubocop-capybara (2.18.0)
|
89
90
|
rubocop (~> 1.41)
|
91
|
+
rubocop-factory_bot (2.22.0)
|
92
|
+
rubocop (~> 1.33)
|
90
93
|
rubocop-performance (1.16.0)
|
91
94
|
rubocop (>= 1.7.0, < 2.0)
|
92
95
|
rubocop-ast (>= 0.4.0)
|
93
96
|
rubocop-rake (0.6.0)
|
94
97
|
rubocop (~> 1.0)
|
95
|
-
rubocop-rspec (2.
|
98
|
+
rubocop-rspec (2.22.0)
|
96
99
|
rubocop (~> 1.33)
|
97
100
|
rubocop-capybara (~> 2.17)
|
101
|
+
rubocop-factory_bot (~> 2.22)
|
98
102
|
ruby-progressbar (1.13.0)
|
99
103
|
simplecov (0.22.0)
|
100
104
|
docile (~> 1.1)
|
@@ -102,16 +106,23 @@ GEM
|
|
102
106
|
simplecov_json_formatter (~> 0.1)
|
103
107
|
simplecov-html (0.12.3)
|
104
108
|
simplecov_json_formatter (0.1.4)
|
105
|
-
standard (1.
|
109
|
+
standard (1.28.2)
|
106
110
|
language_server-protocol (~> 3.17.0.2)
|
111
|
+
lint_roller (~> 1.0)
|
107
112
|
rubocop (~> 1.50.2)
|
113
|
+
standard-custom (~> 1.0.0)
|
114
|
+
standard-performance (~> 1.0.1)
|
115
|
+
standard-custom (1.0.0)
|
116
|
+
lint_roller (~> 1.0)
|
117
|
+
standard-performance (1.0.1)
|
118
|
+
lint_roller (~> 1.0)
|
108
119
|
rubocop-performance (~> 1.16.0)
|
109
120
|
tzinfo (2.0.6)
|
110
121
|
concurrent-ruby (~> 1.0)
|
111
122
|
unicode-display_width (2.4.2)
|
112
123
|
wisper (2.0.1)
|
113
124
|
wisper-rspec (1.1.0)
|
114
|
-
zeitwerk (2.6.
|
125
|
+
zeitwerk (2.6.8)
|
115
126
|
|
116
127
|
PLATFORMS
|
117
128
|
x86_64-darwin-21
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/dji_mqtt_connect/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "dji_mqtt_connect"
|
7
|
+
spec.version = DjiMqttConnect::VERSION
|
8
|
+
spec.authors = ["Sphere Drones"]
|
9
|
+
spec.email = ["projects@spheregroup.com.au"]
|
10
|
+
|
11
|
+
spec.summary = "Manages MQTT Connections with DJI Drones"
|
12
|
+
# spec.description = "TODO: Write a longer description or delete this line."
|
13
|
+
spec.homepage = "https://github.com/sphere-drones/dji_mqtt_connect"
|
14
|
+
spec.required_ruby_version = ">= 3.1.0"
|
15
|
+
|
16
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/sphere-drones/dji_mqtt_connect"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/sphere-drones/dji_mqtt_connect/main/CHANGELOG.md"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
# Use Active Support for inflection and logging
|
34
|
+
spec.add_dependency "activesupport", ">= 6.0", "<= 8"
|
35
|
+
|
36
|
+
# Use Dry-rb for valid POROs
|
37
|
+
spec.add_dependency "dry-struct", "~> 1.6"
|
38
|
+
spec.add_dependency "dry-transformer", "~> 1.0"
|
39
|
+
|
40
|
+
# Use MQTT as a client for receiving/sending messages
|
41
|
+
spec.add_dependency "mqtt", "~> 0.6.0"
|
42
|
+
|
43
|
+
# Use Whisper for events
|
44
|
+
spec.add_dependency "wisper", "~> 2.0"
|
45
|
+
|
46
|
+
# For more information and examples about making a new gem, check out our
|
47
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
48
|
+
end
|
@@ -114,5 +114,70 @@ module DjiMqttConnect
|
|
114
114
|
end
|
115
115
|
)
|
116
116
|
end
|
117
|
+
|
118
|
+
def build_thing_product_airport_organization_get_requests_message
|
119
|
+
message_data = {
|
120
|
+
device_binding_code: "device_binding_code",
|
121
|
+
organization_id: "organization_id"
|
122
|
+
}
|
123
|
+
|
124
|
+
Thing::Product::AirportOrganizationGetRequestsMessage.new(
|
125
|
+
_method: "airport_organization_get",
|
126
|
+
_data: message_data,
|
127
|
+
tid: "3093aad5-bb98-5026-685a-cea540eb9267",
|
128
|
+
bid: "479533e0-cb01-933c-3758-22f2a82abeb8",
|
129
|
+
timestamp: 1679380233000,
|
130
|
+
data: message_data
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
def build_thing_product_airport_organization_get_requests_reply_message
|
135
|
+
airport_organization_get_request = build_thing_product_airport_organization_get_requests_message
|
136
|
+
|
137
|
+
Thing::Product::AirportOrganizationGetRequestsReplyMessage.build_for(
|
138
|
+
airport_organization_get_request,
|
139
|
+
result: 0,
|
140
|
+
output: {
|
141
|
+
organization_name: "12345"
|
142
|
+
}
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
def build_thing_product_storage_config_get_requests_message
|
147
|
+
message_data = {
|
148
|
+
module: 0
|
149
|
+
}
|
150
|
+
|
151
|
+
Thing::Product::StorageConfigGetRequestsMessage.new(
|
152
|
+
_method: "storage_config_get",
|
153
|
+
_data: message_data,
|
154
|
+
tid: "3093aad5-bb98-5026-685a-cea540eb9267",
|
155
|
+
bid: "479533e0-cb01-933c-3758-22f2a82abeb8",
|
156
|
+
timestamp: 1679380233000,
|
157
|
+
data: message_data
|
158
|
+
)
|
159
|
+
end
|
160
|
+
|
161
|
+
def build_thing_product_storage_config_get_requests_reply_message
|
162
|
+
storage_config_get_request = build_thing_product_storage_config_get_requests_message
|
163
|
+
|
164
|
+
Thing::Product::StorageConfigGetRequestsReplyMessage.build_for(
|
165
|
+
storage_config_get_request,
|
166
|
+
result: 0,
|
167
|
+
output: {
|
168
|
+
bucket: "bucket_name",
|
169
|
+
credentials: {
|
170
|
+
access_key_id: "access_key_id",
|
171
|
+
access_key_secret: "access_key_secret",
|
172
|
+
expire: 3600,
|
173
|
+
security_token: "security_token"
|
174
|
+
},
|
175
|
+
endpoint: "https://oss-cn-hangzhou.aliyuncs.com",
|
176
|
+
object_key_prefix: "b4cfaae6-bd9d-4cd0-8472-63b608c3c581",
|
177
|
+
provider: "ali",
|
178
|
+
region: "hz"
|
179
|
+
}
|
180
|
+
)
|
181
|
+
end
|
117
182
|
end
|
118
183
|
end
|
@@ -12,10 +12,15 @@ module DjiMqttConnect
|
|
12
12
|
import Dry::Transformer::HashTransformations
|
13
13
|
|
14
14
|
define! do
|
15
|
+
# Ensure there is a data element
|
16
|
+
map_value "data", ->(data) { data || {} }
|
17
|
+
|
18
|
+
# Create a backup for unsupported attributes
|
15
19
|
copy_keys "data" => "_data"
|
16
20
|
|
17
21
|
map_value "data" do
|
18
|
-
|
22
|
+
# Rename the invalid (for Ruby) wireless link attributes
|
23
|
+
guard ->(data) { data.key?("wireless_link") } do
|
19
24
|
# Translate wireless_link 4g keys (from remote payload)
|
20
25
|
map_value "wireless_link" do
|
21
26
|
rename_keys "4g_freq_band" => "_4g_freq_band",
|
@@ -38,6 +43,8 @@ module DjiMqttConnect
|
|
38
43
|
DroneOsdMessage
|
39
44
|
elsif remote_osd_data_attributes.all? { |attribute| message_data.key?(attribute.to_s) }
|
40
45
|
RemoteOsdMessage
|
46
|
+
elsif dock_osd_data_attributes.any? { |attribute| message_data.key?(attribute.to_s) }
|
47
|
+
DockOsdMessage
|
41
48
|
else
|
42
49
|
OsdMessage
|
43
50
|
end
|
@@ -57,12 +64,19 @@ module DjiMqttConnect
|
|
57
64
|
@attribute_transformer ||= AttributeTransformer.new
|
58
65
|
end
|
59
66
|
|
67
|
+
def dock_osd_data_attributes
|
68
|
+
@dock_osd_data_attributes ||= DockOsdMessage::STABLE_DATA_ATTRIBUTES.map(&:to_s)
|
69
|
+
end
|
70
|
+
|
60
71
|
def drone_osd_data_attributes
|
61
|
-
@drone_osd_data_attributes ||=
|
72
|
+
@drone_osd_data_attributes ||= DroneOsdMessage::STABLE_DATA_ATTRIBUTES.map(&:to_s)
|
62
73
|
end
|
63
74
|
|
64
75
|
def remote_osd_data_attributes
|
65
|
-
@remote_osd_data_attributes ||=
|
76
|
+
@remote_osd_data_attributes ||= begin
|
77
|
+
data_keys = DjiMqttConnect::Thing::Product::RemoteOsdMessage::Data.schema.keys
|
78
|
+
data_keys.select(&:required?).map(&:name)
|
79
|
+
end
|
66
80
|
end
|
67
81
|
end
|
68
82
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DjiMqttConnect
|
4
|
+
module Thing::Product
|
5
|
+
# https://developer.dji.com/doc/cloud-api-tutorial/en/server-api-reference/mqtt/thing-model/gateway/dock/organization.html#search-for-the-organization-information-that-device-bound-to
|
6
|
+
class AirportOrganizationGetRequestsMessage < RequestsMessage
|
7
|
+
attribute :_method, Types::String.enum("airport_organization_get")
|
8
|
+
|
9
|
+
attribute :data do
|
10
|
+
attribute :device_binding_code, Types::String
|
11
|
+
attribute :organization_id, Types::String
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/dji_mqtt_connect/messages/thing/product/airport_organization_get_requests_reply_message.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DjiMqttConnect
|
4
|
+
module Thing::Product
|
5
|
+
# https://developer.dji.com/doc/cloud-api-tutorial/en/server-api-reference/mqtt/thing-model/gateway/dock/organization.html#search-for-the-organization-information-that-device-bound-to
|
6
|
+
class AirportOrganizationGetRequestsReplyMessage < Message
|
7
|
+
def self.build_for(airport_organization_get_message, output:, result: 0)
|
8
|
+
new(
|
9
|
+
_method: airport_organization_get_message._method,
|
10
|
+
tid: airport_organization_get_message.tid,
|
11
|
+
bid: airport_organization_get_message.bid,
|
12
|
+
timestamp: Time.now.strftime("%s%L").to_i,
|
13
|
+
data: {
|
14
|
+
result: result,
|
15
|
+
output: output
|
16
|
+
}
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
attribute :tid, Types::UUID
|
21
|
+
attribute :bid, Types::UUID
|
22
|
+
attribute :timestamp, Types::Timestamp
|
23
|
+
|
24
|
+
attribute :_method, Types::String.enum("airport_organization_get")
|
25
|
+
|
26
|
+
attribute :data do
|
27
|
+
attribute :result, Types::Integer
|
28
|
+
attribute :output do
|
29
|
+
attribute :organization_name, Types::String
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DjiMqttConnect
|
4
|
+
module Thing::Product
|
5
|
+
class DockOsdMessage < OsdMessage
|
6
|
+
schema schema.strict
|
7
|
+
|
8
|
+
STABLE_DATA_ATTRIBUTES = %i[
|
9
|
+
drc_state
|
10
|
+
drone_in_dock
|
11
|
+
job_number
|
12
|
+
]
|
13
|
+
|
14
|
+
attribute :data do
|
15
|
+
include Mixins::LatitudeConditional
|
16
|
+
include Mixins::LongitudeConditional
|
17
|
+
include Mixins::TemperatureConditional
|
18
|
+
|
19
|
+
attribute? :acc_time, Types::Integer
|
20
|
+
attribute? :activation_time, Types::Integer
|
21
|
+
|
22
|
+
attribute? :backup_battery do
|
23
|
+
include Mixins::TemperatureConditional
|
24
|
+
|
25
|
+
attribute :switch, Types::Integer.enum(0, 1)
|
26
|
+
attribute :voltage, Types::Integer
|
27
|
+
attribute :temperature, Types::Temperature
|
28
|
+
end
|
29
|
+
|
30
|
+
# {"0":"Disconnected","1":"Connecting","2":"Connected"}
|
31
|
+
attribute? :drc_state, Types::Integer.enum(0, 1, 2)
|
32
|
+
|
33
|
+
attribute? :drone_battery_maintenance_info do
|
34
|
+
attribute :batteries, Types::Array do
|
35
|
+
include Mixins::TemperatureConditional
|
36
|
+
|
37
|
+
attribute :index, Types::Integer
|
38
|
+
attribute :capacity_percent, Types::Integer
|
39
|
+
attribute :voltage, Types::Integer
|
40
|
+
attribute :temperature, Types::Temperature
|
41
|
+
end
|
42
|
+
|
43
|
+
# {"0":"Battery is not in heating or insulation","1":"Battery is in heating","2":"Battery is in insulation"}
|
44
|
+
attribute? :heat_state, Types::Integer.enum(0, 1, 2)
|
45
|
+
|
46
|
+
# {"0":"No need to maintenance","1":"Need maintenance","2":"Under maintenance"}
|
47
|
+
attribute? :maintenance_state, Types::Integer
|
48
|
+
attribute? :maintenance_time_left, Types::Integer
|
49
|
+
end
|
50
|
+
|
51
|
+
attribute? :drone_charge_state do
|
52
|
+
# {"0":"not charging","1":"charging"}
|
53
|
+
attribute :state, Types::Integer.enum(0, 1)
|
54
|
+
|
55
|
+
# {"min":"0","max":"100"}
|
56
|
+
attribute :capacity_percent, Types::Integer.constrained(gteq: 0, lteq: 100)
|
57
|
+
end
|
58
|
+
attribute? :drone_in_dock, Types::Integer.enum(0, 1)
|
59
|
+
|
60
|
+
attribute? :electric_supply_voltage, Types::Integer
|
61
|
+
|
62
|
+
attribute? :flighttask_prepare_capacity, Types::Integer
|
63
|
+
attribute? :flighttask_step_code, Types::Integer
|
64
|
+
|
65
|
+
attribute? :job_number, Types::Integer
|
66
|
+
|
67
|
+
attribute? :latitude, Types::Latitude
|
68
|
+
attribute? :longitude, Types::Longitude
|
69
|
+
|
70
|
+
attribute? :maintain_status do
|
71
|
+
attribute :maintain_status_array, Types::Array do
|
72
|
+
# {"0":"No maintenance","1":"Under maintenance"}
|
73
|
+
attribute :state, Types::Integer
|
74
|
+
|
75
|
+
# {"0":"No maintenance","1":"Drone basic maintenance","2":"Drone routine maintenance","3":"Drone deep maintenance","17":"Dock maintenance"}
|
76
|
+
attribute :last_maintain_type, Types::Integer
|
77
|
+
|
78
|
+
attribute :last_maintain_time, Types::Integer
|
79
|
+
attribute :last_maintain_work_sorties, Types::Integer
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
attribute? :media_file_detail do
|
84
|
+
attribute :remain_upload, Types::Integer
|
85
|
+
end
|
86
|
+
|
87
|
+
attribute? :network_state do
|
88
|
+
attribute :type, Types::Integer
|
89
|
+
attribute :quality, Types::Integer
|
90
|
+
attribute :rate, Types::JSON::Decimal
|
91
|
+
end
|
92
|
+
|
93
|
+
attribute? :sdr do
|
94
|
+
attribute :down_quality, Types::Integer
|
95
|
+
attribute :frequency_band, Types::JSON::Decimal
|
96
|
+
attribute :up_quality, Types::Integer
|
97
|
+
end
|
98
|
+
|
99
|
+
attribute? :wireless_link do
|
100
|
+
attribute :_4g_freq_band, Types::JSON::Decimal
|
101
|
+
attribute :_4g_gnd_quality, Types::Integer
|
102
|
+
attribute :_4g_link_state, Types::Integer
|
103
|
+
attribute :_4g_quality, Types::Integer
|
104
|
+
attribute :_4g_uav_quality, Types::Integer
|
105
|
+
attribute :dongle_number, Types::Integer
|
106
|
+
attribute :link_workmode, Types::Integer
|
107
|
+
attribute :sdr_freq_band, Types::JSON::Decimal
|
108
|
+
attribute :sdr_link_state, Types::Integer
|
109
|
+
attribute :sdr_quality, Types::Integer
|
110
|
+
end
|
111
|
+
|
112
|
+
attribute? :working_current, Types::Integer
|
113
|
+
attribute? :working_voltage, Types::Integer
|
114
|
+
|
115
|
+
# Weather
|
116
|
+
|
117
|
+
attribute? :height, Types::JSON::Decimal
|
118
|
+
attribute? :humidity, Types::Integer
|
119
|
+
attribute? :rainfall, Types::JSON::Decimal
|
120
|
+
attribute? :temperature, Types::Temperature
|
121
|
+
attribute? :wind_speed, Types::JSON::Decimal
|
122
|
+
attribute? :environment_temperature, Types::Temperature
|
123
|
+
|
124
|
+
attribute? :alternate_land_point do
|
125
|
+
attribute :latitude, Types::Latitude
|
126
|
+
attribute :longitude, Types::Longitude
|
127
|
+
attribute :height, Types::JSON::Decimal
|
128
|
+
attribute :safe_land_height, Types::JSON::Decimal
|
129
|
+
attribute :is_configured, Types::Integer.enum(0, 1)
|
130
|
+
end
|
131
|
+
|
132
|
+
attribute? :first_power_on, Types::Integer
|
133
|
+
|
134
|
+
attribute? :position_state do
|
135
|
+
# {"0":"Not calibrated","1":"Calibrated"}
|
136
|
+
attribute :is_calibration, Types::Integer.enum(0, 1)
|
137
|
+
|
138
|
+
# {"0":"Not start","1":"fixing","2":"fix successfully","3":"fix failed"}
|
139
|
+
attribute :is_fixed, Types::Integer.enum(0, 1, 2, 3)
|
140
|
+
|
141
|
+
attribute :quality, Types::Integer.constrained(gteq: 0, lteq: 5)
|
142
|
+
attribute :gps_number, Types::Integer
|
143
|
+
attribute :rtk_number, Types::Integer
|
144
|
+
end
|
145
|
+
|
146
|
+
attribute? :storage do
|
147
|
+
attribute :total, Types::Integer
|
148
|
+
attribute :used, Types::Integer
|
149
|
+
end
|
150
|
+
|
151
|
+
# {"0":"Idle","1":"On-site debugging","2":"Remote debugging","3":"Firmware upgrading","4":"Working"}
|
152
|
+
attribute? :mode_code, Types::Integer.enum(0, 1, 2, 3, 4)
|
153
|
+
|
154
|
+
attribute? :air_conditioner do
|
155
|
+
# {"0":"Idle mode (No cooling, heating, and dehumidification)","1":"Cooling mode","2":"Heating mode","3":"Dehumidification mode","4":"Cooling exit mode","5":"Heating exit mode","6":"Dehumidification exit mode","7":"Cooling preparation mode","8":"Heating preparation mode","9":"Dehumidification preparation mode"}
|
156
|
+
attribute :air_conditioner_state, Types::Integer.enum(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
|
157
|
+
attribute :switch_time, Types::Integer
|
158
|
+
end
|
159
|
+
|
160
|
+
# {"0":"Closed","1":"Opened"}
|
161
|
+
attribute? :alarm_state, Types::Integer.enum(0, 1)
|
162
|
+
|
163
|
+
# {"1":"Planned storage strategy of battery","2":"Emergency storage strategy of battery"}
|
164
|
+
attribute? :battery_store_mode, Types::Integer.enum(1, 2)
|
165
|
+
|
166
|
+
# {"0":"Closed","1":"Opened","2":"Half-open","3":"Cover state is abnormal"}
|
167
|
+
attribute? :cover_state, Types::Integer.enum(0, 1, 2, 3)
|
168
|
+
|
169
|
+
# {"0":"Closed","1":"Opened"}
|
170
|
+
attribute? :emergency_stop_state, Types::Integer.enum(0, 1)
|
171
|
+
|
172
|
+
# {"0":"Closed","1":"Opened","2":"Half-open","3":"Putter state is abnormal"}
|
173
|
+
attribute? :putter_state, Types::Integer.enum(0, 1, 2, 3)
|
174
|
+
|
175
|
+
# {"0":"Closed","1":"Opened"}
|
176
|
+
attribute? :supplement_light_state, Types::Integer.enum(0, 1)
|
177
|
+
|
178
|
+
attribute? :sub_device do
|
179
|
+
attribute? :device_sn, Types::String
|
180
|
+
attribute? :device_model_key, Types::String
|
181
|
+
|
182
|
+
# {"0":"Offline","1":"Online"}
|
183
|
+
attribute :device_online_status, Types::Integer.enum(0, 1)
|
184
|
+
|
185
|
+
# {"0":"Not paired","1":"Paired"}
|
186
|
+
attribute :device_paired, Types::Integer.enum(0, 1)
|
187
|
+
end
|
188
|
+
|
189
|
+
def drone_in_dock?
|
190
|
+
drone_in_dock == 1
|
191
|
+
end
|
192
|
+
|
193
|
+
def environment_temperature?
|
194
|
+
environment_temperature && !Types::InvalidTemperature.valid?(environment_temperature)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -3,19 +3,42 @@
|
|
3
3
|
module DjiMqttConnect
|
4
4
|
module Thing::Product
|
5
5
|
class DroneOsdMessage < OsdMessage
|
6
|
+
STABLE_DATA_ATTRIBUTES = %i[
|
7
|
+
mode_code
|
8
|
+
gear
|
9
|
+
horizontal_speed
|
10
|
+
vertical_speed
|
11
|
+
latitude
|
12
|
+
longitude
|
13
|
+
height
|
14
|
+
elevation
|
15
|
+
attitude_head
|
16
|
+
attitude_pitch
|
17
|
+
attitude_roll
|
18
|
+
home_distance
|
19
|
+
wind_direction
|
20
|
+
wind_speed
|
21
|
+
]
|
22
|
+
|
6
23
|
attribute :data do
|
24
|
+
include Mixins::LatitudeConditional
|
25
|
+
include Mixins::LongitudeConditional
|
26
|
+
|
7
27
|
attribute :attitude_head, Types::JSON::Decimal
|
8
28
|
attribute :attitude_pitch, Types::JSON::Decimal
|
9
29
|
attribute :attitude_roll, Types::JSON::Decimal
|
10
30
|
attribute :battery do
|
11
31
|
attribute :batteries, Types::Array do
|
32
|
+
include Mixins::TemperatureConditional
|
33
|
+
|
12
34
|
attribute :capacity_percent, Types::Integer
|
13
35
|
attribute :firmware_version, Types::String
|
36
|
+
attribute? :high_voltage_storage_days, Types::Integer
|
14
37
|
attribute :index, Types::Integer
|
15
38
|
attribute :loop_times, Types::Integer
|
16
39
|
attribute :sn, Types::String
|
17
40
|
attribute :sub_type, Types::DeviceSubType
|
18
|
-
attribute :temperature, Types::
|
41
|
+
attribute :temperature, Types::Temperature
|
19
42
|
attribute :type, Types::DeviceType
|
20
43
|
attribute :voltage, Types::Integer
|
21
44
|
end
|
@@ -24,15 +47,21 @@ module DjiMqttConnect
|
|
24
47
|
attribute :remain_flight_time, Types::Integer
|
25
48
|
attribute :return_home_power, Types::Integer
|
26
49
|
end
|
27
|
-
attribute :distance_limit_status do
|
50
|
+
attribute? :distance_limit_status do
|
28
51
|
attribute :distance_limit, Types::Integer
|
29
|
-
|
52
|
+
|
53
|
+
# {"0":"Not reached","1":"Reached"}
|
54
|
+
attribute? :is_near_distance_limit, Types::Integer.enum(0, 1)
|
55
|
+
|
56
|
+
# {"0":"Not set","1":"Have been set"}
|
57
|
+
attribute :state, Types::Integer.enum(0, 1)
|
30
58
|
end
|
31
59
|
attribute :elevation, Types::JSON::Decimal
|
60
|
+
|
32
61
|
attribute :firmware_version, Types::String
|
33
62
|
attribute :gear, Types::Integer
|
34
63
|
attribute :height, Types::JSON::Decimal
|
35
|
-
attribute :height_limit, Types::JSON::Decimal
|
64
|
+
attribute? :height_limit, Types::JSON::Decimal
|
36
65
|
attribute :home_distance, Types::JSON::Decimal
|
37
66
|
attribute :horizontal_speed, Types::JSON::Decimal
|
38
67
|
attribute :latitude, Types::Latitude
|
@@ -44,24 +73,99 @@ module DjiMqttConnect
|
|
44
73
|
attribute :quality, Types::Integer
|
45
74
|
attribute :rtk_number, Types::Integer
|
46
75
|
end
|
47
|
-
attribute :storage do
|
76
|
+
attribute? :storage do
|
48
77
|
attribute :total, Types::Integer
|
49
78
|
attribute :used, Types::Integer
|
50
79
|
end
|
51
80
|
attribute :total_flight_distance, Types::JSON::Decimal
|
52
81
|
attribute :total_flight_time, Types::JSON::Decimal
|
53
|
-
attribute :track_id, Types::String
|
82
|
+
attribute? :track_id, Types::String
|
54
83
|
attribute :vertical_speed, Types::JSON::Decimal
|
55
84
|
attribute :wind_direction, Types::JSON::Decimal
|
56
85
|
attribute :wind_speed, Types::JSON::Decimal
|
57
86
|
|
58
|
-
|
59
|
-
|
87
|
+
# M30 Fields
|
88
|
+
|
89
|
+
attribute? :cameras, Types::Array do
|
90
|
+
# {"0":"Photo taking","1":"Recording"}
|
91
|
+
attribute :camera_mode, Types::Integer.enum(0, 1)
|
92
|
+
|
93
|
+
# {"min":2,"max":20}
|
94
|
+
attribute :ir_zoom_factor, Types::JSON::Decimal
|
95
|
+
|
96
|
+
attribute :liveview_world_region do
|
97
|
+
attribute :bottom, Types::JSON::Decimal.constrained(gteq: 0, lteq: 1)
|
98
|
+
attribute :left, Types::JSON::Decimal.constrained(gteq: 0, lteq: 1)
|
99
|
+
attribute :right, Types::JSON::Decimal.constrained(gteq: 0, lteq: 1)
|
100
|
+
attribute :top, Types::JSON::Decimal.constrained(gteq: 0, lteq: 1)
|
101
|
+
end
|
102
|
+
|
103
|
+
attribute :payload_index, Types::String
|
104
|
+
|
105
|
+
# {"0":"Idle","1":"Taking photo"}
|
106
|
+
attribute :photo_state, Types::Integer.enum(0, 1)
|
107
|
+
|
108
|
+
# {"0":"Idle","1":"Recording"}
|
109
|
+
attribute :recording_state, Types::Integer.enum(0, 1)
|
110
|
+
|
111
|
+
# {"unit":"s"}
|
112
|
+
attribute :record_time, Types::Integer
|
113
|
+
|
114
|
+
attribute :remain_photo_num, Types::Integer
|
115
|
+
|
116
|
+
# {"unit":"s"}
|
117
|
+
attribute :remain_record_duration, Types::Integer
|
118
|
+
|
119
|
+
# {"min":2,"max":200}
|
120
|
+
attribute :zoom_factor, Types::JSON::Decimal
|
60
121
|
end
|
61
122
|
|
62
|
-
|
63
|
-
|
123
|
+
# {"0":"Continue to execute the wayline task","1":"Exit the wayline task, and execute the remote controller out of control action."}
|
124
|
+
attribute? :exit_wayline_when_rc_lost, Types::Integer.enum(0, 1)
|
125
|
+
|
126
|
+
attribute? :is_near_area_limit, Types::Integer.enum(0, 1)
|
127
|
+
|
128
|
+
# {"0":"Not reached","1":"Reached"}
|
129
|
+
attribute? :is_near_height_limit, Types::Integer.enum(0, 1)
|
130
|
+
|
131
|
+
attribute? :maintain_status do
|
132
|
+
attribute :maintain_status_array, Types::Array do
|
133
|
+
attribute :last_maintain_flight_sorties, Types::Integer
|
134
|
+
|
135
|
+
# {"unit":"hour"}
|
136
|
+
attribute :last_maintain_flight_time, Types::Integer
|
137
|
+
|
138
|
+
attribute :last_maintain_time, Types::Integer
|
139
|
+
|
140
|
+
# {"0":"No maintenance","1":"Drone basic maintenance","2":"Drone routine maintenance","3":"Drone deep maintenance","17":"Dock maintenance"}
|
141
|
+
attribute :last_maintain_type, Types::Integer.enum(0, 1, 2, 3, 17)
|
142
|
+
|
143
|
+
# {"0":"No maintenance","1":"Under maintenance"}
|
144
|
+
attribute :state, Types::Integer.enum(0, 1)
|
145
|
+
end
|
64
146
|
end
|
147
|
+
|
148
|
+
# {"0":"Off","1":"On"}
|
149
|
+
attribute? :night_lights_state, Types::Integer.enum(0, 1)
|
150
|
+
|
151
|
+
attribute? :obstacle_avoidance do
|
152
|
+
# {"0":"Close","1":"Open"}
|
153
|
+
attribute :horizon, Types::Integer.enum(0, 1)
|
154
|
+
|
155
|
+
# {"0":"Close","1":"Open"}
|
156
|
+
attribute :upside, Types::Integer.enum(0, 1)
|
157
|
+
|
158
|
+
# {"0":"Close","1":"Open"}
|
159
|
+
attribute :downside, Types::Integer.enum(0, 1)
|
160
|
+
end
|
161
|
+
|
162
|
+
# {"0":"Hovering","1":"Landing","2":"Returning to home"}
|
163
|
+
attribute? :rc_lost_action, Types::Integer.enum(0, 1, 2)
|
164
|
+
|
165
|
+
# {"unit":"m","min":20,"max":500}
|
166
|
+
attribute? :rth_altitude, Types::Integer
|
167
|
+
|
168
|
+
attribute? :total_flight_sorties, Types::Integer
|
65
169
|
end
|
66
170
|
end
|
67
171
|
end
|
@@ -4,6 +4,9 @@ module DjiMqttConnect
|
|
4
4
|
module Thing::Product
|
5
5
|
class RemoteOsdMessage < OsdMessage
|
6
6
|
attribute :data do
|
7
|
+
include Mixins::LatitudeConditional
|
8
|
+
include Mixins::LongitudeConditional
|
9
|
+
|
7
10
|
attribute :capacity_percent, Types::Integer
|
8
11
|
attribute :latitude, Types::Latitude
|
9
12
|
attribute :live_status do
|
@@ -14,7 +17,7 @@ module DjiMqttConnect
|
|
14
17
|
end
|
15
18
|
attribute :longitude, Types::Longitude
|
16
19
|
attribute :transmission_signal_quality, Types::Integer
|
17
|
-
attribute :wireless_link do
|
20
|
+
attribute? :wireless_link do
|
18
21
|
attribute :_4g_freq_band, Types::JSON::Decimal
|
19
22
|
attribute :_4g_gnd_quality, Types::Integer
|
20
23
|
attribute :_4g_link_state, Types::Integer
|
@@ -31,14 +34,6 @@ module DjiMqttConnect
|
|
31
34
|
attribute :frequency_band, Types::Integer
|
32
35
|
attribute :upward_quality, Types::Integer
|
33
36
|
end
|
34
|
-
|
35
|
-
def latitude?
|
36
|
-
!Types::NullInteger.valid?(latitude)
|
37
|
-
end
|
38
|
-
|
39
|
-
def longitude?
|
40
|
-
!Types::NullInteger.valid?(longitude)
|
41
|
-
end
|
42
37
|
end
|
43
38
|
end
|
44
39
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DjiMqttConnect
|
4
|
+
module Thing::Product
|
5
|
+
# https://developer.dji.com/doc/cloud-api-tutorial/en/server-api-reference/mqtt/thing-model/gateway/dock/file.html#obtain-upload-temporary-credentials
|
6
|
+
class StorageConfigGetRequestsMessage < RequestsMessage
|
7
|
+
attribute :_method, Types::String.enum("storage_config_get")
|
8
|
+
|
9
|
+
attribute :data do
|
10
|
+
attribute :module, Types::Integer.enum(0)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DjiMqttConnect
|
4
|
+
module Thing::Product
|
5
|
+
# https://developer.dji.com/doc/cloud-api-tutorial/en/server-api-reference/mqtt/thing-model/gateway/dock/file.html#obtain-upload-temporary-credentials
|
6
|
+
class StorageConfigGetRequestsReplyMessage < Message
|
7
|
+
def self.build_for(storage_config_get_message, output:, result: 0)
|
8
|
+
new(
|
9
|
+
_method: storage_config_get_message._method,
|
10
|
+
tid: storage_config_get_message.tid,
|
11
|
+
bid: storage_config_get_message.bid,
|
12
|
+
timestamp: Time.now.strftime("%s%L").to_i,
|
13
|
+
data: {
|
14
|
+
result: result,
|
15
|
+
output: output
|
16
|
+
}
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
attribute :tid, Types::UUID
|
21
|
+
attribute :bid, Types::UUID
|
22
|
+
attribute :timestamp, Types::Timestamp
|
23
|
+
|
24
|
+
attribute :_method, Types::String.enum("storage_config_get")
|
25
|
+
|
26
|
+
attribute :data do
|
27
|
+
attribute :result, Types::Integer
|
28
|
+
attribute :output do
|
29
|
+
attribute :bucket, Types::String
|
30
|
+
attribute :credentials do
|
31
|
+
attribute :access_key_id, Types::String
|
32
|
+
attribute :access_key_secret, Types::String
|
33
|
+
attribute :expire, Types::Integer
|
34
|
+
attribute :security_token, Types::String
|
35
|
+
end
|
36
|
+
attribute :endpoint, Types::String
|
37
|
+
attribute :provider, Types::String
|
38
|
+
attribute :region, Types::String
|
39
|
+
attribute :object_key_prefix, Types::String
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DjiMqttConnect
|
4
|
+
module Mixins
|
5
|
+
# Provides a conditional that returns false for nil for invalid latitude values
|
6
|
+
module LatitudeConditional
|
7
|
+
def latitude?
|
8
|
+
latitude && !Types::NullInteger.valid?(latitude)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DjiMqttConnect
|
4
|
+
module Mixins
|
5
|
+
# Provides a conditional that returns false for nil for invalid longitude values
|
6
|
+
module LongitudeConditional
|
7
|
+
def longitude?
|
8
|
+
longitude && !Types::NullInteger.valid?(longitude)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DjiMqttConnect
|
4
|
+
module Mixins
|
5
|
+
# Provides a conditional that returns false for nil or excessive values (invalid data)
|
6
|
+
module TemperatureConditional
|
7
|
+
def temperature?
|
8
|
+
temperature && !Types::InvalidTemperature.valid?(temperature)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -16,7 +16,14 @@ module DjiMqttConnect
|
|
16
16
|
message = status_marshal.load(raw_message)
|
17
17
|
|
18
18
|
logger.info("Received #{message} from #{device_identifier}")
|
19
|
-
|
19
|
+
|
20
|
+
if message.instance_of?(StatusMessage)
|
21
|
+
# Broadcast an unsupported message event
|
22
|
+
broadcast(:unsupported_message, topic, raw_message)
|
23
|
+
else
|
24
|
+
# Build event name and broadcast (i.e. ::UpdateTopoStatusMessage => update_topo)
|
25
|
+
broadcast(message._method.to_sym, device_identifier, message)
|
26
|
+
end
|
20
27
|
rescue ParseError => error
|
21
28
|
broadcast(:parse_error, error, topic, raw_message)
|
22
29
|
end
|
@@ -17,9 +17,14 @@ module DjiMqttConnect
|
|
17
17
|
|
18
18
|
logger.info("Received #{message} from #{device_identifier}")
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
if message.instance_of?(OsdMessage)
|
21
|
+
# Broadcast an unsupported message event
|
22
|
+
broadcast(:unsupported_message, topic, raw_message)
|
23
|
+
else
|
24
|
+
# Build event name and broadcast (i.e. ::RemoteOsdMessage => remote_osd_update)
|
25
|
+
event_name = message.class.name.demodulize.sub(/Message\z/, "Update").underscore.to_sym
|
26
|
+
broadcast(event_name, device_identifier, message)
|
27
|
+
end
|
23
28
|
rescue ParseError => error
|
24
29
|
broadcast(:parse_error, error, topic, raw_message)
|
25
30
|
end
|
@@ -16,7 +16,14 @@ module DjiMqttConnect
|
|
16
16
|
message = requests_marshal.load(raw_message)
|
17
17
|
|
18
18
|
logger.info("Received #{message} from #{gateway_sn}")
|
19
|
-
|
19
|
+
|
20
|
+
if message.instance_of?(RequestsMessage)
|
21
|
+
# Broadcast an unsupported message event
|
22
|
+
broadcast(:unsupported_message, topic, raw_message)
|
23
|
+
else
|
24
|
+
# Build event name and broadcast (i.e. ::ConfigRequestsMessage => config)
|
25
|
+
broadcast(message._method.to_sym, gateway_sn, message)
|
26
|
+
end
|
20
27
|
rescue ParseError => error
|
21
28
|
broadcast(:parse_error, error, topic, raw_message)
|
22
29
|
end
|
@@ -24,5 +24,8 @@ module DjiMqttConnect
|
|
24
24
|
DeviceDomain = Types::Coercible::Integer # dock returns this value as string
|
25
25
|
DeviceType = Types::Integer
|
26
26
|
DeviceSubType = Types::Integer
|
27
|
+
|
28
|
+
InvalidTemperature = Types::Integer.constrained(gteq: 1300) # maximum temp of k-type thermocouple, errors are usually 30K+
|
29
|
+
Temperature = InvalidTemperature | Types::JSON::Decimal
|
27
30
|
end
|
28
31
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/keys"
|
4
|
+
|
5
|
+
module DjiMqttConnect
|
6
|
+
module Utils
|
7
|
+
# Sanitizes MQTT messages to remove any personally-identifiable information
|
8
|
+
class MessageSanitizer
|
9
|
+
DEFAULT_LATITUDE = "-33.865143"
|
10
|
+
DEFAULT_LONGITUDE = "151.209900"
|
11
|
+
|
12
|
+
SECRET_KEYS = %w[app_id app_key app_license callsign device_secret nonce]
|
13
|
+
|
14
|
+
SERIAL_NUMBER_KEYS = %w[device_sn gateway sn]
|
15
|
+
|
16
|
+
TOPIC_REGEX = /\A.+\/.+\/(.+)\/.+/
|
17
|
+
|
18
|
+
def initialize(latitude: DEFAULT_LATITUDE, longitude: DEFAULT_LONGITUDE, secret_keys: SECRET_KEYS, serial_number_keys: SERIAL_NUMBER_KEYS, serial_number_aliases: {})
|
19
|
+
@latitude = latitude
|
20
|
+
@longitude = longitude
|
21
|
+
|
22
|
+
@secret_keys = secret_keys
|
23
|
+
@serial_number_keys = serial_number_keys
|
24
|
+
|
25
|
+
serial_aliases = serial_number_aliases.stringify_keys
|
26
|
+
@serial_number_generator = Hash.new do |h, k|
|
27
|
+
return "" if k.length == 0
|
28
|
+
h[k] = serial_aliases.fetch(k) { sprintf("%s%02d", "SERIAL", h.length + 1) }
|
29
|
+
end
|
30
|
+
serial_aliases.keys.each { |k| serial_number_generator[k] }
|
31
|
+
end
|
32
|
+
|
33
|
+
def sanitize_topic(topic)
|
34
|
+
topic.sub(TOPIC_REGEX) { |topic_match| topic_match.sub($1, serial_number_generator[$1]) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def sanitize_message(message)
|
38
|
+
sanitized_message = sanitize_object(message)
|
39
|
+
strip_serial_numbers(sanitized_message)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :latitude, :longitude
|
45
|
+
attr_reader :secret_keys, :serial_number_keys, :serial_number_generator
|
46
|
+
|
47
|
+
def sanitize_object(object)
|
48
|
+
case object
|
49
|
+
when Hash
|
50
|
+
serial_number_keys.each do |key|
|
51
|
+
if object.key?(key) && object[key].length > 0
|
52
|
+
object[key] = serial_number_generator[object[key]]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
secret_keys.each do |key|
|
56
|
+
object[key] = key.upcase if object.key?(key)
|
57
|
+
end
|
58
|
+
if object.key?("latitude")
|
59
|
+
obj_latitude = object["latitude"]
|
60
|
+
object["latitude"] = (obj_latitude.is_a?(Integer) && obj_latitude.zero?) ? 0 : latitude
|
61
|
+
end
|
62
|
+
if object.key?("longitude")
|
63
|
+
obj_longitude = object["longitude"]
|
64
|
+
object["longitude"] = (obj_longitude.is_a?(Integer) && obj_longitude.zero?) ? 0 : longitude
|
65
|
+
end
|
66
|
+
|
67
|
+
object.transform_values { |v| sanitize_object(v) }
|
68
|
+
when Array
|
69
|
+
object.map { |v| sanitize_object(v) }
|
70
|
+
else
|
71
|
+
object
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Strips known serial numbers from an object
|
76
|
+
def strip_serial_numbers(object)
|
77
|
+
case object
|
78
|
+
when Hash
|
79
|
+
object.transform_values { |v| strip_serial_numbers(v) }
|
80
|
+
when Array
|
81
|
+
object.map { |v| strip_serial_numbers(v) }
|
82
|
+
when String
|
83
|
+
object.split("-").map { |s| serial_number_generator.fetch(s, s) }.join("-")
|
84
|
+
else
|
85
|
+
object
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/wrap"
|
4
|
+
|
5
|
+
module DjiMqttConnect
|
6
|
+
module Utils
|
7
|
+
# Generates a simple message schema from a mqtt message
|
8
|
+
#
|
9
|
+
# Schema is simply the class name of any element that is not a Hash or Array
|
10
|
+
class MessageSchemaGenerator
|
11
|
+
def initialize(retain_attributes: [])
|
12
|
+
@retain_attributes = Array.wrap(retain_attributes)
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate(message)
|
16
|
+
case message
|
17
|
+
when Hash
|
18
|
+
retain_values = retain_attributes.each_with_object({}) do |attribute, hash|
|
19
|
+
next unless message.key?(attribute)
|
20
|
+
|
21
|
+
attribute_value = message[attribute]
|
22
|
+
next if attribute_value.is_a?(Hash) || attribute_value.is_a?(Array)
|
23
|
+
|
24
|
+
hash[attribute] = "#{attribute_value.class.name}:#{attribute_value}"
|
25
|
+
end
|
26
|
+
message.transform_values { |v| generate(v) }.merge(retain_values)
|
27
|
+
when Array
|
28
|
+
message.map { |v| generate(v) }
|
29
|
+
else
|
30
|
+
message.class.name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :retain_attributes
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
module DjiMqttConnect
|
6
|
+
module Utils
|
7
|
+
# Generates anonymised fixture data from MQTTX JSON Export Dumps
|
8
|
+
class MqttxFixtureDataGenerator
|
9
|
+
def self.from_file(file_path, **sanitizer_args)
|
10
|
+
raw_data = File.read(file_path)
|
11
|
+
new JSON.parse(raw_data), message_sanitizer: MessageSanitizer.new(**sanitizer_args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(mqttx_data, message_sanitizer:)
|
15
|
+
@mqttx_data = mqttx_data
|
16
|
+
@message_sanitizer = message_sanitizer
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generates fixture data using ALL found messages in the MQTTX export
|
20
|
+
#
|
21
|
+
# # Output is:
|
22
|
+
# <topic 1>: <message 1 as json>
|
23
|
+
# <topic 1>: <message 2 as json>
|
24
|
+
def generate
|
25
|
+
extracted_messages.each_with_object(StringIO.new) do |(topic, message), builder|
|
26
|
+
builder.puts [topic, message.to_json].join(": ")
|
27
|
+
end.string
|
28
|
+
end
|
29
|
+
|
30
|
+
# Generates fixture data using unique found messages in the MQTTX export
|
31
|
+
#
|
32
|
+
# A message is considered unique if the topic and schema does not match any other messages
|
33
|
+
#
|
34
|
+
# # Output is:
|
35
|
+
# <topic 1>: <message 1 as json>
|
36
|
+
def generate_unique(**schema_generator_args)
|
37
|
+
message_schema_generator = MessageSchemaGenerator.new(**schema_generator_args)
|
38
|
+
|
39
|
+
unique_messages = extracted_messages.each_with_object({}) do |(topic, message), found|
|
40
|
+
message_schema = message_schema_generator.generate(message)
|
41
|
+
found[[topic, message_schema]] ||= [topic, message]
|
42
|
+
end.values
|
43
|
+
|
44
|
+
unique_messages.each_with_object(StringIO.new) do |(topic, message), builder|
|
45
|
+
builder.puts [topic, message.to_json].join(": ")
|
46
|
+
end.string
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :mqttx_data, :message_sanitizer
|
52
|
+
|
53
|
+
def extracted_messages
|
54
|
+
return enum_for(:extracted_messages) unless block_given?
|
55
|
+
|
56
|
+
mqttx_data.each do |client_output|
|
57
|
+
client_messages = client_output["messages"]
|
58
|
+
|
59
|
+
client_messages.each do |message|
|
60
|
+
message_topic = message_sanitizer.sanitize_topic(message.fetch("topic"))
|
61
|
+
message_payload = JSON.parse message.fetch("payload")
|
62
|
+
yield(message_topic, message_sanitizer.sanitize_message(message_payload))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/dji_mqtt_connect.rb
CHANGED
@@ -34,15 +34,20 @@ module DjiMqttConnect
|
|
34
34
|
module Product
|
35
35
|
# Messages
|
36
36
|
autoload :OsdMessage, "dji_mqtt_connect/messages/thing/product/osd_message"
|
37
|
+
autoload :DockOsdMessage, "dji_mqtt_connect/messages/thing/product/dock_osd_message"
|
37
38
|
autoload :DroneOsdMessage, "dji_mqtt_connect/messages/thing/product/drone_osd_message"
|
38
39
|
autoload :RemoteOsdMessage, "dji_mqtt_connect/messages/thing/product/remote_osd_message"
|
39
40
|
|
40
41
|
autoload :AirportBindStatusRequestsMessage, "dji_mqtt_connect/messages/thing/product/airport_bind_status_requests_message"
|
42
|
+
autoload :AirportOrganizationGetRequestsMessage, "dji_mqtt_connect/messages/thing/product/airport_organization_get_requests_message"
|
41
43
|
autoload :ConfigRequestsMessage, "dji_mqtt_connect/messages/thing/product/config_requests_message"
|
44
|
+
autoload :StorageConfigGetRequestsMessage, "dji_mqtt_connect/messages/thing/product/storage_config_get_requests_message"
|
42
45
|
autoload :RequestsMessage, "dji_mqtt_connect/messages/thing/product/requests_message"
|
43
46
|
|
44
47
|
autoload :AirportBindStatusRequestsReplyMessage, "dji_mqtt_connect/messages/thing/product/airport_bind_status_requests_reply_message"
|
48
|
+
autoload :AirportOrganizationGetRequestsReplyMessage, "dji_mqtt_connect/messages/thing/product/airport_organization_get_requests_reply_message"
|
45
49
|
autoload :ConfigRequestsReplyMessage, "dji_mqtt_connect/messages/thing/product/config_requests_reply_message"
|
50
|
+
autoload :StorageConfigGetRequestsReplyMessage, "dji_mqtt_connect/messages/thing/product/storage_config_get_requests_reply_message"
|
46
51
|
|
47
52
|
# Topics
|
48
53
|
autoload :OsdTopicRepository, "dji_mqtt_connect/topics/thing/product/osd"
|
@@ -57,9 +62,19 @@ module DjiMqttConnect
|
|
57
62
|
end
|
58
63
|
end
|
59
64
|
|
65
|
+
# Mixinx
|
66
|
+
module Mixins
|
67
|
+
autoload :LatitudeConditional, "dji_mqtt_connect/mixins/latitude_conditional"
|
68
|
+
autoload :LongitudeConditional, "dji_mqtt_connect/mixins/longitude_conditional"
|
69
|
+
autoload :TemperatureConditional, "dji_mqtt_connect/mixins/temperature_conditional"
|
70
|
+
end
|
71
|
+
|
60
72
|
# Utils
|
61
73
|
module Utils
|
62
74
|
autoload :MessageParsing, "dji_mqtt_connect/utils/message_parsing"
|
75
|
+
autoload :MessageSanitizer, "dji_mqtt_connect/utils/message_sanitizer"
|
76
|
+
autoload :MessageSchemaGenerator, "dji_mqtt_connect/utils/message_schema_generator"
|
77
|
+
autoload :MqttxFixtureDataGenerator, "dji_mqtt_connect/utils/mqttx_fixture_data_generator"
|
63
78
|
end
|
64
79
|
|
65
80
|
# Railties
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dji_mqtt_connect
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sphere Drones
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -103,6 +103,7 @@ files:
|
|
103
103
|
- Gemfile.lock
|
104
104
|
- README.md
|
105
105
|
- Rakefile
|
106
|
+
- dji_mqtt_connect.gemspec
|
106
107
|
- lib/dji_mqtt_connect.rb
|
107
108
|
- lib/dji_mqtt_connect/client.rb
|
108
109
|
- lib/dji_mqtt_connect/error.rb
|
@@ -119,12 +120,20 @@ files:
|
|
119
120
|
- lib/dji_mqtt_connect/messages/sys/product/update_topo_status_reply_message.rb
|
120
121
|
- lib/dji_mqtt_connect/messages/thing/product/airport_bind_status_requests_message.rb
|
121
122
|
- lib/dji_mqtt_connect/messages/thing/product/airport_bind_status_requests_reply_message.rb
|
123
|
+
- lib/dji_mqtt_connect/messages/thing/product/airport_organization_get_requests_message.rb
|
124
|
+
- lib/dji_mqtt_connect/messages/thing/product/airport_organization_get_requests_reply_message.rb
|
122
125
|
- lib/dji_mqtt_connect/messages/thing/product/config_requests_message.rb
|
123
126
|
- lib/dji_mqtt_connect/messages/thing/product/config_requests_reply_message.rb
|
127
|
+
- lib/dji_mqtt_connect/messages/thing/product/dock_osd_message.rb
|
124
128
|
- lib/dji_mqtt_connect/messages/thing/product/drone_osd_message.rb
|
125
129
|
- lib/dji_mqtt_connect/messages/thing/product/osd_message.rb
|
126
130
|
- lib/dji_mqtt_connect/messages/thing/product/remote_osd_message.rb
|
127
131
|
- lib/dji_mqtt_connect/messages/thing/product/requests_message.rb
|
132
|
+
- lib/dji_mqtt_connect/messages/thing/product/storage_config_get_requests_message.rb
|
133
|
+
- lib/dji_mqtt_connect/messages/thing/product/storage_config_get_requests_reply_message.rb
|
134
|
+
- lib/dji_mqtt_connect/mixins/latitude_conditional.rb
|
135
|
+
- lib/dji_mqtt_connect/mixins/longitude_conditional.rb
|
136
|
+
- lib/dji_mqtt_connect/mixins/temperature_conditional.rb
|
128
137
|
- lib/dji_mqtt_connect/railtie.rb
|
129
138
|
- lib/dji_mqtt_connect/railties/message_serializer.rb
|
130
139
|
- lib/dji_mqtt_connect/topic_repository.rb
|
@@ -135,6 +144,9 @@ files:
|
|
135
144
|
- lib/dji_mqtt_connect/topics/thing/product/requests_reply.rb
|
136
145
|
- lib/dji_mqtt_connect/types.rb
|
137
146
|
- lib/dji_mqtt_connect/utils/message_parsing.rb
|
147
|
+
- lib/dji_mqtt_connect/utils/message_sanitizer.rb
|
148
|
+
- lib/dji_mqtt_connect/utils/message_schema_generator.rb
|
149
|
+
- lib/dji_mqtt_connect/utils/mqttx_fixture_data_generator.rb
|
138
150
|
- lib/dji_mqtt_connect/version.rb
|
139
151
|
- sig/dji_mqtt_connect.rbs
|
140
152
|
homepage: https://github.com/sphere-drones/dji_mqtt_connect
|