Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Designing a Humidity Monitoring iOS App for Warehouses Using BLE

DZone 's Guide to

Designing a Humidity Monitoring iOS App for Warehouses Using BLE

Want to learn how to design a humidity monitoring iOS app for warehouses? Check out this tutorial to learn how using BLE and Wi-Fi Hub.

· IoT Zone ·
Free Resource

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 of the system communicate with each other?

This article will elaborate on the essential components to design a system for the monitoring iOS app so that you can get an idea of the data flow and overall system design.

Warehouses are generally 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, these systems are capable of raising alarms as well as indicating it to the remote monitoring team. The remote monitoring team can be immediately summoned 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 and Wi-Fi Hub. The Wi-Fi 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 these 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 and 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:
var manager:CBCentralManager!


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

  1. Initialize the CBCentralManager object in the viewDidLoad method of the ViewController as follows:
override func viewDidLoad() { 
  super.viewDidLoad()
  manager = CBCentralManager(delegate: self, queue: nil)
}


2. Adapt the ViewController to theCBCentralManagerDelegate protocol, as shown here:

class ViewController: UIViewController,
  CBCentralManagerDelegate


3. Make the CBCentralManager object aware that the ViewController class is the delegate where it should deliver the results of the discovery by adding the following code in viewDidLoad:

manager.delegate = self


4. 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 centralManagerDidUpdateStat method of the CBCentralManagerDelegate.

5. 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:

func centralManagerDidUpdateState(_ central: CBCentralManager) {
  if central.state == .poweredOn {
    central.scanForPeripherals(withServices: nil, options: nil) 
  }
  else { 
   print("Bluetooth not available.") 
  }  
}


6. 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:

let NAME_SENSOR_TAG = "SensorTag"


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

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)
   }
 }
}


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

var sensorTagPeripheral : CBPeripheral!


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.

8. If the connection was successful, then the centralManager  didConnectToPeripheral   method should be called via the CBCentralManagerDelegate. This is where you need to initiate the service discovery:

public func centralManager(_ central: CBCentralManager, 
          didConnect peripheral: CBPeripheral) {

     peripheral.delegate = self
     peripheral.discoverServices(nil)
}


9. You need to filter out the Humidity Service from the list of services being discovered. For this, define the UUID for HumidityService  in the ViewController class:

let HumidityServiceUUID = CBUUID(string: "F000AA20-0451-4000-B000-000000000000")


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. Next, override the peripheral didDiscoverServices in the ViewController class, as shown here:

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)
       }
     }
   }
}


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

10. Now, you need to define the data and configuration characteristics from the HumidityService in the ViewController class:

let HumidityDataUUID = CBUUID(string: "F000AA21-0451-4000-B000-000000000000")
let HumidityConfigUUID = CBUUID(string: "F000AA22-0451-4000-B000-000000000000")


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

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)
   }
  }
}


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

11. 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. You will need to override this method in the ViewController class:

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)
  }
}


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

12. The HumidityData 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:

class func getRelativeHumidity(value: NSData) -> Double {
  let dataFromSensor = dataToUnsignedBytes16(value: value)
  let humidity = -6 + 125/65536 * Double(dataFromSensor[1])
  return humidity
}


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

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
}


You have now successfully completed the implementation! If you run the app 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  HumidityData from the  SensorTag. Find the code for the iOSWarehouseMonitor here.

Topics:
iot ,tutorial ,humidity monitor ,sensors ,connected devices ,ble

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}