Designing a Humidity Monitoring iOS App for Warehouses using BLE

Learn how to design a humidity monitoring iOS app for warehouses in this tutorial by Madhur Bhargava, the author of IoT Projects with Bluetooth Low Energy.

Problem Statement and Solution Design

While designing any system, it is imperative to understand the following:

  • Which problem does the system address?
  • How do the various individual parts in the system communicate with each other?

This article will only elaborate on the essential parts and show you how to design the bare minimum system of the monitoring iOS app so that you can get an idea of the data flow and overall system design.

Apart from several other items, warehouses are also used for storing vegetables, food, and medicines. Specifically for these items, it’s crucial to maintain a correct balance of the temperature and humidity; otherwise, there is a risk of major losses. Hence, a warehouse deploys specially designed monitoring systems, which continuously monitor the climate inside a warehouse. If anything goes awry, then these systems are capable of raising alarms as well as indicating it to the remote monitoring team. The remote monitoring team can immediately summon on site and put a cap on the situation at hand.

These remote monitoring systems work through an intelligent network of sensors, connected to a remote backend for monitoring and notifications. The system design described in this tutorial will follow a similar approach, as shown in the following diagram:

store warehouse using iOS BLE

To solve the problem stated earlier, one of the solutions is to strategically plant BLE Temperature and Humidity Sensors across the warehouse. They can then record the climate data and keep sending it to a central hub device capable of communicating over both BLE + WiFi Hub. The WiFi Hub is then responsible for connecting to a local Router, which can finally enable the data upload to the Remote Cloud and Monitoring Station periodically.

With the periodic data now in their hands, the remote monitoring team can efficiently monitor them and make crucial decisions on the fly. This design even enables large warehouses to operate on a skeletal crew of 1-2 personnel.

Just try to imagine what it would take to monitor the various crucial climatic parameters if such a solution was not in place.

Designing the Monitoring iOS App

The basic idea behind the setup is based on the system design discussed above. The iOS app behaves as the BLE + Wi-Fi Hub, reading data directly from the sensors planted on store/warehouse shelves, and uploads it to the backend (Firebase) for monitoring purposes.

iOS app Prerequisites

The following are the prerequisites for building the app for iOS devices:

  • XCode 8.2
  • The latest iOS device (the development device in this tutorial uses iOS 9.3.5)
  • Basic familiarity with Swift 2.3 and iOS
  • Texas Instruments CC2650 STK SensorTag

Time to get started!

Reading the Humidity Data

To read the humidity data on an iOS device, perform the following steps:

  1. Start by creating a single view project in XCode by the name of iOSWarehouseMonitor.
  2. After project creation, create a very simple UI using the storyboard. The UI will consist of a single humidity label to show the incoming humidity values, as shown in the following screenshot:

iOS app for IoT

  1. Add a CBCentralManager member variable called manager to the ViewController class as follows:
[cpp] var manager:CBCentralManager!
[/cpp]

The CBCentralManager objects (provided by the CoreBluetooth framework in iOS) are used to manage discovered and connected peripheral (CBPeripheral) objects. To read more about CBCentralManager, visit https://developer.apple.com/reference/corebluetooth/cbcentralmanager.

  1. Initialize the CBCentralManager object in the viewDidLoad method of the ViewController as follows:
[cpp] override func viewDidLoad() {
super.viewDidLoad()
manager = CBCentralManager(delegate: self, queue: nil)
}
[/cpp]
  1. Adapt the ViewController to the CBCentralManagerDelegate protocol as shown here:
[cpp] class ViewController: UIViewController,
CBCentralManagerDelegate
[/cpp]
  1. Make the CBCentralManager object aware that the ViewController class is the delegate where it should deliver results of the discovery by adding the following code in viewDidLoad:
[cpp] manager.delegate = self
[/cpp]
  1. The CBCentralManager object has a state and, as soon as you initialize it, its state gets updated. You can receive these state changes and updates in the centralManagerDidUpdateState method of CBCentralManagerDelegate.
  2. It is also a good idea to instruct the manager to start scanning for devices as soon as it is in the poweredOn state. For this, you need to implement the centralManagerDidUpdateState method, as shown in the following code:
[cpp] func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
central.scanForPeripherals(withServices: nil, options: nil)
}
else {
print("Bluetooth not available.")
}
}
[/cpp]
  1. Once you have added the code for scanning the peripherals, you need to filter out the SensorTag from the other broadcasting peripherals and then connect to it. For this, define a variable with the name for SensorTag in the ViewController class:
[cpp] let NAME_SENSOR_TAG = "SensorTag"
[/cpp]

Now, override the centralManager didDiscoverPeripheral function as you would be implementing your own filtering and connection logic here:

[cpp] public func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any],
rssi RSSI: NSNumber) {

let nameOfDeviceFound = (advertisementData as NSDictionary)
.object(forKey: CBAdvertisementDataLocalNameKey) as? String

if let nameOfDeviceFound = nameOfDeviceFound {
if (nameOfDeviceFound.contains(NAME_SENSOR_TAG)) {
print(peripheral.name!)
self.centralManager.stopScan()
// Set as the peripheral to use and establish connection
self.sensorTagPeripheral = peripheral
self.sensorTagPeripheral.delegate = self
self.centralManager.connect(peripheral, options: nil)
}
}
}
[/cpp]

Note that you will also need to declare a CBPeripheral variable in the ViewController class as shown next:

[cpp] var sensorTagPeripheral : CBPeripheral!
[/cpp]

If you run the app now, then you should be able to successfully connect to a SensorTag if there is at least one broadcasting device nearby.

  1. If the connection was successful, then the centralManager didConnectToPeripheral method should be called via CBCentralManagerDelegate. This is where you need to initiate service discovery:
[cpp] public func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {

peripheral.delegate = self
peripheral.discoverServices(nil)
}
[/cpp]

  1. You need to filter out the Humidity Service from the list of services being discovered. For this, define the UUID for Humidity service in the ViewController class:
[cpp] let HumidityServiceUUID = CBUUID(string: "F000AA20-0451-4000-B000-000000000000")
[/cpp]

The results of the service discovery can be explored in the peripheral didDiscoverServices method, which is called by CBPeripheralDelegate to deliver the results of the service discovery request. Override peripheral didDiscoverServices in the ViewController class, as shown here:

[cpp] public func peripheral(_ peripheral: CBPeripheral,
didDiscoverServices error: Error?) {

if let services = peripheral.services {
for service in services {
if service.uuid == HumidityServiceUUID {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
}
[/cpp]

In the preceding code, filter out the Humidity Service and place a characteristic discovery request for the same.

  1. Now you need to define the Data and Configuration characteristics from the Humidity Service in the ViewController class:
[cpp] let HumidityDataUUID = CBUUID(string: "F000AA21-0451-4000-B000-000000000000")
let HumidityConfigUUID = CBUUID(string: "F000AA22-0451-4000-B000-000000000000")
[/cpp]

Also, the results for characteristics discovery will be delivered in the peripheral didDiscoverCharacteristicsFor service method, which is called by CBPeripheralDelegate. Override peripheral didDiscoverCharacteristicsFor service in the ViewController class as shown here:

[cpp] public func peripheral(_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?) {
// 0x01 data byte to enable sensor
var enableValue = 1
let enablyBytes = NSData(bytes: &enableValue, length:
MemoryLayout<UInt8>.size)

// check the uuid of each characteristic to find config
//and data characteristics

for charateristic in service.characteristics! {
let thisCharacteristic = charateristic as CBCharacteristic
// check for data characteristic
if thisCharacteristic.uuid == HumidityDataUUID {
// Enable Sensor Notification
self.sensorTagPeripheral.setNotifyValue(true,
for: thisCharacteristic)
}
// check for config characteristic
if thisCharacteristic.uuid == HumidityConfigUUID {
// Enable Sensor
self.sensorTagPeripheral.writeValue(enablyBytes as Data,
for: thisCharacteristic,
type: CBCharacteristicWriteType.withResponse)
}
}
}
[/cpp]

In the above code necessary action is taken depending on the characteristicdiscovered. If the the Humidity Data characteristic is discovered, then enable notifications on the same. On the other hand, if it is the Configuration characteristic, then enable the Humidity Sensor from Sleep to Active mode.

  1. Once the Sensor is active and the notifications are enabled, it should start broadcasting periodic data. You can receive the broadcasted data in the peripheral didUpdateValueFor characteristic method, which is called by CBPeripheralDelegate whenever new data is available. Override this method in the ViewController class:
[cpp] func peripheral(_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {

if characteristic.uuid == HumidityDataUUID {
let humidity = Utilities.getRelativeHumidity(value: characteristic.value! as
NSData)
let humidityRound = Double(round(1000*humidity)/1000)

// Display on the humidity label
labelHumidity.text = "Humidity: "+String(humidityRound)
}
}
[/cpp]

Parse the received data and update it on the UI label.

  1. The Humidity Data is structured as TempLSB:TempMSB:HumidityLSB:HumidityMSB and needs to be parsed before it can be presented to the user. For parsing this data, create a separate class called Utilities and define the getRelativeHumidity method in it:
[cpp] class func getRelativeHumidity(value: NSData) -> Double {
let dataFromSensor = dataToUnsignedBytes16(value: value)
let humidity = -6 + 125/65536 * Double(dataFromSensor[1])
return humidity
}
[/cpp]

The preceding method makes use of the dataToUnsignedBytes16 method, which converts the characteristic’s value to an array of 16-bit unsigned Integers. This can be implemented and added to the Utilities class, as shown here:

[cpp] class func dataToUnsignedBytes16(value : NSData) -> [UInt16] {
let count = value.length
var array = [UInt16](repeating: 0, count: count)
value.getBytes(&array, length:count * MemoryLayout<UInt16>.size)
return array
}
[/cpp]

You have now successfully completed the implementation. If you run the app now and have a SensorTag broadcasting nearby, then you should see the humidity values updating dynamically on the screen, as shown in the following screenshot:

read sensor using iOS

If you are able to see the humidity values getting updated dynamically on the screen as shown above, then congratulations! You have successfully read the Humidity Data from the SensorTag. Find the code for iOSWarehouseMonitor at the following link: https://github.com/madhurbhargava/iOSWarehouseMonitor.

If you enjoyed this tutorial and want to build your own IoT projects with BLE, you can explore the book, IoT Projects with Bluetooth Low Energy, by Madhur Bhargava. The book is a complete technical reference guide to designing applications with Bluetooth Low Energy, and all the applications/projects described in the book serve as a guide to designing BLE-based solutions for real-world scenarios.

 

Tags: ,
  • Add Your Comment