iBeacons And Hue Lights Tutorial Part 3: Integrating Node.JS with iOS
This is post 3 of 4 in the series “iBeacons and Hue Lights Tutorial Series”
- iBeacons and Hue Lights Tutorial Part 1: Introducing The Beacon Solution
- iBeacons and Hue Lights Tutorial Part 2: Implementing a Node.JS Server
- iBeacons And Hue Lights Tutorial Part 3: Integrating Node.JS with iOS
- iBeacons And Hue Lights Tutorial Part 4: Typhoon Dependency Injection Framework
A small band of Gorillas created a three-week iBeacons and Hue Lights project using different technologies. In our first post, we introduced our project and provided a tutorial on implementing Beacon SDK and integrating iOS devices through Bluetooth. In the second post, we discussed how to implement a Node.JS Server to manage and handle a Philips Hue Bridge with Hue Lights.
In this post, we demonstrate the Hue Lights integration for Node.JS with our iOS App. First, we’ll discuss the communication between the Node.JS Server and our iOS App through implementing an Alamofire framework and creating a RestAPIClient. Then we share how to implement a Lights Manager to manage the services.
RestAPIClient
In the iOS App we need to implement Alamofire for our RestAPIClient service. Through RestAPIClient we send all the requests to the Node.JS Server.
1. AlamoFire: Alamofire is an HTTP networking library written in Swift.
2. Add Alamofire to the CocoaPods file in the project.
3. Add the Alamofire library to the Cocoapods file.
4. Create a new file, RestAPIClient.swift, and implement the code below.
// Swift Code.
import Alamofire
class RestAPIClient: NSObject {
private var baseURL: String!
private var port: String!
init(serverBaseUrl: String, serverPort: String) {
self.baseURL = serverBaseUrl
self.port = serverPort
super.init()
}
func request(verb: String, method: String, body:Dictionary<String, Any>?, completion:@escaping (Error?)->Void) -> Void {
switch verb {
case "post":
self.requestPost(method: method, body: body, completion: { error in
completion(error)
})
default:
break
}
}
private func requestPost(method: String, body:Dictionary<String, Any>?, completion: @escaping (Error?)->Void) -> Void {
let fullURL = self.fullApiEndPoint(method: method)
Alamofire.request(URL(string:fullURL)!, method: .post, parameters: body, encoding: JSONEncoding.default, headers: nil).validate().response(completionHandler: { dataResponse in
if dataResponse.response?.statusCode == 200 {
completion(nil)
} else {
completion(dataResponse.error)
}
})
}
private func fullApiEndPoint(method:String) -> String {
return String (format: "%@:%@/%@", self.baseURL, self.port, method)
}
private func fullServerURL() -> String {
return String (format: "@%:%@", self.baseURL, self.port)
}
func updateBaseURL() {
let defaults = UserDefaults.standard
if let serverIpText = defaults.string(forKey: Constants.serverBaseUserDefault) {
self.baseURL = serverIpText
print("server baseURL: \(self.baseURL)")
}
}
}
This class handles all server requests and responses.
Hue Lights Manager
This Swift class manages all the functions for manipulating the states of the lamp.
The RestAPIClient variable type allows us to access the request methods of our RestAPIClient class, and we instance this variable in the Init of the class.
var apiClient: RestAPIClient!
init(api: RestAPIClient) {
self.apiClient = api
super.init()
}
How to Calculate the Color
Working with the hue color is not easy. A hue color is obtained from a UIColor variable or a RGB scheme, so after a little research, I discovered a simple solution provided by Philips: a simple function that takes a UIColor as parameter and returns a CGPoint. But we need to change our endpoints and functions on the server side to work with the CGPoints.
1. Add the Philips Hue SDK to our iOS Project.
- Download the Philips SDK here.
- Copy the SDK file into the project folder and add it to the project.
- The original SDK works in Objective C, so we need to add the SDK into ObjCBridge file.
- Now we can use the function.
2. Create a function in the HueLightsManager.swift called: func calculateColor(color:UIColor) -> CGPoint
3. Add a private static let lampModel = “LCT014″ constance to the file. This is necessary to calculate the CGPoint color.
4. In the function call the PHUtilities.calculateXY from the Philips Hue SDK, passing in the parameters.
func calculateColor(color:UIColor) -> CGPoint {
return PHUtilities.calculateXY(color, forModel: HueLightsManager.lampModel)
}
5. Now we need to change our functions and endpoints in the JS Server node.
- Stop the service.
- Modify the endpoints changing the hueColor parameter to xColor and yColor to parameters. Validate the parameters, add the same parameters to the functions and test it.
app.post('/turnOnLight', function(req, res) {
console.log(`Hue Xcolor: ${req.body.xColor}`);
console.log(`Hue Ycolor: ${req.body.yColor}`);
console.log(`Hue Brightness: ${req.body.hueBrightness}`);
if(!req.body.xColor || !req.body.yColor || !req.body.hueBrightness) {
res.status(400).send("400 Bad Request")
}
turnOnLight(req.body.xColor,req.body.yColor, req.body.hueBrightness)
res.status(200).end()
})
function turnOnLight (xColor, yColor, brightness) {
client.lights.getById(ligthId)
.then(light => {
console.log('Function turnOnLight:');
console.log(` Light [${light.id}]: ${light.name}`);
light.brightness = brightness;
light.on = true
light.xy = [xColor, yColor]
return client.lights.save(light);
})
.catch(error => {
console.log('Could not find light');
console.log(error.stack);
});
}
- Use Postman to test the changes.
Calculating the Brightness
The Philips Hue SDK brightness scale is from 0 to 255. To make it easier, however, we are going to transfer this scale to 0-100 and pass the right values to the server.
func caculateBrightness( brightness: Int) -> Int {
var hueBrightness: Int
if (brightness > 100) {hueBrightness = 100} else {hueBrightness = brightness}
hueBrightness = (hueBrightness * HueLightsManager.maxBrightness)/100
return hueBrightness
}
Managing the Light States
Copy the code below and implement it in the file HueLightsManager.swift
private static let lampModel = "LCT014"
private static let bodyXColorKey = "xColor"
private static let bodyYColorKey = "yColor"
private static let bodyBrightnessKey = "hueBrightness"
private static let turnOnMethod = "turnOnLight"
private static let sendPulseMethod = "turnOnLightWithPulseEffect"
private static let stopPulserMethod = "stopPulseEffectWithColor"
private static let turnOffMethod = "turnOffLight"
private static let maxBrightness = 255
var apiClient: RestAPIClient!
init(api: RestAPIClient) {
self.apiClient = api
super.init()
}
func turnOnLigths(color:UIColor, brightness: Int) -> Void {
let hueBrightness = self.caculateBrightness(brightness: brightness)
let xyColor = self.calculateColor(color: color)
let body: Dictionary = [HueLightsManager.bodyXColorKey: xyColor.x, HueLightsManager.bodyYColorKey: xyColor.y, HueLightsManager.bodyBrightnessKey: hueBrightness] as [String : Any]
apiClient.request(verb: "post", method: HueLightsManager.turnOnMethod, body: body, completion: { error in
if error != nil {
}
})
}
func sendPulse(color:UIColor) -> Void {
let xyColor = self.calculateColor(color: color)
let body: Dictionary = [HueLightsManager.bodyXColorKey: xyColor.x, HueLightsManager.bodyYColorKey: xyColor.y] as [String : Any]
apiClient.request(verb: "post", method: HueLightsManager.sendPulseMethod, body: body, completion: { error in
if error != nil {
}
})
func stopPulse(color: UIColor) -> Void {
let xyColor = self.calculateColor(color: color)
let body: Dictionary = [HueLightsManager.bodyXColorKey: xyColor.x, HueLightsManager.bodyYColorKey: xyColor.y] as [String : Any]
apiClient.request(verb: "post", method: HueLightsManager.stopPulserMethod, body: body, completion: { error in
if error != nil {
}
})
}
func turnOffLights() {
apiClient.request(verb: "post", method: HueLightsManager.turnOffMethod, body: nil, completion: {error in
if error != nil {
}
})
}
func calculateColor(color:UIColor) -> CGPoint {
return PHUtilities.calculateXY(color, forModel: HueLightsManager.lampModel)
}
func caculateBrightness( brightness: Int) -> Int {
var hueBrightness: Int
if (brightness > 100) {hueBrightness = 100} else {hueBrightness = brightness}
hueBrightness = (hueBrightness * HueLightsManager.maxBrightness)/100
return hueBrightness
}
Creating an Interface for Changing the Colors of the Lamp
Create the interface and set the labels and the controls. For the slider with the Hue colors, I recommend using SwiftHUEColorPicker. You could add CocoaPod to the project, or a single file to the project.
Add a Swift file as a view controller.
Set the delegate to SwiftHUEColorPicker.Delegate = self and implement the delegate methods:
extension HueLightsSetupViewController: SwiftHUEColorPickerDelegate {
func valuePicked(_ color: UIColor, type: SwiftHUEColorPicker.PickerType) {
let image = UIImage(named: "small_light_brightness")
self.bulbBrightnessImageView.image = image?.maskWithColor(color: color)
self.hueLightsManager.turnOnLigths(color: color, brightness: 100)
}
}
Our implementation code will look like this.
public protocol HueLightsSetupViewControllerDelegate: class {
func dismissParent() -> Void
}
class HueLightsSetupViewController: UIViewController {
@IBOutlet weak var horizontalColorPicker: SwiftHUEColorPicker!
@IBOutlet weak var bulbImageView: UIImageView!
@IBOutlet weak var bulbBrightnessImageView: UIImageView!
weak var delegate : HueLightsSetupViewControllerDelegate!
var hueLightsManager : HueLightsManager!
override func viewDidLoad() {
super.viewDidLoad()
let restApi = RestAPIClient(serverBaseUrl: Constants.serverBaseUrl, serverPort: Constants.serverPort)
self.hueLightsManager = HueLightsManager(api: r
self.horizontalColorPicker.direction = .horizontal
self.horizontalColorPicker.type = .color
self.horizontalColorPicker.delegate = self
}
Connect the view controller to your storyboard.
Set the segue from the Yes button to our new ViewController and set the presentation as Modally.
Run the App and test the Request and Response.
Conclusions
- Using the Alamofire Framework is simpler than creating a request through NSMutableURLRequest and URLSession and all the delegates.
- By creating a RestAPIClient we centralize all the requests and responses, making it easier for the LightManager Service to send all the requests and responses to the Node.JS Server.
- Implementing CocoaPods helps us manage all third party frameworks like Alamofire. Just install and use; no manual copying of files.
Watch the video to see our project in action!
This video was created by the iBeacons – Hue Lights team to share at an internal Lunch & Learn with other curious Gorillas.