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.
Join the DZone community and get the full member experience.
Join For Free
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:
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:
- Start by creating a single view project in XCode by the name of iOSWarehouseMonitor.
- 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:
- Add a
CBCentralManager
member variable called manager to theViewController
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.
- Initialize the
CBCentralManager
object in the viewDidLoad method of theViewController
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:
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.
Published at DZone with permission of Francesco Azzola, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments