Getting Started
Prior to registering devices and sending telemetry to the Sonar IoT Hub, please contact the MTech Sonar team to obtain credentials. Once you’ve obtained credentials you can access the Sonar Installer API and begin to automate your device registration and telemetry delivery processes. With Installer API you can retrieve MTech farm/house details, register devices, retrieve recent device payloads, etc.
Before registering devices, use the InstallerFarms API to get a list of all the farms and houses your credentials are assigned to (Details can be found here: InstallerFarms API). Included in the object returned from this endpoint are the farmIds, houseIds, and a number of other MTech specific properties. Note: The houseIds provided by this endpoint are required to register your devices as each device must be registered to a single house.
Device Registration
Before you begin the device registration process, be sure to use the InstallerFarms API to get a list of farms and houses that your credentials are assigned to. The houseId is required in the request body of the device registration API and all devices must be uniquely registered to a given Company/Farm/House.
Upon successful registration, all devices will receive a unique SAS key and this key will be returned in the response body. SAS Keys are required in order to generate SAS tokens for authentication with the IoT Hub. If you need to get a specific devices SAS Key after registration you can use the following API to retrieve it: Get SAS Key API.
- Device Registration API
- Device Properties
- DeviceId: The deviceId is a unique id given to each physical device at a farm. The device is what sends messages to our IoT Hub. Your system will need to generate this Id.
- Type: String
- Required
- HouseId: The houseId where the device is installed. HouseIds assigned to your credentials can be retrieved from the InstallerFarms API.
- Type: Integer
- Required
- Description: The description provides a place for you to enter additional details about the device.
- Type: String
- Not Required
- Location: The location is the physical location of the device in the house.
- Type: String
- Not Required
- UseCertificateAuthority: UseCertificateAuthority should be set to false if not using an X.509 certificate (a SAS key will be returned when set to false).
- Type: Boolean
- Not Required
- BirdWeightMode: BirdWeightMode represents the way in which bird weights are derived that are sent in the birdWeight properties of your telemetry payload. There are two options.
- 0 = Calculated. Calculated means the bird weight values that are sent to Sonar have already been calculated by the controller or device sending the reading.
- 1 = Raw. Raw means the bird weight values that are sent to Sonar are the values from the weight scale and have not been filtered or calculated by a bird weight algorithm.
- Not required. Default = 0 (calculated).
- DeviceSensors: DeviceSensors should be a collection of DeviceSensor objects. The collection is used to define what sensor data will be sent with each payload.
- Required
- Ex: If there are three temperature sensors, then the sensorTypes will look similar to the following example:
- DeviceId: The deviceId is a unique id given to each physical device at a farm. The device is what sends messages to our IoT Hub. Your system will need to generate this Id.
[
{ sensorType: 0, index: 1, description: ‘If you have identification information for this sensor, put it here’, location: 'location of the sensor' },
{ sensorType: 0, index: 2, description: null, location: null },
{ sensorType: 0, index: 3, description: null, location: null }
// Any combination of sensors can be specified
]
// Valid Sensor Types
Temperature = 0, // 20 allowed per house
Humidity = 1, // 4 allowed per house
WaterConsumption = 2, // 4 allowed per house
Weight = 3, // 4 allowed per house
BinFeedAmount = 4, // 4 allowed per house
Ammonia = 5, // 4 allowed per house
Health = 6, // 1 allowed per house
AirFlow = 7, // 4 allowed per house
CarbonDioxide = 8, // 4 allowed per house
RuntimeConsumption = 9, // 1 allowed per house
Light = 10, // 1 allowed per house
Movement = 11, // 1 allowed per house
Distress = 12, // 1 allowed per house
DailyGasConsumption = 13, // 5 allowed per house
DailyPowerConsumption = 14, // 5 allowed per house
OutsideTemperature = 15, // 1 allowed per house
EggsProduced = 16, // 4 allowed per house
StaticPressure = 17, // 1 allowed per house
AnimalWelfareIndex = 18 // 1 allowed per house
Telemetry Payload
- Only timestamp is required
- All values are in metric
- Can support individual or composite sensors
- Cannot use the same field for different devices
{
"timestamp": "2017-12-19T17:38:43Z", // ISO8601
// Temperature
"temperature1": 28.45, // Celsius
"temperature2": 29.55,
"temperature3": null,
...
"temperature36": null, // Sonar only handles 20 sensors today, but will be expanding this to 36
"temperature1Low": 26.55, // Minimum temperature for the period
"temperature1High": 32.55, // Maximum temperature for the period
"temperature2Low": 26.55,
"temperature2High": 32.55,
"temperature3Low": null,
"temperature3High": null,
...
"temperature20Low": null,
"temperature20High": null,
"outsideTemperature": 35.55,
"temperatureCurveOn": 1, // 1 for ON; 0 for OFF; null or leave out if it doesn't exist
"temperatureOffset": 66.22,
"windChillTemperature": 25.55,
"windDirection": 2,
"windSpeed": 64.55,
// Other Area Temperatures
"atticTemperature": 22.55, // All temperatures in Celsius
"circuitBreakerTemperature": 22.55,
"coolingTemperature": 22.55,
"heatingTemperature": 22.55,
"emergencyTemperature1": 22.55,
"emergencyTemperature2": 22.55,
"feedAreaTemperature": 22.55,
"workRoomTemperature": 22.55,
"tunnelTemperature": 22.55,
// Humidity
"humidity1": 65.55,
"humidity2": 72.25,
"humidity3": null,
"humidity4": null,
"humidity1Low": 60.00, // minimum humidity for the period
"humidity1High": 80.00, // maximum humidity for the period
"humidity2Low": 60.00,
"humidity2High": 80.00,
"humidity3Low": null,
"humidity3High": null,
"humidity4Low": null,
"humidity4High":null,
"outsideHumidity": 32.70,
"targetHumidity": 70.00,
"humidityTreatmentOn": 1, // 1 for ON; 0 for OFF; null or leave out if it doesn't exist
// Feed
"binFeedAmount1": 12500.70, // Kilograms, feed inventory
"binFeedAmount2": 12125.70,
"binFeedAmount3": null,
"binFeedAmount4": null,
“dailyFeed”: 12500.70, // Kilograms, runtime consumption. Do not use anymore, this is being phased out.
“dailyFeedConsumption”: 12500.70, // Kilograms, accumulated feed consumption for the day. This is used for broiler farms. Breeder farms should send the male and female data.
“femaleFeedConsumption”: 12500.70, // Kilograms, accumulated feed consumption for the day for females on a breeder farm only. Do not use for a broiler farm.
“maleFeedConsumption”: 12500.70, // Kilograms, accumulated feed consumption for the day for males on a breeder farm only. Do not use for a broiler farm.
"numberOfAugers": 2,
"feedingOn": 0, // 1 for ON; 0 for OFF; null or leave out if it doesn't exist
// Water
"water1": 60.00, // Liters, accumulated water consumption since midnight local time
"water2": 45.00,
"water3": null,
"water4": null,
"waterOnDemandPressure": 66.44,
"numberOfWaterBypass": 0,
"numberOfWaterLines": 3,
"numberOfWaterMainLines": 2,
"numberOfWaterOnDemands": 3,
// Weight
"birdWeights": [1230], // Grams, if sending a calculated bird weight, then send it under birdWeights. if sending raw data, then do not send birdWeights
"birdWeights1": [1230,1110,1300], // Grams, use birdWeights1-4 for sending raw bird weight data. each 1-4 should be data from different bird scales. Can be used for breeder or broiler farm.
"birdWeights2": [1230,1110,1300],
"birdWeights3": null,
"birdWeights4": null,
"femaleBirdWeight": 1230, // Grams, calculated weight for females on a breeder farm only. Do not use for a broiler farm.
"maleBirdWeight": 1230, // Grams, calculated weight for males on a breeder farm only. Do not use for a broiler farm.
"cv": 12.22, // should be sent for broiler farms when also sending the calculated bird weight. Not needed when sending raw bird weight data.
"uniformity": 88.15, // should be sent for broiler farms when also sending the calculated weight. Not needed when sending raw bird weight data.
"femaleCv": 12.22, // should be sent for breeder farms when also sending the calculated female bird weight. Not needed when sending raw bird weight data.
"femaleUniformity": 88.15, // should be sent for breeder farms when also sending the calculated female weight. Not needed when sending raw bird weight data.
"maleCv": 12.22, // should be sent for breeder farms when also sending the calculated male weight. Not needed when sending raw bird weight data.
"maleUniformity": 88.15, // should be sent for breeder farms when also sending the calculated male weight. Not needed when sending raw bird weight data.
// Air and Ventilation Data
"airFlow1": 100.00, // Cubic meters per hour
"airFlow2": 100.00,
"airFlow3": null,
"airFlow4": null,
"airFlowPercentage": 6.22,
"staticPressure": 100.00, // Pascals
"airVentPosition1": 6.00,
...
"airVentPosition4":null,
"ventMode": 4, // 1 = Minimum Ventilation; 2 = Natural Ventilation; 4 = Tunnel Ventilation
"ventLevel": 6.22,
"minVentLevel": 1.00,
"maxVentLevel": 6.00,
"variableHeat1": 64.55,
...
"variableHeat8": null,
// CO2
"carbonDioxide1": 100.00, // PPM
"carbonDioxide2": 100.00,
"carbonDioxide3": null,
"carbonDioxide4": null,
"carbonDioxide1Low": 80.00, // Minimum CO2 reading for the period
"carbonDioxide1High": 110.00, // Maximum CO2 reading for the period
...
"carbonDioxide4Low": null,
"carbonDioxide4High": null,
"carbonDioxideTarget": 65.55,
"carbonDioxideTreatmentOn": 0, // 1 for ON; 0 for OFF; null or leave out if it doesn't exist
// Lighting Data
“light”: 250.00, // Lux, deprecating this parameter. Should be sent as light1.
"light1": 250.00, // Lux, light intensity
"light2": 222.11,
...
"light10": null,
"numberOfLights": 6,
"lightDimmer1": 222.11,
"lightDimmer2": 200.12,
...
"lightDimmer10": null,
"numberOfLightDimmers": 6,
// Egg Production & Egg Room Data
"eggsProduced1": 12,
"eggsProduced2": 1,
"eggsProduced3": 6,
"eggsProduced4": null,
"eggRoomTemperature": 22.55,
"eggRoomHumidity": 52.55,
"numberOfEggRoomCool": 2,
"numberOfEggRoomFans": 4,
"numberOfEggRoomHeaters": 4,
// Battery Information
"batteryPowerLevel": 3.32, // in Volts
"rssi": -75, // integer in dBm, expected values are negative values from -150 to 0
"snr": 50, // integer in dB, expected values are 0 to 100
// Equipment Data
"atticPosition1": 6.00,
"curtainPosition1": 6.00,
"curtainPosition2": 6.00,
...
"curtainPosition12": null,
"tunnelCurtainPosition1": 6.00,
...
"tunnelCurtainPosition6": null,
"zoneInletPosition1": 6.00,
...
"zoneInletPosition12": null,
"numberOfNests": 6,
"nestOn1": 1, // 1 for ON; 0 for OFF
"nestOn2": 1,
"nestOn3": 0,
"nestOn4": null,
"extraSystemOn1": 1, // 1 for ON; 0 for OFF; null or leave out if it doesn't exist
"extraSystemOn2": null,
"extraSystemOn3": null,
"extraSystemOn4": null,
"coolPadFlushOn": 1, // 1 for ON; 0 for OFF; null or leave out if it doesn't exist
"cycleOn": 1, // 1 for ON; 0 for OFF; null or leave out if it doesn't exist
"nippleFlushOn": null, // 1 for ON; 0 for OFF; null or leave out if it doesn't exist
"thiModeOn": null, // 1 for ON; 0 for OFF; null or leave out if it doesn't exist
"numberOfCooling": 6,
"numberOfCoolingPads": 6,
"numberOfExhaustFans": 6,
"numberOfExtraSystems": 6,
"numberOfFoggers": 6,
"numberOfHeaters": 6,
"numberOfHumidifiers": 6,
"numberOfRadiantHighHeaters": null,
"numberOfRadiantLowHeaters": null,
"numberOfStirFans": null,
"numberOfSValves": null,
"numberOfTunnelFans": null,
"numberOfVariableHeaters": null,
// Munters Drive Data
"driveDcBussVoltage1": 6.00,
...
"driveDcBussVoltage50": null,
"driveMotorRunSpeed1": 6.00,
...
"driveMotorRunSpeed50": null,
"driveCommandSpeed1": 6.00,
...
"driveCommandSpeed50": null,
"drivePowerOnHours1": 6.00,
...
"drivePowerOnHours50": null,
"driveRunHours1": 6.00,
...
"driveRunHours50": null,
"driveHeatSinkJunctionTemperature1": 6.00,
...
"driveHeatSinkJunctionTemperature50": null,
"driveIdCurrent1": 6.00,
...
"driveIdCurrent50":null,
"driveIqCurrent1": 6.00,
...
"driveIqCurrent50": 6.00,
"driveWatts1": 6.00,
...
"driveWatts50": null,
"driveAlarmStatus1": 6.00,
...
"driveAlarmStatus50": null,
// Miscellaneous
"ammonia": 4, // available only for old devices from when only 1 ammonia sensor was supported. new devices should use ammonia1 - ammonia4 instead
"ammonia1": 4,
"ammonia2": 4,
"ammonia3": 4,
"ammonia4": 4,
"health": 7, // Health score from OpticFlock devices
“movement”: 10.00,
“distress”: 15.00,
“animalWelfareIndex”: 7.00, // Calculated value from Greengage
“dailyGasConsumption1”: 15.00, // Liters
“dailyGasConsumption2”: 15.00,
...
“dailyGasConsumption5”: null,
“dailyPowerConsumption1”: 100.00, // Kilowatts
“dailyPowerConsumption2”: 100.00,
...
“dailyPowerConsumption5”: null
}
Telemetry Payload: Flock Performance
- This type of message should be used to send mortality data
- Data should only be sent as it is entered or updated. These values should not be sent every 15 minutes.
{
"deviceid": "EXAMPLE-F3T4T-UGL5H-C43LI-K425S",
"messageType": "flock-performance",
"timestamp": "2020-12-19T17:38:43.0000000Z",
"transDate": "2020-12-19T17:38:43.0000000Z",
"mortalityF": 10,
"mortalityM": 15,
"cullF": 1,
"cullM": 3,
// below are not used in Sonar yet, purely for auditing purposes
"headPlacedF": 20000,
"headPlacedM": 900,
"headMovedOutF": null,
"headMovedOutM": null
}
Telemetry Payload: Mute Settings
- This type of message should be used to send if a sensor value should be ignored
- Data should only be sent as it is entered or updated. These values should not be sent every 15 minutes.
{
"deviceid": "EXAMPLE-F3T4T-UGL5H-C43LI-K425S",
"messageType": "mute-settings",
"timestamp": "2020-12-19T17:38:43.0000000Z",
// Temperature
"includeTemperature1": "false",
"includeTemperature2": "true",
"includeTemperature3": "true",
...
"includeTemperature20": "true",
// Humidity
"includeHumidity1": "true",
"includeHumidity2": "true",
"includeHumidity3": "true",
"includeHumidity4": "true",
// Water
"includeWater1": "true",
"includeWater2": "true",
"includeWater3": "true",
"includeWater4": "true",
// Bird Weights
"includeBirdWeights1": "true",
"includeBirdWeights2": "true",
"includeBirdWeights3": "true",
"includeBirdWeights4": "true",
// Feed
"includeBinFeedAmount1": "true",
"includeBinFeedAmount2": "true",
"includeBinFeedAmount3": "true",
"includeBinFeedAmount4": "true",
// Ammonia
"includeAmmonia1": "true",
"includeAmmonia2": "true",
"includeAmmonia3": "true",
"includeAmmonia4": "true",
// Health
"includeHealth": "true",
// Air Flow
"includeAirFlow1": "true",
"includeAirFlow2": "true",
"includeAirFlow3": "true",
"includeAirFlow4": "true",
// Carbon Dioxide
"includeCarbonDioxide1": "true",
"includeCarbonDioxide2": "true",
"includeCarbonDioxide3": "true",
"includeCarbonDioxide4": "true",
}
HTTP Example (C#)
- https://docs.microsoft.com/en-us/rest/api/iothub/device/senddeviceevent
- https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-security#security-tokens
- SAS Token is generated using the generateSasToken snippet provided by Microsoft here: https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-dev-guide-sas?tabs=node#security-token-structure
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "{SAS Token Generated from shared access key}");
var telemetryData = new
{
timestamp = DateTime.UtcNow,
binFeedAmount1 = 100,
binFeedAmount2= 200
};
var response = await httpClient.PostAsJsonAsync(
"https://Sonar.azure-devices.net/devices/{DeviceId}/messages/events?api-version=2018-06-30",
telemetryData);
SDK Example (C#)
var deviceClient = DeviceClient.Create(
"{IoTHubUri}",
new DeviceAuthenticationWithRegistrySymmetricKey("{DeviceId}"," {DeviceSASKey}"),
TransportType.Http1);
var telemetryData = new
{
timestamp = DateTime.UtcNow,
binFeedAmount1 = 100,
binFeedAmount2= 200
};
var messageString = JsonConvert.SerializeObject(telemetryData,
new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
var message = new Message(Encoding.ASCII.GetBytes(messageString));
message.CorrelationId = Guid.NewGuid().ToString();
await deviceClient.SendEventAsync(message);
Installer API
- Retrieve token using credentials from either the dev or production environments
- Include bearer token in all API requests
- Ex: Use the devices endpoint to retrieve the latest device readings
FAQ
How do you setup a farm / house?
Farms and Houses are set up by MTech and cannot be created through the installer API.
What is the deviceId?
The deviceId is a unique id given to each physical device at a farm. The device is what sends messages to our IoT Hub. Your system will need to generate this Id.
Do you have a means to see what data / telemetry has been sent when testing our devices?
Yes, our Devices/{deviceId} API provides several properties about a given device. This API will provide you with the following properties:
- LastReading: The device’s last telemetry reading saved in our DB
- RawTelemetryData: The last 10 telemetry readings enqueued into our IoT Hub
- Metadata: Device metadata
- LastReadingTime: The device’s last reading timestamp
- Location: The location of the device
- FeedBinId: The FeedBinId assigned to the device
- NumberOfWaterSensors: Total number of water sensors registered to the device
What if we need to add sensors to a device?
The device will need to be deprovisioned then reprovisioned.
- The Deprovision Device endpoint can be found here: DELETE Device API
- After Deprovisioning you can reprovision using the same endpoint your used to register the device.
What do we send in the telemetry payload if the sensors are not registered?
We prefer the sensor value be set to null; however, excluding the sensor from the payload is also acceptable.
What is the difference between InputIndex and SensorIndex?
How can I update the location of a device?
You can use the existing device provisioning endpoints to update the location of a device after it has been created.
What should happen once an offline device reconnects?
If a device has gone offline, when it comes back online, all the missing data from the offline period should also be uploaded to Sonar. For example, if a device went offline for 24 hours, once it comes back online, we would expect to get all the data for those 24 hours. The data should get uploaded with the correct timestamp of when the data was recorded during those 24 hours.
How are devices and data secured?
-
All data in transit is encrypted using a secure SSL/TLS protocol (TLS 1.2) with 2048-bit or larger key length
-
All stored sensitive/confidential information is encrypted using AES-256 or stronger encryption
-
PBKDF2 with HMAC-SHA1
-
-
Transparent data encryption automatically encrypts data at rest
-
All encryption keys are stored in a secure location that is separated from than the encrypted data
-
https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-tls-support#iot-hubs-server-tls-certificate