Skip to content

Commit 4333796

Browse files
Merge pull request #7672 from TheThingsNetwork/feature/normalized-payload-rain
Extend normalized payload
2 parents c1f3787 + 390fbcf commit 4333796

File tree

6 files changed

+646
-4
lines changed

6 files changed

+646
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ For details about compatibility between different releases, see the **Commitment
1111

1212
### Added
1313

14+
- Parsers for newly added normalized payload fields.
15+
1416
### Changed
1517

1618
### Deprecated

config/messages.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7748,6 +7748,15 @@
77487748
"file": "uplink.go"
77497749
}
77507750
},
7751+
"error:pkg/messageprocessors/normalizedpayload:field_not_allowed": {
7752+
"translations": {
7753+
"en": "`{path}` should be one of `{allowed}`"
7754+
},
7755+
"description": {
7756+
"package": "pkg/messageprocessors/normalizedpayload",
7757+
"file": "uplink.go"
7758+
}
7759+
},
77517760
"error:pkg/messageprocessors/normalizedpayload:field_type": {
77527761
"translations": {
77537762
"en": "invalid field type of `{path}`"

pkg/messageprocessors/normalizedpayload/uplink.go

Lines changed: 202 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ type Air struct {
4343
Pressure *float64
4444
CO2 *float64
4545
LightIntensity *float64
46+
Location *string
47+
}
48+
49+
// Motion is a motion measurement.
50+
type Motion struct {
51+
Detected *bool
52+
Count *float64
53+
}
54+
55+
// Action is an action measurement.
56+
type Action struct {
57+
Motion Motion
58+
ContactState *string
4659
}
4760

4861
// Wind is a wind measurement.
@@ -51,12 +64,46 @@ type Wind struct {
5164
Direction *float64
5265
}
5366

67+
// Rain is a rain measurement.
68+
type Rain struct {
69+
Intensity *float64
70+
Cumulative *float64
71+
}
72+
73+
// Water is a water measurement.
74+
type Water struct {
75+
Leak *bool
76+
Temperature *float64
77+
}
78+
79+
// WaterMetering is a water metering measurement.
80+
type WaterMetering struct {
81+
Total *float64
82+
}
83+
84+
// Metering is a metering measurement.
85+
type Metering struct {
86+
Water WaterMetering
87+
}
88+
89+
// Position is a position measurement.
90+
type Position struct {
91+
Latitude *float64
92+
Longitude *float64
93+
}
94+
5495
// Measurement is a measurement.
5596
type Measurement struct {
56-
Time *time.Time
57-
Soil Soil
58-
Air Air
59-
Wind Wind
97+
Time *time.Time
98+
Battery *float64
99+
Soil Soil
100+
Air Air
101+
Wind Wind
102+
Rain Rain
103+
Water Water
104+
Metering Metering
105+
Action Action
106+
Position Position
60107
}
61108

62109
var (
@@ -65,6 +112,10 @@ var (
65112
"field_minimum",
66113
"`{path}` should be equal or greater than `{minimum}`",
67114
)
115+
errFieldNotAllowed = errors.DefineDataLoss(
116+
"field_not_allowed",
117+
"`{path}` should be one of `{allowed}`",
118+
)
68119
//nolint:unused
69120
errFieldExclusiveMinimum = errors.DefineDataLoss(
70121
"field_exclusive_minimum",
@@ -141,6 +192,29 @@ func parseNumber(selector func(dst *Measurement) **float64, vals ...fieldValidat
141192
}
142193
}
143194

195+
// parseString parses and validates a string.
196+
func parseString(selector func(dst *Measurement) **string, allowed []string) fieldParser {
197+
return func(dst *Measurement, src *structpb.Value, path string) []error {
198+
val, ok := src.Kind.(*structpb.Value_StringValue)
199+
if !ok {
200+
return []error{errFieldType.WithAttributes("path", path)}
201+
}
202+
p := val.StringValue
203+
for _, e := range allowed {
204+
if p == e {
205+
*selector(dst) = &p
206+
return nil
207+
}
208+
}
209+
return []error{
210+
errFieldNotAllowed.WithAttributes(
211+
"path", path,
212+
"allowed", allowed,
213+
),
214+
}
215+
}
216+
}
217+
144218
// parsePercentage parses and validates a percentage.
145219
func parsePercentage(selector func(dst *Measurement) **float64) fieldParser {
146220
return parseNumber(
@@ -159,6 +233,18 @@ func parseConcentration(selector func(dst *Measurement) **float64) fieldParser {
159233
)
160234
}
161235

236+
// parseBoolean parses a boolean.
237+
func parseBoolean(selector func(dst *Measurement) **bool) fieldParser {
238+
return func(dst *Measurement, src *structpb.Value, path string) []error {
239+
val, ok := src.Kind.(*structpb.Value_BoolValue)
240+
if !ok {
241+
return []error{errFieldType.WithAttributes("path", path)}
242+
}
243+
*selector(dst) = &val.BoolValue
244+
return nil
245+
}
246+
}
247+
162248
// minimum returns a field validator that checks the inclusive minimum.
163249
func minimum[T constraints.Ordered](min T) fieldValidator[T] {
164250
return func(v T, path string) error {
@@ -214,6 +300,12 @@ func exclusiveMaximum[T constraints.Ordered](max T) fieldValidator[T] {
214300
}
215301

216302
var fieldParsers = map[string]fieldParser{
303+
"battery": parseNumber(
304+
func(dst *Measurement) **float64 {
305+
return &dst.Battery
306+
},
307+
minimum(0.0),
308+
),
217309
"time": parseTime(
218310
func(dst *Measurement) **time.Time {
219311
return &dst.Time
@@ -304,6 +396,15 @@ var fieldParsers = map[string]fieldParser{
304396
},
305397
minimum(0.0),
306398
),
399+
"air.location": parseString(
400+
func(dst *Measurement) **string {
401+
return &dst.Air.Location
402+
},
403+
[]string{
404+
"indoor",
405+
"outdoor",
406+
},
407+
),
307408
"wind": object(
308409
func(dst *Measurement) *Wind {
309410
return &dst.Wind
@@ -322,6 +423,103 @@ var fieldParsers = map[string]fieldParser{
322423
minimum(0.0),
323424
exclusiveMaximum(360.0),
324425
),
426+
"rain": object(
427+
func(dst *Measurement) *Rain {
428+
return &dst.Rain
429+
},
430+
),
431+
"rain.intensity": parseNumber(
432+
func(dst *Measurement) **float64 {
433+
return &dst.Rain.Intensity
434+
},
435+
minimum(0.0),
436+
),
437+
"rain.cumulative": parseNumber(
438+
func(dst *Measurement) **float64 {
439+
return &dst.Rain.Cumulative
440+
},
441+
minimum(0.0),
442+
),
443+
"water": object(
444+
func(dst *Measurement) *Water {
445+
return &dst.Water
446+
},
447+
),
448+
"water.leak": parseBoolean(
449+
func(dst *Measurement) **bool {
450+
return &dst.Water.Leak
451+
},
452+
),
453+
"water.temperature": parseNumber(
454+
func(dst *Measurement) **float64 {
455+
return &dst.Water.Temperature
456+
},
457+
),
458+
"metering": object(
459+
func(dst *Measurement) *Metering {
460+
return &dst.Metering
461+
},
462+
),
463+
"metering.water": object(
464+
func(dst *Measurement) *WaterMetering {
465+
return &dst.Metering.Water
466+
},
467+
),
468+
"metering.water.total": parseNumber(
469+
func(dst *Measurement) **float64 {
470+
return &dst.Metering.Water.Total
471+
},
472+
minimum(0.0),
473+
),
474+
"action": object(
475+
func(dst *Measurement) *Action {
476+
return &dst.Action
477+
},
478+
),
479+
"action.motion": object(
480+
func(dst *Measurement) *Motion {
481+
return &dst.Action.Motion
482+
},
483+
),
484+
"action.motion.detected": parseBoolean(
485+
func(dst *Measurement) **bool {
486+
return &dst.Action.Motion.Detected
487+
},
488+
),
489+
"action.motion.count": parseNumber(
490+
func(dst *Measurement) **float64 {
491+
return &dst.Action.Motion.Count
492+
},
493+
minimum(0.0),
494+
),
495+
"action.contactState": parseString(
496+
func(dst *Measurement) **string {
497+
return &dst.Action.ContactState
498+
},
499+
[]string{
500+
"open",
501+
"closed",
502+
},
503+
),
504+
"position": object(
505+
func(dst *Measurement) *Position {
506+
return &dst.Position
507+
},
508+
),
509+
"position.latitude": parseNumber(
510+
func(dst *Measurement) **float64 {
511+
return &dst.Position.Latitude
512+
},
513+
minimum(-90.0),
514+
maximum(90.0),
515+
),
516+
"position.longitude": parseNumber(
517+
func(dst *Measurement) **float64 {
518+
return &dst.Position.Longitude
519+
},
520+
minimum(-180.0),
521+
maximum(180.0),
522+
),
325523
}
326524

327525
// ParsedMeasurement is the result of parsing measurements with Parse.

pkg/messageprocessors/normalizedpayload/uplink_internal_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ var (
1919
ErrFieldExclusiveMinimum = errFieldExclusiveMinimum
2020
ErrFieldMaximum = errFieldMaximum
2121
ErrFieldExclusiveMaximum = errFieldExclusiveMaximum
22+
ErrFieldNotAllowed = errFieldNotAllowed
2223
)

0 commit comments

Comments
 (0)