Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

as: Sync normalized payload data model and validation to the latest schema #6154

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 96 additions & 3 deletions pkg/messageprocessors/normalizedpayload/uplink.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,25 @@ import (
"google.golang.org/protobuf/types/known/structpb"
)

// Soil is an soil measurement.
type Soil struct {
Depth *float64
Moisture *float64
Temperature *float64
ElectricalConductivity *float64
PHLevel *float64
Nitrogen *float64
Phosphorus *float64
Potassium *float64
}

// Air is an air measurement.
type Air struct {
Temperature *float64
RelativeHumidity *float64
Pressure *float64
CO2 *float64
LightIntensity *float64
}

// Wind is a wind measurement.
Expand All @@ -40,6 +54,7 @@ type Wind struct {
// Measurement is a measurement.
type Measurement struct {
Time *time.Time
Soil Soil
Air Air
Wind Wind
}
Expand Down Expand Up @@ -126,6 +141,24 @@ func parseNumber(selector func(dst *Measurement) **float64, vals ...fieldValidat
}
}

// parsePercentage parses and validates a percentage.
func parsePercentage(selector func(dst *Measurement) **float64) fieldParser {
return parseNumber(
selector,
minimum(0.0),
maximum(100.0),
)
}

// parseConcentration parses and validates a concentration. Concentration must be in ppm between 0 and 1000000.
func parseConcentration(selector func(dst *Measurement) **float64) fieldParser {
return parseNumber(
selector,
minimum(0.0),
maximum(1000000.0),
)
}

// minimum returns a field validator that checks the inclusive minimum.
func minimum[T constraints.Ordered](min T) fieldValidator[T] {
return func(v T, path string) error {
Expand Down Expand Up @@ -186,6 +219,57 @@ var fieldParsers = map[string]fieldParser{
return &dst.Time
},
),
"soil": object(
func(dst *Measurement) *Soil {
return &dst.Soil
},
),
"soil.depth": parseNumber(
func(dst *Measurement) **float64 {
return &dst.Soil.Depth
},
minimum(0.0),
),
"soil.moisture": parsePercentage(
func(dst *Measurement) **float64 {
return &dst.Soil.Moisture
},
),
"soil.temperature": parseNumber(
func(dst *Measurement) **float64 {
return &dst.Soil.Temperature
},
minimum(-273.15),
),
"soil.ec": parseNumber(
func(dst *Measurement) **float64 {
return &dst.Soil.ElectricalConductivity
},
minimum(0.0),
maximum(621.0),
),
"soil.pH": parseNumber(
func(dst *Measurement) **float64 {
return &dst.Soil.PHLevel
},
minimum(0.0),
maximum(14.0),
),
"soil.n": parseConcentration(
func(dst *Measurement) **float64 {
return &dst.Soil.Nitrogen
},
),
"soil.p": parseConcentration(
func(dst *Measurement) **float64 {
return &dst.Soil.Phosphorus
},
),
"soil.k": parseConcentration(
func(dst *Measurement) **float64 {
return &dst.Soil.Potassium
},
),
"air": object(
func(dst *Measurement) *Air {
return &dst.Air
Expand All @@ -197,12 +281,10 @@ var fieldParsers = map[string]fieldParser{
},
minimum(-273.15),
),
"air.relativeHumidity": parseNumber(
"air.relativeHumidity": parsePercentage(
func(dst *Measurement) **float64 {
return &dst.Air.RelativeHumidity
},
minimum(0.0),
maximum(100.0),
),
"air.pressure": parseNumber(
func(dst *Measurement) **float64 {
Expand All @@ -211,6 +293,17 @@ var fieldParsers = map[string]fieldParser{
minimum(900.0),
maximum(1100.0),
),
"air.co2": parseConcentration(
func(dst *Measurement) **float64 {
return &dst.Air.CO2
},
),
"air.lightIntensity": parseNumber(
func(dst *Measurement) **float64 {
return &dst.Air.LightIntensity
},
minimum(0.0),
),
"wind": object(
func(dst *Measurement) *Wind {
return &dst.Wind
Expand Down
62 changes: 62 additions & 0 deletions pkg/messageprocessors/normalizedpayload/uplink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,35 @@ func TestUplink(t *testing.T) {
},
},
},
{
name: "one soil nutrient concentration",
normalizedPayload: []*structpb.Struct{
{
Fields: map[string]*structpb.Value{
"soil": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
"n": {
Kind: &structpb.Value_NumberValue{
NumberValue: 999999.99,
},
},
},
},
},
},
},
},
},
expected: []normalizedpayload.Measurement{
{
Soil: normalizedpayload.Soil{
Nitrogen: float64Ptr(999999.99),
},
},
},
},
{
name: "two air temperatures",
normalizedPayload: []*structpb.Struct{
Expand Down Expand Up @@ -122,6 +151,39 @@ func TestUplink(t *testing.T) {
{},
},
},
{
name: "above 100 percent soil moisture",
normalizedPayload: []*structpb.Struct{
{
Fields: map[string]*structpb.Value{
"soil": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
"moisture": {
Kind: &structpb.Value_NumberValue{
NumberValue: 120,
},
},
},
},
},
},
},
},
},
expected: []normalizedpayload.Measurement{
{},
},
expectedValidationErrors: [][]error{
{
normalizedpayload.ErrFieldMaximum.WithAttributes(
"path", "soil.moisture",
"maximum", 100.0,
),
},
},
},
{
name: "below absolute zero",
normalizedPayload: []*structpb.Struct{
Expand Down