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, // 36 allowed per house
Humidity = 1, // 4 allowed per house
WaterConsumption = 2, // 24 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, // 10 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
OutsideHumidity = 19 // 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
- Alarms/error reporting included in the telemetry payload. Alarms should send as long as the issue is occurring. When the issue stops, the alarm should not be included on the payload anymore.
{
"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,
"temperatureSetPoint": 25.55,
"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,
"feedBinPercentFull1": 12.91, // percentage of the bin that has feed
“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
// Feed Volume Data
"feedBinEmptyVolume1": 2.16, // cubic meters
"feedBinMaxEmptyVolumeSeen1": 12.91, // cubic meters
// Water
"water1": 60.00, // Liters, accumulated water consumption since midnight local time
"water2": 45.00,
...
"water24": null,
"waterTemperature": 28.45, // Celsius
"waterOnDemandPressure": 66.44,
"numberOfWaterBypass": 0,
"numberOfWaterLines": 3,
"numberOfWaterMainLines": 2,
"numberOfWaterOnDemands": 3,
// Weight
// If sending calculated average bird weight, then send the calculated value under birdWeights1.
// If sending raw bird scale data, then send the data by scale using birdWeights1-4. Ex. birdWeights1 would be raw data from scale 1, birdWeights2 would be raw data from scale 2, etc.
// If sending both calculated average bird weight AND raw bird scale data, then send the calculated value in birdWeights1 and the raw data in birdWeights2-4.
"birdWeights": [1230], // no longer used, will be deprecated
"birdWeights1": [1230,1110,1300], // Grams
"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": 15, // % for dimming
"lightDimmer2": 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 & Transmission Levels
"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
// Alarm & error reporting
"alarms":[
{
"code": "BarnTools-163", // Each vendor can use any code system. They just need to provide MTech with a list of codes and their meanings.
"sensorIndex": 0 // optional
},
{
"code": "555"
}
]
}
Telemetry Payload: Cleansed Reading
- This type of message should be used when updating a prior reading with cleansed data.
- Data should be sent as it is cleansed and does not need to be sent on any specific interval.
- This message type will follow the same format as the Telemetry Payload, but with a “messageType” property
{
"deviceid": "EXAMPLE-F3T4T-UGL5H-C43LI-K425S",
"messageType": "cleansed",
"timestamp": "2020-12-19T17:38:43.0000000Z",
...normal telemetry message
}
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, // Female mortality OR straight run/as hatched mortality
"mortalityM": 15, // Male mortality
"cullF": 1, // Female culls OR straight run/as hatched culls
"cullM": 3, // Male culls
// 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 production environment
- 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 identifier assigned to each physical device at a farm. It is used to send messages to our IoT Hub. Your system will need to generate this deviceId.
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, including:
- LastReading: The most recent telemetry reading saved in our database.
- RawTelemetryData: The last 10 telemetry readings queued into our IoT Hub.
- Metadata: The device’s metadata.
- LastReadingTime: The timestamp of the device's most recent reading.
- Location: The location of the device.
- FeedBinId: The FeedBinId associated with the device.
- NumberOfWaterSensors: The total number of water sensors registered to the device.
What if we need to add sensors to a device?
To add sensors, the device must first be deprovisioned and then reprovisioned.
- Use the Deprovision Device endpoint: DELETE Device API
- Reprovision the device using the same endpoint you used during registration.
What do we send in the telemetry payload if the sensors are not registered?
If a sensor is not registered, you should set the sensor value to null
. However, omitting the sensor from the payload is also acceptable.
What is the difference between InputIndex and SensorIndex?
These values allow multiple devices with the same sensor type to be registered for the same house.
- InputIndex: Refers to the position of the sensor on the specific device.
- SensorIndex: Refers to the order of sensors of the same type within a house.
For example:
- Device A, which measures temperature, is registered first and has an InputIndex of 1 and a SensorIndex of 1.
- Device B, which also measures temperature but is registered later, has an InputIndex of 1 (since it’s the first sensor on that device) and a SensorIndex of 2 (because it is the second temperature sensor in the house).
You don’t need to use both values unless you're registering a device in a house that already has a sensor of the same type.
How can I update the location of a device?
You can update the location of a device using the existing device provisioning endpoints after the device has been created.
What should happen once an offline device reconnects?
When a device that was offline comes back online, it should upload all missing data from the offline period. For example, if a device was offline for 24 hours, it should send all the data recorded during that time once it reconnects, with the correct timestamps reflecting when the data was originally captured.
How are devices and data secured?
We ensure device and data security with the following measures:
- Data in transit is encrypted using SSL/TLS protocols (TLS 1.2) with a key length of 2048-bit or higher.
- Sensitive/confidential data at rest is encrypted with AES-256 or stronger encryption.
- Key management: Encryption keys are securely stored in a location separate from the encrypted data.
- Encryption algorithms: We use PBKDF2 with HMAC-SHA1 for encryption, and transparent data encryption is automatically applied to data at rest.
For more information, please refer to these resources: