WolkSense-Hexiwear/iOS/Hexiwear/FWUpgradeViewController.swift
2016-07-21 12:22:05 +02:00

320 lines
13 KiB
Swift

//
// Hexiwear application is used to pair with Hexiwear BLE devices
// and send sensor readings to WolkSense sensor data cloud
//
// Copyright (C) 2016 WolkAbout Technology s.r.o.
//
// Hexiwear is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Hexiwear is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
//
// FWUpgradeViewController.swift
//
import UIKit
import CoreBluetooth
protocol OTAPDelegate {
func didCancelOTAP()
func didFailedOTAP()
}
class FWUpgradeViewController: UIViewController {
@IBOutlet weak var progressView: UIProgressView!
@IBOutlet weak var firmwareLabel: UILabel!
@IBOutlet weak var panelContainer: UIView!
@IBOutlet weak var filenameLabel: UILabel!
@IBOutlet weak var versionLabel: UILabel!
@IBOutlet weak var fwType: UILabel!
var fwPath: String = ""
var fwFileName: String = ""
var fwTypeString: String = ""
var fwVersion: String = ""
var hexiwearPeripheral : CBPeripheral!
var hexiwearDelegate: HexiwearPeripheralDelegate?
var otapDelegate: OTAPDelegate?
// OTAP vars
var isOTAPEnabled = true // if hexiware is in OTAP mode this will be true
var hexiwearFW: [UInt8] = [] // array of bytes of hexiwear firmware
var hexiwearFWLength: Int = 0
var otapIsRunning = false
var otapControlPointCharacteristic: CBCharacteristic?
var otapDataCharacteristic: CBCharacteristic?
var otapStateCharacteristic: CBCharacteristic?
var isOtapStateAvailable = false // first read which otap state (i.e. which firmware file to send) and then proceed with otap
let formatter = NSNumberFormatter()
override func viewDidLoad() {
super.viewDidLoad()
hexiwearPeripheral.delegate = self
hexiwearPeripheral.discoverServices(nil)
// Progress % formatter
formatter.maximumFractionDigits = 1
formatter.minimumFractionDigits = 1
formatter.minimumIntegerDigits = 1
title = "Firmware upgrade"
progressView.progress = 0.0
filenameLabel.text = ""
versionLabel.text = ""
fwType.text = ""
}
override func viewWillAppear(animated: Bool) {
filenameLabel.text = "FILE: \(fwFileName)"
versionLabel.text = "VERSION: \(fwVersion)"
fwType.text = "TYPE: \(fwTypeString)"
}
override func viewWillDisappear(animated: Bool) {
if isMovingFromParentViewController() && otapIsRunning {
otapDelegate?.didCancelOTAP()
}
}
}
//MARK:- CBPeripheralDelegate
extension FWUpgradeViewController: CBPeripheralDelegate {
func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
if let error = error { print("didDiscoverServices error: \(error)") }
guard let services = peripheral.services else { return }
for service in services {
let thisService = service as CBService
if Hexiwear.validService(thisService, isOTAPEnabled: isOTAPEnabled) {
peripheral.discoverCharacteristics(nil, forService: thisService)
}
}
}
// Enable notification for each characteristic of valid service
func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
if let error = error { print("didDiscoverCharacteristicsForService error: \(error)") }
guard let characteristics = service.characteristics else { return }
for charateristic in characteristics {
let thisCharacteristic = charateristic as CBCharacteristic
if Hexiwear.validDataCharacteristic(thisCharacteristic, isOTAPEnabled: isOTAPEnabled) {
if thisCharacteristic.UUID == OTAPControlPointUUID {
otapControlPointCharacteristic = thisCharacteristic
}
else if thisCharacteristic.UUID == OTAPDataUUID {
otapDataCharacteristic = thisCharacteristic
}
else if thisCharacteristic.UUID == OTAPStateUUID {
otapStateCharacteristic = thisCharacteristic
peripheral.readValueForCharacteristic(otapStateCharacteristic!)
}
}
}
}
// Get data values when they are updated
private func setLabelsHidden(hidden: Bool) {
panelContainer.hidden = hidden
}
private func setInfo(infoText: String) {
firmwareLabel.text = infoText
}
func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
// If authentication is lost, drop OTAP
if let err = error where err.domain == CBATTErrorDomain {
let authenticationError: Int = CBATTError.InsufficientAuthentication.rawValue
if err.code == authenticationError {
hexiwearDelegate?.didLoseBonding()
return
}
}
setLabelsHidden(false)
if characteristic.UUID == OTAPStateUUID {
isOtapStateAvailable = true
let otapState = Hexiwear.getOtapState(characteristic.value)
print("otapState: \(otapState)")
if let otap = otapControlPointCharacteristic {
peripheral.setNotifyValue(true, forCharacteristic: otap)
}
}
else if characteristic.UUID == OTAPControlPointUUID {
otapIsRunning = true
let rawData = characteristic.value
print("Control point notified with \(characteristic.value)")
// Get command
let otapCommand = getOTAPCommand(rawData)
switch otapCommand {
case .NewImageInfoRequest:
// Parse received NewImageInfoRequest command
guard let _ = OTAP_CMD_NewImgInfoReq(data: rawData) else {
setInfo("Error initiating firmware upgrade. Wrong image info request received.")
return
}
print("New image info request parsed!")
guard let data = NSData(contentsOfFile: fwPath) else {
setInfo("Error reading firmware file data. Upgrade aborted.")
return
}
var dataBytes: [UInt8] = [UInt8](count: data.length, repeatedValue: 0x00)
let length = data.length
data.getBytes(&dataBytes, length: length)
hexiwearFW = dataBytes
hexiwearFWLength = length
// Parse FW file header
guard let otapImageFileHeader = OTAImageFileHeader(data: data) else {
setInfo("Error initiating firmware upgrade. Wrong image info request received.")
return
}
// Make NewImageInfoResponse command and send to OTAP Client
let newImageInfoResponse = OTAP_CMD_NewImgInfoRes(imageId: uint16ToLittleEndianBytesArray(otapImageFileHeader.imageId), imageVersion: otapImageFileHeader.imageVersion, imageFileSize: otapImageFileHeader.totalImageFileSize)
let newImageResponseBinary = newImageInfoResponse.convertToBinary()
let newImageResponseData = NSData(bytes: newImageResponseBinary, length: newImageResponseBinary.count)
print("write image info response binary: \(newImageResponseBinary) data: \(newImageResponseData)")
peripheral.writeValue(newImageResponseData, forCharacteristic: otapControlPointCharacteristic!, type: CBCharacteristicWriteType.WithResponse)
print("New image info response sent!")
case .ImageBlockRequest:
// Parse received Image block request command
guard let imageBlockRequest = OTAP_CMD_ImgBlockReq(data: rawData) else {
setInfo("Error upgrading firmware. Wrong image block request received.")
return
}
// Set OTAP transfer parameters
let startPosition = imageBlockRequest.startPosition
let blockSize = imageBlockRequest.blockSize
let chunkSize = imageBlockRequest.chunkSize
self.sendImageChunks(peripheral, startPosition: startPosition, blockSize: blockSize, chunkSize: chunkSize, sequenceNumber: 0, amountOfChunksToSend: 4)
case .ErrorNotification:
otapIsRunning = false
// Parse received Error notification command
guard let errorNotification = OTAP_CMD_ErrNotification(data: rawData) else {
setInfo("Error upgrading firmware. Error notification received.")
return
}
if let errorStatus = OTAP_Status(rawValue: errorNotification.errStatus) {
setInfo("Error upgrading firmware. Error status = \(errorStatus).")
print(errorStatus)
}
else {
setInfo("Error upgrading firmware.")
print("Unknown error status")
}
otapDelegate?.didFailedOTAP()
case .ImageTransferComplete:
otapIsRunning = false
// Parse image transfer complete
guard let imageTransferComplete = OTAP_CMD_ImgTransferComplete(data: rawData) else {
setInfo("Error upgrading firmware. Image transfer failed.")
return
}
if imageTransferComplete.status == OTAP_Status.StatusSuccess {
setInfo("Firmware upgrade completed successfully.")
print("Image transfer complete with success !")
}
else {
setInfo("Firmware upgrade failed.")
print("Image transfer failed !")
print(imageTransferComplete.status)
}
default:
print("Command not recognized")
}
}
}
func sendImageChunks(peripheral: CBPeripheral, startPosition: UInt32, blockSize: UInt32, chunkSize: UInt16, sequenceNumber: UInt8, amountOfChunksToSend: UInt8) {
guard otapIsRunning else { return }
var sequenceNumber = sequenceNumber
for i: UInt8 in 0 ..< amountOfChunksToSend {
sendChunk(peripheral, startPosition:startPosition, blockSize: blockSize, chunkSize: chunkSize, sequenceNumber: sequenceNumber + i)
}
// Print progress
let start = startPosition / UInt32(chunkSize)
let end = UInt32(hexiwearFWLength) / UInt32(chunkSize) + 1
let progress = start + UInt32(sequenceNumber) + UInt32(amountOfChunksToSend)
let progressPercentage = Float(progress)/Float(end)
progressView.progress = progressPercentage
if progress <= end {
setInfo("Uploaded \(progress) of \(end) chunks (\(formatter.stringFromNumber(Float(100 * progressPercentage))!))%")
}
if sequenceNumber < 0xFF - amountOfChunksToSend + 1 {
// Continue with next chunks
sequenceNumber += amountOfChunksToSend
delay((GAP_PPCP_connectionInterval + GAP_PPCP_packetLeeway) * Double(amountOfChunksToSend)) {
self.sendImageChunks(peripheral, startPosition: startPosition, blockSize: blockSize, chunkSize: chunkSize, sequenceNumber: sequenceNumber, amountOfChunksToSend: amountOfChunksToSend)
}
}
}
func sendChunk(peripheral: CBPeripheral, startPosition: UInt32, blockSize: UInt32, chunkSize: UInt16, sequenceNumber: UInt8) {
guard otapIsRunning else { return }
let currentChunkStart = startPosition + UInt32(UInt16(sequenceNumber) * chunkSize)
let proposedChunkEnd = currentChunkStart + UInt32(chunkSize) - 1
let isProposedChunkEndBeyondFileSize = proposedChunkEnd >= UInt32(hexiwearFWLength)
let currentChunkEnd = isProposedChunkEndBeyondFileSize ? hexiwearFWLength - 1 : Int(proposedChunkEnd)
guard UInt32(hexiwearFWLength) > currentChunkStart else { return }
let data: [UInt8] = [UInt8](hexiwearFW[Int(currentChunkStart)...Int(currentChunkEnd)])
let imageChunkCmd = OTAP_CMD_ImgChunkAtt(seqNumber: sequenceNumber, data: data)
let imageChunkBinary = imageChunkCmd.convertToBinary()
let imageChunkData = NSData(bytes: imageChunkBinary, length: imageChunkBinary.count)
peripheral.writeValue(imageChunkData, forCharacteristic: otapDataCharacteristic!, type: CBCharacteristicWriteType.WithoutResponse)
}
}