alpr-sdk 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +119 -0
- data/lib/alpr/client.rb +99 -0
- data/lib/alpr/error.rb +46 -0
- data/lib/alpr/model.rb +45 -0
- data/lib/alpr/models.rb +622 -0
- data/lib/alpr/multipart.rb +90 -0
- data/lib/alpr/page.rb +40 -0
- data/lib/alpr/params.rb +107 -0
- data/lib/alpr/services.rb +90 -0
- data/lib/alpr/version.rb +14 -0
- data/lib/alpr/webhooks.rb +169 -0
- data/lib/alpr.rb +26 -0
- metadata +53 -0
data/lib/alpr/models.rb
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Code generated by sdk/generator. DO NOT EDIT.
|
|
4
|
+
|
|
5
|
+
module Alpr
|
|
6
|
+
# Camera is a resource of Alpr.sh's API.
|
|
7
|
+
class Camera < Model
|
|
8
|
+
MEMBERS = %i[created_at enabled id location name protocol reading_area].freeze
|
|
9
|
+
|
|
10
|
+
# @return [String]
|
|
11
|
+
attr_reader :created_at
|
|
12
|
+
|
|
13
|
+
# @return [Boolean]
|
|
14
|
+
attr_reader :enabled
|
|
15
|
+
|
|
16
|
+
# @return [String]
|
|
17
|
+
attr_reader :id
|
|
18
|
+
|
|
19
|
+
# O endereço da câmera como texto; vazio se não foi informado.
|
|
20
|
+
# @return [String]
|
|
21
|
+
attr_reader :location
|
|
22
|
+
|
|
23
|
+
# @return [String]
|
|
24
|
+
attr_reader :name
|
|
25
|
+
|
|
26
|
+
# Como a câmera é lida. Nunca traz o endereço do stream, usuário nem senha.
|
|
27
|
+
# @return [String ("rtsp", "rtmp", "hls", "http", "hikconnect", "hikcloud", "rtmppush")]
|
|
28
|
+
attr_reader :protocol
|
|
29
|
+
|
|
30
|
+
# Nulo quando a câmera lê a imagem inteira.
|
|
31
|
+
# @return [ReadingArea, nil]
|
|
32
|
+
attr_reader :reading_area
|
|
33
|
+
|
|
34
|
+
def initialize(created_at:, enabled:, id:, location:, name:, protocol:, reading_area:)
|
|
35
|
+
@created_at = created_at
|
|
36
|
+
@enabled = enabled
|
|
37
|
+
@id = id
|
|
38
|
+
@location = location
|
|
39
|
+
@name = name
|
|
40
|
+
@protocol = protocol
|
|
41
|
+
@reading_area = reading_area
|
|
42
|
+
freeze
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Builds a Camera from the API's JSON.
|
|
46
|
+
def self.from_hash(data)
|
|
47
|
+
new(
|
|
48
|
+
created_at: data.fetch("createdAt"),
|
|
49
|
+
enabled: data.fetch("enabled"),
|
|
50
|
+
id: data.fetch("id"),
|
|
51
|
+
location: data.fetch("location"),
|
|
52
|
+
name: data.fetch("name"),
|
|
53
|
+
protocol: data.fetch("protocol"),
|
|
54
|
+
reading_area: data["readingArea"]&.then { |value| ReadingArea.from_hash(value) }
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# DetectedPlate is a resource of Alpr.sh's API.
|
|
60
|
+
class DetectedPlate < Model
|
|
61
|
+
MEMBERS = %i[conf coordinate_bottom coordinate_left coordinate_right coordinate_top created_at plate_type time].freeze
|
|
62
|
+
|
|
63
|
+
# Confiança do detector, de 0 a 100, com uma casa.
|
|
64
|
+
# @return [Float]
|
|
65
|
+
attr_reader :conf
|
|
66
|
+
|
|
67
|
+
# @return [Integer]
|
|
68
|
+
attr_reader :coordinate_bottom
|
|
69
|
+
|
|
70
|
+
# @return [Integer]
|
|
71
|
+
attr_reader :coordinate_left
|
|
72
|
+
|
|
73
|
+
# @return [Integer]
|
|
74
|
+
attr_reader :coordinate_right
|
|
75
|
+
|
|
76
|
+
# @return [Integer]
|
|
77
|
+
attr_reader :coordinate_top
|
|
78
|
+
|
|
79
|
+
# @return [String]
|
|
80
|
+
attr_reader :created_at
|
|
81
|
+
|
|
82
|
+
# @return [String ("car")]
|
|
83
|
+
attr_reader :plate_type
|
|
84
|
+
|
|
85
|
+
# @return [String]
|
|
86
|
+
attr_reader :time
|
|
87
|
+
|
|
88
|
+
def initialize(conf:, coordinate_bottom:, coordinate_left:, coordinate_right:, coordinate_top:, created_at:, plate_type:, time:)
|
|
89
|
+
@conf = conf
|
|
90
|
+
@coordinate_bottom = coordinate_bottom
|
|
91
|
+
@coordinate_left = coordinate_left
|
|
92
|
+
@coordinate_right = coordinate_right
|
|
93
|
+
@coordinate_top = coordinate_top
|
|
94
|
+
@created_at = created_at
|
|
95
|
+
@plate_type = plate_type
|
|
96
|
+
@time = time
|
|
97
|
+
freeze
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Builds a DetectedPlate from the API's JSON.
|
|
101
|
+
def self.from_hash(data)
|
|
102
|
+
new(
|
|
103
|
+
conf: Float(data.fetch("conf")),
|
|
104
|
+
coordinate_bottom: data.fetch("coordinateBottom"),
|
|
105
|
+
coordinate_left: data.fetch("coordinateLeft"),
|
|
106
|
+
coordinate_right: data.fetch("coordinateRight"),
|
|
107
|
+
coordinate_top: data.fetch("coordinateTop"),
|
|
108
|
+
created_at: data.fetch("createdAt"),
|
|
109
|
+
plate_type: data.fetch("plateType"),
|
|
110
|
+
time: data.fetch("time")
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# DetectionImageLink is a resource of Alpr.sh's API.
|
|
116
|
+
class DetectionImageLink < Model
|
|
117
|
+
MEMBERS = %i[detection_id expires_in image_url].freeze
|
|
118
|
+
|
|
119
|
+
# @return [String]
|
|
120
|
+
attr_reader :detection_id
|
|
121
|
+
|
|
122
|
+
# Segundos de validade do link (300).
|
|
123
|
+
# @return [Integer]
|
|
124
|
+
attr_reader :expires_in
|
|
125
|
+
|
|
126
|
+
# Endereço absoluto de GET /api/v1/detections/{id}/image/file, assinado; abra-o como veio.
|
|
127
|
+
# @return [String]
|
|
128
|
+
attr_reader :image_url
|
|
129
|
+
|
|
130
|
+
def initialize(detection_id:, expires_in:, image_url:)
|
|
131
|
+
@detection_id = detection_id
|
|
132
|
+
@expires_in = expires_in
|
|
133
|
+
@image_url = image_url
|
|
134
|
+
freeze
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Builds a DetectionImageLink from the API's JSON.
|
|
138
|
+
def self.from_hash(data)
|
|
139
|
+
new(
|
|
140
|
+
detection_id: data.fetch("detectionId"),
|
|
141
|
+
expires_in: data.fetch("expiresIn"),
|
|
142
|
+
image_url: data.fetch("imageUrl")
|
|
143
|
+
)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# PhotoDetection is a resource of Alpr.sh's API.
|
|
148
|
+
class PhotoDetection < Model
|
|
149
|
+
MEMBERS = %i[image_height image_width plates processing_time_ms].freeze
|
|
150
|
+
|
|
151
|
+
# @return [Integer]
|
|
152
|
+
attr_reader :image_height
|
|
153
|
+
|
|
154
|
+
# @return [Integer]
|
|
155
|
+
attr_reader :image_width
|
|
156
|
+
|
|
157
|
+
# Onde há placa, sem o texto; o detector não filtra pelo formato brasileiro. Sem placa, [].
|
|
158
|
+
# @return [Array<DetectedPlate>]
|
|
159
|
+
attr_reader :plates
|
|
160
|
+
|
|
161
|
+
# @return [Integer]
|
|
162
|
+
attr_reader :processing_time_ms
|
|
163
|
+
|
|
164
|
+
def initialize(image_height:, image_width:, plates:, processing_time_ms:)
|
|
165
|
+
@image_height = image_height
|
|
166
|
+
@image_width = image_width
|
|
167
|
+
@plates = plates
|
|
168
|
+
@processing_time_ms = processing_time_ms
|
|
169
|
+
freeze
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Builds a PhotoDetection from the API's JSON.
|
|
173
|
+
def self.from_hash(data)
|
|
174
|
+
new(
|
|
175
|
+
image_height: data.fetch("imageHeight"),
|
|
176
|
+
image_width: data.fetch("imageWidth"),
|
|
177
|
+
plates: data.fetch("plates").map { |item| DetectedPlate.from_hash(item) },
|
|
178
|
+
processing_time_ms: data.fetch("processingTimeMs")
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# PhotoRecognition is a resource of Alpr.sh's API.
|
|
184
|
+
class PhotoRecognition < Model
|
|
185
|
+
MEMBERS = %i[credits_remaining image_height image_width plates processing_time_ms].freeze
|
|
186
|
+
|
|
187
|
+
# Créditos que sobraram depois desta leitura.
|
|
188
|
+
# @return [Integer]
|
|
189
|
+
attr_reader :credits_remaining
|
|
190
|
+
|
|
191
|
+
# @return [Integer]
|
|
192
|
+
attr_reader :image_height
|
|
193
|
+
|
|
194
|
+
# @return [Integer]
|
|
195
|
+
attr_reader :image_width
|
|
196
|
+
|
|
197
|
+
# Só as placas válidas no Brasil (ABC1234 ou ABC1D23); sem placa, [].
|
|
198
|
+
# @return [Array<RecognizedPlate>]
|
|
199
|
+
attr_reader :plates
|
|
200
|
+
|
|
201
|
+
# @return [Integer]
|
|
202
|
+
attr_reader :processing_time_ms
|
|
203
|
+
|
|
204
|
+
def initialize(credits_remaining:, image_height:, image_width:, plates:, processing_time_ms:)
|
|
205
|
+
@credits_remaining = credits_remaining
|
|
206
|
+
@image_height = image_height
|
|
207
|
+
@image_width = image_width
|
|
208
|
+
@plates = plates
|
|
209
|
+
@processing_time_ms = processing_time_ms
|
|
210
|
+
freeze
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Builds a PhotoRecognition from the API's JSON.
|
|
214
|
+
def self.from_hash(data)
|
|
215
|
+
new(
|
|
216
|
+
credits_remaining: data.fetch("creditsRemaining"),
|
|
217
|
+
image_height: data.fetch("imageHeight"),
|
|
218
|
+
image_width: data.fetch("imageWidth"),
|
|
219
|
+
plates: data.fetch("plates").map { |item| RecognizedPlate.from_hash(item) },
|
|
220
|
+
processing_time_ms: data.fetch("processingTimeMs")
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# PlateReading is a resource of Alpr.sh's API.
|
|
226
|
+
class PlateReading < Model
|
|
227
|
+
MEMBERS = %i[confidence credits_remaining detection_id latency_ms plate].freeze
|
|
228
|
+
|
|
229
|
+
# Confiança da leitura, de 0 a 1 com quatro casas (nas outras rotas conf vai de 0 a 100).
|
|
230
|
+
# @return [Float]
|
|
231
|
+
attr_reader :confidence
|
|
232
|
+
|
|
233
|
+
# Créditos que sobraram depois desta leitura.
|
|
234
|
+
# @return [Integer]
|
|
235
|
+
attr_reader :credits_remaining
|
|
236
|
+
|
|
237
|
+
# Hoje sempre nulo: a leitura de uma placa recortada não é guardada.
|
|
238
|
+
# @return [String, nil]
|
|
239
|
+
attr_reader :detection_id
|
|
240
|
+
|
|
241
|
+
# @return [Integer]
|
|
242
|
+
attr_reader :latency_ms
|
|
243
|
+
|
|
244
|
+
# O texto lido, sem hífen, maiúsculo; não é validado pelo formato brasileiro.
|
|
245
|
+
# @return [String]
|
|
246
|
+
attr_reader :plate
|
|
247
|
+
|
|
248
|
+
def initialize(confidence:, credits_remaining:, detection_id:, latency_ms:, plate:)
|
|
249
|
+
@confidence = confidence
|
|
250
|
+
@credits_remaining = credits_remaining
|
|
251
|
+
@detection_id = detection_id
|
|
252
|
+
@latency_ms = latency_ms
|
|
253
|
+
@plate = plate
|
|
254
|
+
freeze
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Builds a PlateReading from the API's JSON.
|
|
258
|
+
def self.from_hash(data)
|
|
259
|
+
new(
|
|
260
|
+
confidence: Float(data.fetch("confidence")),
|
|
261
|
+
credits_remaining: data.fetch("creditsRemaining"),
|
|
262
|
+
detection_id: data["detectionId"],
|
|
263
|
+
latency_ms: data.fetch("latencyMs"),
|
|
264
|
+
plate: data.fetch("plate")
|
|
265
|
+
)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# ReadingArea is a resource of Alpr.sh's API.
|
|
270
|
+
class ReadingArea < Model
|
|
271
|
+
MEMBERS = %i[box points].freeze
|
|
272
|
+
|
|
273
|
+
# A caixa envolvente, em frações da imagem (0 a 1); é o que o motor lê.
|
|
274
|
+
# @return [ReadingAreaBox]
|
|
275
|
+
attr_reader :box
|
|
276
|
+
|
|
277
|
+
# Os cantos do polígono, de 3 a 16 pares [x, y] em frações da imagem; um retângulo traz os quatro cantos. Placas fora dele são ignoradas.
|
|
278
|
+
# @return [Array<Array<Float>>]
|
|
279
|
+
attr_reader :points
|
|
280
|
+
|
|
281
|
+
def initialize(box:, points:)
|
|
282
|
+
@box = box
|
|
283
|
+
@points = points
|
|
284
|
+
freeze
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Builds a ReadingArea from the API's JSON.
|
|
288
|
+
def self.from_hash(data)
|
|
289
|
+
new(
|
|
290
|
+
box: ReadingAreaBox.from_hash(data.fetch("box")),
|
|
291
|
+
points: data.fetch("points").map { |item| item.map { |item_| Float(item_) } }
|
|
292
|
+
)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# A caixa envolvente, em frações da imagem (0 a 1); é o que o motor lê.
|
|
297
|
+
class ReadingAreaBox < Model
|
|
298
|
+
MEMBERS = %i[height width x y].freeze
|
|
299
|
+
|
|
300
|
+
# @return [Float]
|
|
301
|
+
attr_reader :height
|
|
302
|
+
|
|
303
|
+
# @return [Float]
|
|
304
|
+
attr_reader :width
|
|
305
|
+
|
|
306
|
+
# @return [Float]
|
|
307
|
+
attr_reader :x
|
|
308
|
+
|
|
309
|
+
# @return [Float]
|
|
310
|
+
attr_reader :y
|
|
311
|
+
|
|
312
|
+
def initialize(height:, width:, x:, y:)
|
|
313
|
+
@height = height
|
|
314
|
+
@width = width
|
|
315
|
+
@x = x
|
|
316
|
+
@y = y
|
|
317
|
+
freeze
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Builds a ReadingAreaBox from the API's JSON.
|
|
321
|
+
def self.from_hash(data)
|
|
322
|
+
new(
|
|
323
|
+
height: Float(data.fetch("height")),
|
|
324
|
+
width: Float(data.fetch("width")),
|
|
325
|
+
x: Float(data.fetch("x")),
|
|
326
|
+
y: Float(data.fetch("y"))
|
|
327
|
+
)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Recognition is a resource of Alpr.sh's API.
|
|
332
|
+
class Recognition < Model
|
|
333
|
+
MEMBERS = %i[camera_id captured_at conf id plate source].freeze
|
|
334
|
+
|
|
335
|
+
# Nulo numa leitura feita pela API por foto.
|
|
336
|
+
# @return [String, nil]
|
|
337
|
+
attr_reader :camera_id
|
|
338
|
+
|
|
339
|
+
# Com o deslocamento do fuso do Alpr.sh.
|
|
340
|
+
# @return [String]
|
|
341
|
+
attr_reader :captured_at
|
|
342
|
+
|
|
343
|
+
# De 0 a 100, com uma casa.
|
|
344
|
+
# @return [Float]
|
|
345
|
+
attr_reader :conf
|
|
346
|
+
|
|
347
|
+
# @return [String]
|
|
348
|
+
attr_reader :id
|
|
349
|
+
|
|
350
|
+
# @return [String]
|
|
351
|
+
attr_reader :plate
|
|
352
|
+
|
|
353
|
+
# camera: lida por uma câmera; api: enviada por foto.
|
|
354
|
+
# @return [String ("camera", "api")]
|
|
355
|
+
attr_reader :source
|
|
356
|
+
|
|
357
|
+
def initialize(camera_id:, captured_at:, conf:, id:, plate:, source:)
|
|
358
|
+
@camera_id = camera_id
|
|
359
|
+
@captured_at = captured_at
|
|
360
|
+
@conf = conf
|
|
361
|
+
@id = id
|
|
362
|
+
@plate = plate
|
|
363
|
+
@source = source
|
|
364
|
+
freeze
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Builds a Recognition from the API's JSON.
|
|
368
|
+
def self.from_hash(data)
|
|
369
|
+
new(
|
|
370
|
+
camera_id: data["cameraId"],
|
|
371
|
+
captured_at: data.fetch("capturedAt"),
|
|
372
|
+
conf: Float(data.fetch("conf")),
|
|
373
|
+
id: data.fetch("id"),
|
|
374
|
+
plate: data.fetch("plate"),
|
|
375
|
+
source: data.fetch("source")
|
|
376
|
+
)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# RecognizedPlate is a resource of Alpr.sh's API.
|
|
381
|
+
class RecognizedPlate < Model
|
|
382
|
+
MEMBERS = %i[char_confs conf coordinate_bottom coordinate_left coordinate_right coordinate_top created_at plate plate_type time].freeze
|
|
383
|
+
|
|
384
|
+
# Confiança de cada caractere, de 0 a 1 com duas casas, separadas por ponto e vírgula ("0.99;0.98").
|
|
385
|
+
# @return [String]
|
|
386
|
+
attr_reader :char_confs
|
|
387
|
+
|
|
388
|
+
# Confiança da placa, de 0 a 100, com uma casa.
|
|
389
|
+
# @return [Float]
|
|
390
|
+
attr_reader :conf
|
|
391
|
+
|
|
392
|
+
# Pixels da foto enviada.
|
|
393
|
+
# @return [Integer]
|
|
394
|
+
attr_reader :coordinate_bottom
|
|
395
|
+
|
|
396
|
+
# @return [Integer]
|
|
397
|
+
attr_reader :coordinate_left
|
|
398
|
+
|
|
399
|
+
# @return [Integer]
|
|
400
|
+
attr_reader :coordinate_right
|
|
401
|
+
|
|
402
|
+
# @return [Integer]
|
|
403
|
+
attr_reader :coordinate_top
|
|
404
|
+
|
|
405
|
+
# Igual a time.
|
|
406
|
+
# @return [String]
|
|
407
|
+
attr_reader :created_at
|
|
408
|
+
|
|
409
|
+
# Sete caracteres, maiúsculos: ABC1234 ou ABC1D23 (Mercosul).
|
|
410
|
+
# @return [String]
|
|
411
|
+
attr_reader :plate
|
|
412
|
+
|
|
413
|
+
# Sempre car; a API não informa país nem formato.
|
|
414
|
+
# @return [String ("car")]
|
|
415
|
+
attr_reader :plate_type
|
|
416
|
+
|
|
417
|
+
# Instante da leitura, com o deslocamento do fuso do Alpr.sh.
|
|
418
|
+
# @return [String]
|
|
419
|
+
attr_reader :time
|
|
420
|
+
|
|
421
|
+
def initialize(char_confs:, conf:, coordinate_bottom:, coordinate_left:, coordinate_right:, coordinate_top:, created_at:, plate:, plate_type:, time:)
|
|
422
|
+
@char_confs = char_confs
|
|
423
|
+
@conf = conf
|
|
424
|
+
@coordinate_bottom = coordinate_bottom
|
|
425
|
+
@coordinate_left = coordinate_left
|
|
426
|
+
@coordinate_right = coordinate_right
|
|
427
|
+
@coordinate_top = coordinate_top
|
|
428
|
+
@created_at = created_at
|
|
429
|
+
@plate = plate
|
|
430
|
+
@plate_type = plate_type
|
|
431
|
+
@time = time
|
|
432
|
+
freeze
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# Builds a RecognizedPlate from the API's JSON.
|
|
436
|
+
def self.from_hash(data)
|
|
437
|
+
new(
|
|
438
|
+
char_confs: data.fetch("charConfs"),
|
|
439
|
+
conf: Float(data.fetch("conf")),
|
|
440
|
+
coordinate_bottom: data.fetch("coordinateBottom"),
|
|
441
|
+
coordinate_left: data.fetch("coordinateLeft"),
|
|
442
|
+
coordinate_right: data.fetch("coordinateRight"),
|
|
443
|
+
coordinate_top: data.fetch("coordinateTop"),
|
|
444
|
+
created_at: data.fetch("createdAt"),
|
|
445
|
+
plate: data.fetch("plate"),
|
|
446
|
+
plate_type: data.fetch("plateType"),
|
|
447
|
+
time: data.fetch("time")
|
|
448
|
+
)
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# WebhookAlertTriggered is a resource of Alpr.sh's API.
|
|
453
|
+
class WebhookAlertTriggered < Model
|
|
454
|
+
MEMBERS = %i[camera_id captured_at confidence event id plate recognition_id rule_id rule_name skip tenant_id].freeze
|
|
455
|
+
|
|
456
|
+
# Ausente se a leitura não veio de uma câmera.
|
|
457
|
+
# @return [String, nil]
|
|
458
|
+
attr_reader :camera_id
|
|
459
|
+
|
|
460
|
+
# @return [String]
|
|
461
|
+
attr_reader :captured_at
|
|
462
|
+
|
|
463
|
+
# De 0 a 1.
|
|
464
|
+
# @return [Float]
|
|
465
|
+
attr_reader :confidence
|
|
466
|
+
|
|
467
|
+
# @return [String ("alert.triggered")]
|
|
468
|
+
attr_reader :event
|
|
469
|
+
|
|
470
|
+
# O id do disparo.
|
|
471
|
+
# @return [String]
|
|
472
|
+
attr_reader :id
|
|
473
|
+
|
|
474
|
+
# @return [String]
|
|
475
|
+
attr_reader :plate
|
|
476
|
+
|
|
477
|
+
# A leitura que disparou o alerta.
|
|
478
|
+
# @return [String]
|
|
479
|
+
attr_reader :recognition_id
|
|
480
|
+
|
|
481
|
+
# @return [String]
|
|
482
|
+
attr_reader :rule_id
|
|
483
|
+
|
|
484
|
+
# @return [String]
|
|
485
|
+
attr_reader :rule_name
|
|
486
|
+
|
|
487
|
+
# Os destinos que as listas de permitidos e de bloqueados excluíram deste disparo (internal, channel:<id>, partner:<id>). Presente só quando algum foi excluído; é informação, não há o que fazer com ela.
|
|
488
|
+
# @return [Array<String>, nil]
|
|
489
|
+
attr_reader :skip
|
|
490
|
+
|
|
491
|
+
# @return [String]
|
|
492
|
+
attr_reader :tenant_id
|
|
493
|
+
|
|
494
|
+
def initialize(camera_id: nil, captured_at:, confidence:, event:, id:, plate:, recognition_id:, rule_id:, rule_name:, skip: nil, tenant_id:)
|
|
495
|
+
@camera_id = camera_id
|
|
496
|
+
@captured_at = captured_at
|
|
497
|
+
@confidence = confidence
|
|
498
|
+
@event = event
|
|
499
|
+
@id = id
|
|
500
|
+
@plate = plate
|
|
501
|
+
@recognition_id = recognition_id
|
|
502
|
+
@rule_id = rule_id
|
|
503
|
+
@rule_name = rule_name
|
|
504
|
+
@skip = skip
|
|
505
|
+
@tenant_id = tenant_id
|
|
506
|
+
freeze
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# Builds a WebhookAlertTriggered from the API's JSON.
|
|
510
|
+
def self.from_hash(data)
|
|
511
|
+
new(
|
|
512
|
+
camera_id: data["camera_id"],
|
|
513
|
+
captured_at: data.fetch("captured_at"),
|
|
514
|
+
confidence: Float(data.fetch("confidence")),
|
|
515
|
+
event: data.fetch("event"),
|
|
516
|
+
id: data.fetch("id"),
|
|
517
|
+
plate: data.fetch("plate"),
|
|
518
|
+
recognition_id: data.fetch("recognition_id"),
|
|
519
|
+
rule_id: data.fetch("rule_id"),
|
|
520
|
+
rule_name: data.fetch("rule_name"),
|
|
521
|
+
skip: data["skip"],
|
|
522
|
+
tenant_id: data.fetch("tenant_id")
|
|
523
|
+
)
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# WebhookRecognitionCreated is a resource of Alpr.sh's API.
|
|
528
|
+
class WebhookRecognitionCreated < Model
|
|
529
|
+
MEMBERS = %i[camera_id captured_at confidence event id plate source tenant_id].freeze
|
|
530
|
+
|
|
531
|
+
# Ausente numa leitura enviada por foto.
|
|
532
|
+
# @return [String, nil]
|
|
533
|
+
attr_reader :camera_id
|
|
534
|
+
|
|
535
|
+
# UTC, com fração de segundo.
|
|
536
|
+
# @return [String]
|
|
537
|
+
attr_reader :captured_at
|
|
538
|
+
|
|
539
|
+
# De 0 a 1 (na API de chave, conf vai de 0 a 100).
|
|
540
|
+
# @return [Float]
|
|
541
|
+
attr_reader :confidence
|
|
542
|
+
|
|
543
|
+
# @return [String ("recognition.created")]
|
|
544
|
+
attr_reader :event
|
|
545
|
+
|
|
546
|
+
# O id da leitura; é o mesmo id de GET /api/v1/recognitions e de GET /api/v1/detections/{id}/image.
|
|
547
|
+
# @return [String]
|
|
548
|
+
attr_reader :id
|
|
549
|
+
|
|
550
|
+
# @return [String]
|
|
551
|
+
attr_reader :plate
|
|
552
|
+
|
|
553
|
+
# camera: lida por uma câmera; api: enviada por foto.
|
|
554
|
+
# @return [String ("camera", "api")]
|
|
555
|
+
attr_reader :source
|
|
556
|
+
|
|
557
|
+
# O espaço de trabalho dono da leitura.
|
|
558
|
+
# @return [String]
|
|
559
|
+
attr_reader :tenant_id
|
|
560
|
+
|
|
561
|
+
def initialize(camera_id: nil, captured_at:, confidence:, event:, id:, plate:, source:, tenant_id:)
|
|
562
|
+
@camera_id = camera_id
|
|
563
|
+
@captured_at = captured_at
|
|
564
|
+
@confidence = confidence
|
|
565
|
+
@event = event
|
|
566
|
+
@id = id
|
|
567
|
+
@plate = plate
|
|
568
|
+
@source = source
|
|
569
|
+
@tenant_id = tenant_id
|
|
570
|
+
freeze
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# Builds a WebhookRecognitionCreated from the API's JSON.
|
|
574
|
+
def self.from_hash(data)
|
|
575
|
+
new(
|
|
576
|
+
camera_id: data["camera_id"],
|
|
577
|
+
captured_at: data.fetch("captured_at"),
|
|
578
|
+
confidence: Float(data.fetch("confidence")),
|
|
579
|
+
event: data.fetch("event"),
|
|
580
|
+
id: data.fetch("id"),
|
|
581
|
+
plate: data.fetch("plate"),
|
|
582
|
+
source: data.fetch("source"),
|
|
583
|
+
tenant_id: data.fetch("tenant_id")
|
|
584
|
+
)
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# WebhookTest is a resource of Alpr.sh's API.
|
|
589
|
+
class WebhookTest < Model
|
|
590
|
+
MEMBERS = %i[event id sent_at tenant_id].freeze
|
|
591
|
+
|
|
592
|
+
# @return [String ("webhook.test")]
|
|
593
|
+
attr_reader :event
|
|
594
|
+
|
|
595
|
+
# @return [String]
|
|
596
|
+
attr_reader :id
|
|
597
|
+
|
|
598
|
+
# @return [String]
|
|
599
|
+
attr_reader :sent_at
|
|
600
|
+
|
|
601
|
+
# @return [String]
|
|
602
|
+
attr_reader :tenant_id
|
|
603
|
+
|
|
604
|
+
def initialize(event:, id:, sent_at:, tenant_id:)
|
|
605
|
+
@event = event
|
|
606
|
+
@id = id
|
|
607
|
+
@sent_at = sent_at
|
|
608
|
+
@tenant_id = tenant_id
|
|
609
|
+
freeze
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# Builds a WebhookTest from the API's JSON.
|
|
613
|
+
def self.from_hash(data)
|
|
614
|
+
new(
|
|
615
|
+
event: data.fetch("event"),
|
|
616
|
+
id: data.fetch("id"),
|
|
617
|
+
sent_at: data.fetch("sent_at"),
|
|
618
|
+
tenant_id: data.fetch("tenant_id")
|
|
619
|
+
)
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Alpr
|
|
6
|
+
# A multipart/form-data body, written by hand: one file and the text fields
|
|
7
|
+
# beside it.
|
|
8
|
+
class Form
|
|
9
|
+
# The Content-Type and file name of a photo, told by its first bytes: JPEG
|
|
10
|
+
# and PNG are what the API reads, and anything else goes as bytes for the
|
|
11
|
+
# API to refuse.
|
|
12
|
+
JPEG_MAGIC = "\xFF\xD8\xFF".b.freeze
|
|
13
|
+
PNG_MAGIC = "\x89PNG\r\n\x1A\n".b.freeze
|
|
14
|
+
|
|
15
|
+
def self.image_type(data)
|
|
16
|
+
head = data.byteslice(0, 8).to_s.b
|
|
17
|
+
return ["image/jpeg", "photo.jpg"] if head.start_with?(JPEG_MAGIC)
|
|
18
|
+
return ["image/png", "photo.png"] if head.start_with?(PNG_MAGIC)
|
|
19
|
+
|
|
20
|
+
["application/octet-stream", "photo"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# A number, boolean or string as the text of a field: the shortest decimal
|
|
24
|
+
# form, 0.5 and 1, not 0.50 or 1.0.
|
|
25
|
+
def self.text_of(value)
|
|
26
|
+
return value.to_s unless value.is_a?(Float) && value.finite?
|
|
27
|
+
|
|
28
|
+
value == value.floor && value.abs < 1e15 ? value.to_i.to_s : value.to_s.sub(/\.0e/, "e")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize
|
|
32
|
+
@parts = []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Adds a text field.
|
|
36
|
+
def text(name, value)
|
|
37
|
+
@parts << [:text, name.to_s, Form.text_of(value).b]
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Adds the photo, named and typed by what its first bytes say it is.
|
|
42
|
+
def file(name, data)
|
|
43
|
+
raise ArgumentError, "alpr: #{name} is the bytes of a photo (a String), not #{data.class}" unless data.is_a?(String)
|
|
44
|
+
|
|
45
|
+
@parts << [:file, name.to_s, data.b]
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The body, as bytes.
|
|
50
|
+
def body
|
|
51
|
+
encode.first
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The Content-Type header, boundary included.
|
|
55
|
+
def content_type
|
|
56
|
+
"multipart/form-data; boundary=#{encode.last}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# Writes the body once, with a boundary no part contains.
|
|
62
|
+
def encode
|
|
63
|
+
@encoded ||= begin
|
|
64
|
+
boundary = nil
|
|
65
|
+
loop do
|
|
66
|
+
boundary = "alpr-#{SecureRandom.hex(16)}".b
|
|
67
|
+
break unless @parts.any? { |_, _, data| data.include?(boundary) }
|
|
68
|
+
end
|
|
69
|
+
out = "".b
|
|
70
|
+
@parts.each do |kind, name, data|
|
|
71
|
+
out << "--".b << boundary << "\r\n".b
|
|
72
|
+
if kind == :file
|
|
73
|
+
type, filename = Form.image_type(data)
|
|
74
|
+
out << %(Content-Disposition: form-data; name="#{quote(name)}"; filename="#{filename}"\r\n).b
|
|
75
|
+
out << "Content-Type: #{type}\r\n".b
|
|
76
|
+
else
|
|
77
|
+
out << %(Content-Disposition: form-data; name="#{quote(name)}"\r\n).b
|
|
78
|
+
end
|
|
79
|
+
out << "\r\n".b << data << "\r\n".b
|
|
80
|
+
end
|
|
81
|
+
out << "--".b << boundary << "--\r\n".b
|
|
82
|
+
[out, boundary]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def quote(name)
|
|
87
|
+
name.gsub(/["\r\n]/) { |char| format("%%%02X", char.ord) }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|