From 01be753a6a45d021325b90927ee2e5048f38c2a1 Mon Sep 17 00:00:00 2001 From: Simon Wegendt Date: Fri, 1 Mar 2019 13:07:45 +0100 Subject: [PATCH] LoRa fixes (#288) * Fixed duplicate datastructures defined for cayenne and added LoRa to the documentation. * Implement ADR * Add configuration for EU/US module * Validate configuration ranges --- .../eclipse/mita/base/util/BaseUtils.xtend | 8 +- .../inferrer/StaticValueInferrer.xtend | 5 ++ .../1.0.0/xdk110.platform | 18 +++- .../xdk110/connectivity/LoraGenerator.xtend | 73 ++++++++++------ .../xdk110/connectivity/LoraValidator.xtend | 61 ++++++++++++- website/site/content/platforms/xdk110.md | 86 ++++++++++++++++++- 6 files changed, 219 insertions(+), 32 deletions(-) diff --git a/bundles/org.eclipse.mita.base/src/org/eclipse/mita/base/util/BaseUtils.xtend b/bundles/org.eclipse.mita.base/src/org/eclipse/mita/base/util/BaseUtils.xtend index 5100a162..a630b436 100644 --- a/bundles/org.eclipse.mita.base/src/org/eclipse/mita/base/util/BaseUtils.xtend +++ b/bundles/org.eclipse.mita.base/src/org/eclipse/mita/base/util/BaseUtils.xtend @@ -25,5 +25,11 @@ class BaseUtils { return zip(xs.iterator, ys.iterator); } } -} + } + def static T castOrNull(Object o, Class clazz) { + if(clazz.isInstance(o)) { + return clazz.cast(o); + } + return null; + } } \ No newline at end of file diff --git a/bundles/org.eclipse.mita.program/src/org/eclipse/mita/program/inferrer/StaticValueInferrer.xtend b/bundles/org.eclipse.mita.program/src/org/eclipse/mita/program/inferrer/StaticValueInferrer.xtend index a5e8729d..26f41e07 100644 --- a/bundles/org.eclipse.mita.program/src/org/eclipse/mita/program/inferrer/StaticValueInferrer.xtend +++ b/bundles/org.eclipse.mita.program/src/org/eclipse/mita/program/inferrer/StaticValueInferrer.xtend @@ -34,6 +34,7 @@ import org.eclipse.mita.base.types.SumAlternative import org.eclipse.mita.base.types.SumType import org.eclipse.mita.base.util.BaseUtils import org.eclipse.mita.program.ArrayLiteral +import org.eclipse.mita.program.ConfigurationItemValue import org.eclipse.mita.program.ValueRange import org.eclipse.mita.program.VariableDeclaration import org.eclipse.mita.program.model.ModelUtils @@ -165,6 +166,10 @@ class StaticValueInferrer { return #[lower, upper]; } + static dispatch def Object infer(ConfigurationItemValue configItemValue, (EObject) => void inferenceBlockerAcceptor) { + configItemValue.value.infer(inferenceBlockerAcceptor); + } + static dispatch def Object infer(Void expression, (EObject) => void inferenceBlockerAcceptor) { inferenceBlockerAcceptor.apply(null); return null; diff --git a/platforms/org.eclipse.mita.platform.xdk110/1.0.0/xdk110.platform b/platforms/org.eclipse.mita.platform.xdk110/1.0.0/xdk110.platform index b293147d..e73314eb 100644 --- a/platforms/org.eclipse.mita.platform.xdk110/1.0.0/xdk110.platform +++ b/platforms/org.eclipse.mita.platform.xdk110/1.0.0/xdk110.platform @@ -703,16 +703,30 @@ alt CayennePayload { | GpsLocation: {Latitude: int32, Longitude: int32, Altitude: int32} } +enum Region { + EU, US +} + connectivity named-singleton LoRa { generator "org.eclipse.mita.platform.xdk110.connectivity.LoraGenerator" validator "org.eclipse.mita.platform.xdk110.connectivity.LoraValidator" + required configuration-item region: Region required configuration-item loraAppKey: array required configuration-item loraAppEui: array + configuration-item loraDeviceEui: array + configuration-item adaptiveDataRate: bool = true + + configuration-item bandFrequency: uint16 + configuration-item rx2Frequency: uint32 + configuration-item rx2DataRate: uint8 + configuration-item dataRate: uint8 = 3 + + - signal raw(portNum: uint8 = 1, confirmation: LoRaMessageConfirmation = LoRaMessageConfirmation.Confirmed): array - signal cayenne(portNum: uint8 = 1, confirmation: LoRaMessageConfirmation = LoRaMessageConfirmation.Confirmed): array + signal raw(portNum: uint8 = 1, confirmation: LoRaMessageConfirmation = LoRaMessageConfirmation.Unconfirmed): array + signal cayenne(portNum: uint8 = 1, confirmation: LoRaMessageConfirmation = LoRaMessageConfirmation.Unconfirmed): array } /** diff --git a/platforms/org.eclipse.mita.platform.xdk110/src/org/eclipse/mita/platform/xdk110/connectivity/LoraGenerator.xtend b/platforms/org.eclipse.mita.platform.xdk110/src/org/eclipse/mita/platform/xdk110/connectivity/LoraGenerator.xtend index a6234481..4a8c3d2d 100644 --- a/platforms/org.eclipse.mita.platform.xdk110/src/org/eclipse/mita/platform/xdk110/connectivity/LoraGenerator.xtend +++ b/platforms/org.eclipse.mita.platform.xdk110/src/org/eclipse/mita/platform/xdk110/connectivity/LoraGenerator.xtend @@ -101,6 +101,28 @@ class LoraGenerator extends AbstractSystemResourceGenerator { Retcode_RaiseError(retcode); } } + «IF setup.signalInstances.exists[it.instanceOf.name == "cayenne"]» + // required data structures for cayenne + // translation CayennePayload_enum -> cayenne used buffer size in bytes + static const uint8_t payloadSizes[] = { + 3, 3, 4, 4, 4, 3, 4, 3, 8, 4, 8, 11 + }; + // translation CayennePayload_enum -> CayenneLPPSerializer_DataType_T + static const CayenneLPPSerializer_DataType_T cayenneDataTypes[] = { + CAYENNE_LLP_SERIALIZER_DIGITAL_INPUT, + CAYENNE_LLP_SERIALIZER_DIGITAL_OUTPUT, + CAYENNE_LLP_SERIALIZER_ANALOG_INPUT, + CAYENNE_LLP_SERIALIZER_ANALOG_OUTPUT, + CAYENNE_LLP_SERIALIZER_ILLUMINANCE_SENSOR, + CAYENNE_LLP_SERIALIZER_PRESENCE_SENSOR, + CAYENNE_LLP_SERIALIZER_TEMPERATURE_SENSOR, + CAYENNE_LLP_SERIALIZER_HUMIDITY_SENSOR, + CAYENNE_LLP_SERIALIZER_ACCELEROMETER, + CAYENNE_LLP_SERIALIZER_BAROMETER, + CAYENNE_LLP_SERIALIZER_GYROMETER, + CAYENNE_LLP_SERIALIZER_GPS_LOCATION + }; + «ENDIF» ''' ) .addHeader("FreeRTOS.h", true, IncludePath.HIGH_PRIORITY) @@ -109,17 +131,37 @@ class LoraGenerator extends AbstractSystemResourceGenerator { } + protected val defaultValues = #{ + "EU" -> #{ + "bandFrequency" -> 868, + "rx2Frequency" -> 869525, + "rx2DataRate" -> 0, + "dataRate" -> 3 + }, + "US" -> #{ + "bandFrequency" -> 915, + "rx2Frequency" -> 923300, + "rx2DataRate" -> 8, + "dataRate" -> 3 + } + } + override generateEnable() { val appEui = StaticValueInferrer.infer(configuration.getExpression("loraAppEui"), []); val appKey = StaticValueInferrer.infer(configuration.getExpression("loraAppKey"), []); val deviceEui = StaticValueInferrer.infer(configuration.getExpression("loraDeviceEui"), []); + val regionName = configuration.getEnumerator("region")?.name; + val bandFrequency = configuration.getInteger("bandFrequency") ?: defaultValues.get(regionName)?.get("bandFrequency"); + val rx2Frequency = configuration.getInteger("rx2Frequency" ) ?: defaultValues.get(regionName)?.get("rx2Frequency" ); + val rx2DataRate = configuration.getInteger("rx2DataRate" ) ?: defaultValues.get(regionName)?.get("rx2DataRate" ); + val dataRate = configuration.getInteger("dataRate" ) ?: defaultValues.get(regionName)?.get("dataRate" ); if(appEui instanceof List) { if(appKey instanceof List) { return codeFragmentProvider.create(''' Retcode_T exception = RETCODE_OK; - exception = LoRaDevice_Init(LoRaCallbackFunc, 868); + exception = LoRaDevice_Init(LoRaCallbackFunc, «bandFrequency»); «generateLoggingExceptionHandler("LoRa", "device init")» - exception = LoRaDevice_SetRxWindow2(0, 869525000); + exception = LoRaDevice_SetRxWindow2(«rx2DataRate», «rx2Frequency»000); «generateLoggingExceptionHandler("LoRa", "set rx window")» «IF deviceEui instanceof List» exception = LoRaDevice_SetDevEUI(devEUI); @@ -151,6 +193,9 @@ class LoraGenerator extends AbstractSystemResourceGenerator { exception = LoRaDevice_SetRadioCodingRate(codingRate); «generateLoggingExceptionHandler("LoRa", "set radio coding rate")» + + exception = LoRaDevice_SetADR(«configuration.getExpression("adaptiveDataRate").code»); + «generateLoggingExceptionHandler("LoRa", "set adaptive data rate")» exception = LoRaDevice_SaveConfig(); «generateLoggingExceptionHandler("LoRa", "save config")» @@ -181,7 +226,7 @@ class LoraGenerator extends AbstractSystemResourceGenerator { if (RETCODE_OK == exception) { // Set Data Rate to 3 (increase amount of data to send) and send the data via LoRa - exception = LoRa_SetDataRate(3); + exception = LoRa_SetDataRate(«dataRate»); } if (RETCODE_OK != exception) { @@ -343,34 +388,12 @@ class LoraGenerator extends AbstractSystemResourceGenerator { break; } exception = CayenneLPPSerializer_SingleInstance(&cayenneLPPSerializerInput, &cayenneLPPSerializerOutput); - «generateLoggingExceptionHandler("Cayenne", "conversion")» cayenneLPPSerializerOutput.BufferPointer += cayenneLPPSerializerOutput.BufferFilledLength; } return «sendName»(«portNum.code», dataBuffer, bufferSize); ''') .addHeader("xdk110Types.h", false) .addHeader("XDK_CayenneLPPSerializer.h", true) - .setPreamble(''' - // translation CayennePayload_enum -> cayenne used buffer size in bytes - static const uint8_t payloadSizes[] = { - 3, 3, 4, 4, 4, 3, 4, 3, 8, 4, 8, 11 - }; - // translation CayennePayload_enum -> CayenneLPPSerializer_DataType_T - static const CayenneLPPSerializer_DataType_T cayenneDataTypes[] = { - CAYENNE_LLP_SERIALIZER_DIGITAL_INPUT, - CAYENNE_LLP_SERIALIZER_DIGITAL_OUTPUT, - CAYENNE_LLP_SERIALIZER_ANALOG_INPUT, - CAYENNE_LLP_SERIALIZER_ANALOG_OUTPUT, - CAYENNE_LLP_SERIALIZER_ILLUMINANCE_SENSOR, - CAYENNE_LLP_SERIALIZER_PRESENCE_SENSOR, - CAYENNE_LLP_SERIALIZER_TEMPERATURE_SENSOR, - CAYENNE_LLP_SERIALIZER_HUMIDITY_SENSOR, - CAYENNE_LLP_SERIALIZER_ACCELEROMETER, - CAYENNE_LLP_SERIALIZER_BAROMETER, - CAYENNE_LLP_SERIALIZER_GYROMETER, - CAYENNE_LLP_SERIALIZER_GPS_LOCATION - }; - ''') } } diff --git a/platforms/org.eclipse.mita.platform.xdk110/src/org/eclipse/mita/platform/xdk110/connectivity/LoraValidator.xtend b/platforms/org.eclipse.mita.platform.xdk110/src/org/eclipse/mita/platform/xdk110/connectivity/LoraValidator.xtend index 6c482eb8..f3c5b44f 100644 --- a/platforms/org.eclipse.mita.platform.xdk110/src/org/eclipse/mita/platform/xdk110/connectivity/LoraValidator.xtend +++ b/platforms/org.eclipse.mita.platform.xdk110/src/org/eclipse/mita/platform/xdk110/connectivity/LoraValidator.xtend @@ -14,14 +14,40 @@ package org.eclipse.mita.platform.xdk110.connectivity import java.util.List +import java.util.Map import org.eclipse.emf.ecore.EObject +import org.eclipse.mita.base.types.Enumerator import org.eclipse.mita.program.Program import org.eclipse.mita.program.SystemResourceSetup import org.eclipse.mita.program.inferrer.StaticValueInferrer import org.eclipse.mita.program.validation.IResourceValidator import org.eclipse.xtext.validation.ValidationMessageAcceptor +import static extension org.eclipse.mita.base.util.BaseUtils.castOrNull +import java.util.function.IntPredicate +import java.util.function.Predicate + class LoraValidator implements IResourceValidator { + protected val rangeChecks = #{ + "EU" -> #{ + "bandFrequency" -> [int it | if(!(#[433, 868].contains(it))) {"one of 433, 868"}], + "rx2Frequency" -> [int it | if(!((433050 <= it && it <= 434790) || (863000 <= it && it <= 870000))) {"either between 433050 and 434790 or between 863000 and 870000"}], + "rx2DataRate" -> [int it | if(!(0 <= it && it <= 7)) {"between 0 and 7"}], + "dataRate" -> [int it | if(!(0 <= it && it <= 7)) {"between 0 and 7"}] + }, + "US" -> #{ + "bandFrequency" -> [int it | if(!(it == 915)) {"exactly 915"}], + "rx2Frequency" -> [int it | if(!(923300 <= it && it <= 927500)) {"between 923300 and 927500"}], + "rx2DataRate" -> [int it | if(!(8 <= it && it <= 13)) {"between 8 and 13"}], + "dataRate" -> [int it | if(!(0 <= it && it <= 4)) {"between 0 and 4"}] + } + } + + protected val bandAndRx2Checks = #{ + "EU" -> [int fBand, int fRx2 | ((fBand / 100) as int) == ((fRx2 / 100000) as int)], + "US" -> [a,b | true] + } + override validate(Program program, EObject context, ValidationMessageAcceptor acceptor) { if(context instanceof SystemResourceSetup) { val loraAppKey = context.configurationItemValues.findFirst[ it.item.name == "loraAppKey"]; @@ -39,7 +65,38 @@ class LoraValidator implements IResourceValidator { acceptor.acceptError("Configured value must be an array", k_v.key.value, null, 0, ""); } } + + val region = StaticValueInferrer.infer( + context.configurationItemValues.findFirst[ it.item.name == "region"], [] + ).castOrNull(Enumerator)?.name; + val checks = rangeChecks.get(region); + if(checks !== null) { + checks.entrySet.forEach[name_check | + val name = name_check.key; + val check = name_check.value; + + val configItemValue = context.configurationItemValues.findFirst[it.item.name == name]; + val value = StaticValueInferrer.infer(configItemValue, []).castOrNull(Integer); + if(value !== null) { + val msg = check.apply(value); + if(msg !== null) { + acceptor.acceptError(value + " not in range for region " + region + ". Should be " + msg, configItemValue, null, 0, ""); + } + } + ] + } + val bandRx2Check = bandAndRx2Checks.get(region); + val fBandItem = context.configurationItemValues.findFirst[ it.item.name == "bandFrequency"]; + val fRx2Item = context.configurationItemValues.findFirst[ it.item.name == "rx2Frequency"]; + val fBand = StaticValueInferrer.infer(fBandItem, []).castOrNull(Integer); + val fRx2 = StaticValueInferrer.infer(fRx2Item, []).castOrNull(Integer); + + if(bandRx2Check !== null && fBand !== null && fRx2 !== null && !bandRx2Check.apply(fBand, fRx2)) { + val msg = "bandFrequency and rx2Frequency don't fit together"; + acceptor.acceptError(msg, fBandItem, null, 0, ""); + acceptor.acceptError(msg, fRx2Item, null, 0, ""); + } } } - -} \ No newline at end of file +} + diff --git a/website/site/content/platforms/xdk110.md b/website/site/content/platforms/xdk110.md index 9f0ff112..1c29b7b3 100644 --- a/website/site/content/platforms/xdk110.md +++ b/website/site/content/platforms/xdk110.md @@ -33,7 +33,7 @@ Sensors | Connectivities | Buses | IO [Pressure]({{}}) | [MQTT]({{}}) [Temperature]({{}}) | [Eclipse Hono over MQTT]({{}}) [Noise sensor]({{}}) | [REST over HTTP]({{}}) -[Two buttons]({{}}) | +[Two buttons]({{}}) | [LoRa]({{(1); + data[0] = CayennePayload.Accelerometer( + accelerometer.x_axis.read() as int16, + accelerometer.y_axis.read() as int16, + accelerometer.z_axis.read() as int16 + ); + lora.c.write(data); +} + +every 10 minutes { + let data: array = [0xCA, 0xFE]; + lora.r.write(data); +} +``` + +#### Configuration + + | Name | Description +---|---------------------------------|------------ +**Required** |`region: Region` | Whether you are using the EU or US LoRa module. +**Required** |`loraAppKey: array` | The key of your application in big-endian format (MSB first). +**Required** |`loraAppEui: array` | The EUI of your application in big-endian format (MSB first). + | `loraDeviceEui: array` | Optionally set the device's EUI. If you don't set this the predefined EUI of your LoRa board will be used. + | `adaptiveDataRate: bool` | Control whether adaptive data rate is turned on. Default: `true`. + | `bandFrequency: uint16` | Configure the band frequency according to your gateway **in MHz**. Valid values are for the EU module: 433 MHz and 868 MHz, and for the US module 915 MHz. Defaults: EU: 868, US: 915 + | `rx2Frequency: uint32` | Configure the Rx2 frequency according to your gateway **in kHz**. Valid values are for the EU module: 433050 kHz to 434790 kHz and 863000 kHz to 870000 kHz, and for the US module: 923300 kHz to 927500 kHz. Defaults: EU: 869525 kHz, US: 923300 kHz + | `rx2DataRate: uint8` | Configure the Rx2 data rate according to your gateway. Valid values are for the EU module: 0 to 7, and for the US module: 8 to 13. Defaults: EU: 0, US: 8 + | `dataRate: uint8` | Configure the data rate according to your gateway. Valid values are for the EU module: 0 to 7, and for the US module: 0 to 4. Default: 3 + + +#### Signals + +Name | Description | Parameters | +--------------------------------|---------------------------------------|------------|------------ +`raw: array` | Send data input as raw bytes. | `portNum: uint8` | Which port to use. Default: `1` + || `confirmation: LoRaMessageConfirmation` | Whether to send confirmed messages. One of `Confirmed` and `Unconfirmed`. Default: `Unconfirmed` +`cayenne: array` | Send serialized CayenneLPP messages. | `portNum: uint8` | Which port to use. Default: `1` + || `confirmation: LoRaMessageConfirmation` | Whether to send confirmed messages. One of `Confirmed` and `Unconfirmed`. Default: `Unconfirmed` + +#### Cayenne Messages +The following CayenneLPP messages are supported via a [sum type]({{< ref "/language/types.md#sum-tymes" >}}): + +``` +alt CayennePayload { + DigitalInput: uint8 + | DigitalOutput: uint8 + | AnalogInput: int16 + | AnalogOutput: int16 + | IlluminanceSensor: uint16 + | PresenceSensor: uint8 + | TemperatureSensor: int16 + | HumiditySensor: uint8 + | Accelerometer: int16, int16, int16 + | Barometer: uint16 + | Gyrometer: int16, int16, int16 + | GpsLocation: {Latitude: int32, Longitude: int32, Altitude: int32} +} +``` + +Constructing them works like in the example above or as described in the documentation of [sum types]({{< ref "/language/types.md#sum-tymes" >}}). +For example to create a GPS location message type `CayennePayload.GpsLocation(Latitude=lat, Altitude=alt, Longitude=lon)`. + ## Buses ### GPIO