First release to App Store

This commit is contained in:
Patrick McDonagh
2018-07-02 10:33:17 -05:00
parent 2e08234eef
commit 929221c9af
177 changed files with 31294 additions and 4255 deletions

View File

@@ -0,0 +1,27 @@
//
// DeviceChannelTableCell.swift
// pocloud
//
// Created by Patrick McDonagh on 6/19/18.
// Copyright © 2018 patrickjmcd. All rights reserved.
//
import UIKit
class DeviceChannelTableCell: UITableViewCell {
@IBOutlet weak var valueLabel: UILabel!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var timestampLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

View File

@@ -140,6 +140,11 @@ class DeviceListViewController: UITableViewController {
let destinationVC = segue.destination as! DeviceDetailViewController
destinationVC.thisDevice = selectedDevice!
}
if segue.identifier == "openMaxWaterSystem" {
let destinationVC = segue.destination as! MaxWaterSystemViewController
destinationVC.thisDevice = selectedDevice!
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@@ -156,12 +161,14 @@ class DeviceListViewController: UITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let uniqueDeviceTypes = getUniqueDeviceTypeNames()
let deviceTypesWithDevices = devices?.filter("ANY parentDeviceType.vanityName == %@", uniqueDeviceTypes[indexPath.section]).sorted(byKeyPath: "vanityName")
// let deviceTypesWithDevices = deviceTypes?.filter(deviceTypeFilter)
// if let thisSection = deviceTypesWithDevices?[indexPath.section] {
// selectedDevice = thisSection.devices[indexPath.row]
selectedDevice = deviceTypesWithDevices?[indexPath.row]
selectedDevice = deviceTypesWithDevices?[indexPath.row]
switch (selectedDevice?.parentDeviceType.first?.name){
case "advvfdipp":
performSegue(withIdentifier: "openMaxWaterSystem", sender: self)
default:
performSegue(withIdentifier: "openDeviceDetailView", sender: self)
// }
}
}

View File

@@ -0,0 +1,143 @@
//
// BaseDeviceViewController.swift
// pocloud
//
// Created by Patrick McDonagh on 6/11/18.
// Copyright © 2018 patrickjmcd. All rights reserved.
//
import UIKit
import RealmSwift
import FirebaseDatabase
import SVProgressHUD
import PromiseKit
class BaseDeviceViewController: UIViewController {
let realm = try! Realm()
var ref: DatabaseReference!
let baseURL = (UIApplication.shared.delegate as! AppDelegate).baseURL
let user = (UIApplication.shared.delegate as! AppDelegate).user
var thisDevice : Device?
var deviceTypes : Results<DeviceType>?
var selectedChannel : Channel?
var channelLookup : [String : Int] = [:]
var values : [String : MeshifyValue] = [:]
var updatedChannelNames : [String] = [String]()
var changedChannelNames : [String] = [String]()
let numFormatter = NumberFormatter()
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
numFormatter.minimumFractionDigits = 0
numFormatter.maximumFractionDigits = 3
numFormatter.minimumIntegerDigits = 1
initializeData()
}
func initializeData() {
let macAddress = String((thisDevice?.macAddress.replacingOccurrences(of: ":", with: "").uppercased().dropLast(4))!)
let deviceTypeName = (thisDevice?.parentDeviceType.first?.name)!
SVProgressHUD.show()
firstly {
loadData()
}.then { _ in
getChannels(deviceTypeId: self.thisDevice!.deviceTypeId, baseURL: self.baseURL, authToken: (self.user?.authToken)!)
}.then { _ in
getChannelValues(deviceId: self.thisDevice!.id, baseURL: self.baseURL, authToken: (self.user?.authToken)!)
}.done { (valueDict) in
self.values = valueDict
self.updateGauges()
self.updateChartData()
SVProgressHUD.dismiss()
if let dev = self.thisDevice {
for chanIndex in 0..<dev.values.count {
let chanName = dev.values[chanIndex].name
self.channelLookup[chanName] = chanIndex
}
}
self.getFirebaseDeviceValues(deviceMacAddress: macAddress, deviceTypeName: deviceTypeName)
}.catch { error in
SVProgressHUD.showError(withStatus: "\(error)")
print("Error fetching data in DeviceDetailViewController: \(error)")
}
}
override func viewWillDisappear(_ animated: Bool) {
Database.database().reference().removeAllObservers()
}
func getFirebaseDeviceValues(deviceMacAddress : String, deviceTypeName : String) {
Database.database().reference().child("devices")
.child(deviceMacAddress)
.child(deviceTypeName)
.child("channels")
.observe(.childChanged) { snapshot in
let value = snapshot.value as! NSDictionary
let chanVal = MeshifyValue()
if let name = value["name"] as? String {
chanVal.name = name
}
if let timestamp = value["timestamp"] as? String {
chanVal.timestamp = Int(Double(timestamp)!)
}
if let readValue = value["value"] as? String {
chanVal.value = readValue
}
let prevChanValue = self.values[chanVal.name]?.value
if prevChanValue != chanVal.value {
self.changedChannelNames.append(chanVal.name)
} else {
self.updatedChannelNames.append(chanVal.name)
}
self.values[chanVal.name] = chanVal
self.updateGauges()
self.updateChartData()
}
}
func updateGauges(){
return
}
func updateChartData(){
return
}
func tryRound(value : String, places numberOfPlaces: Int) -> String {
if let doubledValue = Double(value) {
if let formattedString = numFormatter.string(from: NSNumber(value: doubledValue)) {
return formattedString
} else {
return value
}
} else {
return value
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToChannelView" {
let destinationVC = segue.destination as! ChannelDetailViewController
destinationVC.thisDevice = thisDevice!
destinationVC.thisChannel = selectedChannel!
}
}
func loadData() -> Promise<Void> {
return Promise { promise in
deviceTypes = realm.objects(DeviceType.self)
thisDevice = realm.objects(Device.self).filter("id == %d", thisDevice!.id).first!
promise.fulfill(())
}
}
}

View File

@@ -0,0 +1,138 @@
//
// MaxWaterSystemViewController.swift
// pocloud
//
// Created by Patrick McDonagh on 6/11/18.
// Copyright © 2018 patrickjmcd. All rights reserved.
//
import UIKit
import SVProgressHUD
import PromiseKit
import ChameleonFramework
class MaxWaterSystemViewController: BaseDeviceViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var startButton: UIButton!
@IBOutlet weak var stopButton: UIButton!
@IBOutlet weak var resetFaultsButton: UIButton!
@IBOutlet weak var tableView: UITableView!
let channelLabels : [String] = ["Status", "Fluid Level", "Flow Rate", "Intake Pressure", "Intake Temperature", "Motor Frequency", "Motor Current", "Tubing Pressure"]
let channelNames : [String] = ["wellstatus", "fluidlevel", "flowrate", "intakepressure", "intaketemperature", "vfdfrequency", "vfdcurrent", "tubingpressure"]
let formatter = DateFormatter()
override func viewDidLoad() {
super.viewDidLoad()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
self.title = thisDevice?.vanityName
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 80
tableView.rowHeight = UITableViewAutomaticDimension
startButton.backgroundColor = UIColor.flatWhiteColorDark()
startButton.setTitleColor(UIColor.flatForestGreen(), for: .normal)
startButton.layer.cornerRadius = 5
startButton.layer.borderWidth = 1
startButton.layer.borderColor = UIColor.flatForestGreen().cgColor
stopButton.backgroundColor = UIColor.flatWhiteColorDark()
stopButton.setTitleColor(UIColor.flatRed(), for: .normal)
stopButton.layer.cornerRadius = 5
stopButton.layer.borderWidth = 1
stopButton.layer.borderColor = UIColor.flatRed().cgColor
resetFaultsButton.backgroundColor = UIColor.flatWhiteColorDark()
resetFaultsButton.setTitleColor(UIColor.flatBlue(), for: .normal)
resetFaultsButton.layer.cornerRadius = 5
resetFaultsButton.layer.borderWidth = 1
resetFaultsButton.layer.borderColor = UIColor.flatBlue().cgColor
}
//MARK: - Table View Methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return channelLabels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "deviceChannelCell", for: indexPath) as! DeviceChannelTableCell
cell.nameLabel.text = channelLabels[indexPath.row]
if let meshVal = values[channelNames[indexPath.row]] {
cell.valueLabel.text = tryRound(value: meshVal.value, places: 3)
cell.timestampLabel.text = formatter.string(from: Date(timeIntervalSince1970: Double(meshVal.timestamp)))
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedChannel = thisDevice?.parentDeviceType.first?.channels[indexPath.row]
performSegue(withIdentifier: "goToChannelView", sender: self)
}
override func updateChartData() {
tableView.reloadData()
}
//MARK: - Control Button Methods
@IBAction func startButtonPressed(_ sender: Any) {
let alert = UIAlertController(title: "Start Well", message: "Are you sure you want to start the well?", preferredStyle: .alert)
let action = UIAlertAction(title: "Start", style: .default) { (action) in
// what will happen once the user clicks the add item button on our UIAlert
firstly {
setChannelValue(deviceId: (self.thisDevice?.id)!, channelName: "writeplctag", value: "{'tag': 'cmd_Start', 'val': 1}", baseURL: self.baseURL, authToken: (self.user?.authToken)!)
}.done { _ in
self.initializeData()
}.catch { error in
print("ERROR IN startButtonPressed: \(error)")
}
}
alert.addAction(action)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
@IBAction func resetButtonPressed(_ sender: UIButton) {
let alert = UIAlertController(title: "Reset Well", message: "Are you sure you want to reset the well?", preferredStyle: .alert)
let action = UIAlertAction(title: "Reset", style: .default) { (action) in
// what will happen once the user clicks the add item button on our UIAlert
firstly {
setChannelValue(deviceId: (self.thisDevice?.id)!, channelName: "writeplctag", value: "{'tag': 'cmd_ResetAlarms', 'val': 1}", baseURL: self.baseURL, authToken: (self.user?.authToken)!)
}.done { _ in
self.initializeData()
}.catch { error in
print("ERROR IN resetButtonPressed: \(error)")
}
}
alert.addAction(action)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
@IBAction func stopButtonPressed(_ sender: Any) {
let alert = UIAlertController(title: "Stop Well", message: "Are you sure you want to stop the well?", preferredStyle: .alert)
let action = UIAlertAction(title: "Stop", style: .default) { (action) in
// what will happen once the user clicks the add item button on our UIAlert
firstly {
setChannelValue(deviceId: (self.thisDevice?.id)!, channelName: "writeplctag", value: "{'tag': 'cmd_Stop', 'val': 1}", baseURL: self.baseURL, authToken: (self.user?.authToken)!)
}.done { _ in
self.initializeData()
}.catch { error in
print("ERROR IN stopButtonPressed: \(error)")
}
}
alert.addAction(action)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
}

View File

@@ -72,7 +72,14 @@ class MapDetailViewController: UIViewController, MKMapViewDelegate, UITableViewD
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedDevice = devices?[indexPath.row]
performSegue(withIdentifier: "openDeviceDetailView", sender: self)
switch (selectedDevice?.parentDeviceType.first?.name){
case "advvfdipp":
performSegue(withIdentifier: "openMaxWaterSystem", sender: self)
default:
performSegue(withIdentifier: "openDeviceDetailView", sender: self)
}
}
@@ -115,6 +122,11 @@ class MapDetailViewController: UIViewController, MKMapViewDelegate, UITableViewD
let destinationVC = segue.destination as! DeviceDetailViewController
destinationVC.thisDevice = selectedDevice!
}
if segue.identifier == "openMaxWaterSystem" {
let destinationVC = segue.destination as! MaxWaterSystemViewController
destinationVC.thisDevice = selectedDevice!
}
}
func getDevicesAtGateway(){

View File

@@ -372,10 +372,10 @@ func getAllMeshifyData(baseURL : String, authToken : String) -> Promise<Void>{
}
func writeChannelValue(deviceId: Int, channelName: String, value : String, baseURL : String, authToken : String) -> Promise<Any> {
let timestamp: Int = Int(Date().timeIntervalSince1970)
// let timestamp: Int = Int(Date().timeIntervalSince1970)
var channelJson : JSON = JSON()
channelJson["name"].string = channelName
channelJson["timestamp"].int = timestamp
// channelJson["timestamp"].int = timestamp
channelJson["value"].string = value
var listJson = JSON()
@@ -401,6 +401,34 @@ func writeChannelValue(deviceId: Int, channelName: String, value : String, baseU
}
func setChannelValue(deviceId: Int, channelName: String, value : String, baseURL : String, authToken : String) -> Promise<Any> {
// let timestamp: Int = Int(Date().timeIntervalSince1970)
var channelJson : JSON = JSON()
channelJson["name"].string = channelName
// channelJson["timestamp"].int = timestamp
channelJson["value"].string = value
var listJson = JSON()
listJson.arrayObject = [channelJson]
let url = "\(baseURL)/devices/\(deviceId)/sets?tries=2"
let headers : HTTPHeaders = [
"Authorization": authToken
]
return Promise { promise in
Alamofire.request(url, method: .put, parameters: [:], encoding: listJson.rawString()!, headers: headers)
.response { response in
if response.response?.statusCode == 200 {
promise.fulfill(true)
} else {
promise.reject(String(response.response!.statusCode) as! Error)
}
}
}
}
extension String: ParameterEncoding {
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {

View File

@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>9-rc1</string>
<string>10</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@@ -232,7 +232,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="cnt" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="16b-fu-GPA" customClass="PillUILabel" customModule="pocloud" customModuleProvider="target">
<rect key="frame" x="278.5" y="11.5" width="46.5" height="20.5"/>
<rect key="frame" x="278.5" y="8.5" width="46.5" height="26.5"/>
<color key="backgroundColor" red="0.0" green="0.32852089410000002" blue="0.57488495110000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -294,13 +294,13 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="TeN-MF-HxZ">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
<subviews>
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="UFa-F1-ny6">
<rect key="frame" x="0.0" y="0.0" width="375" height="215.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="201"/>
</mapView>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="tuB-TB-6oT">
<rect key="frame" x="0.0" y="215.5" width="375" height="431.5"/>
<rect key="frame" x="0.0" y="201" width="375" height="402"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="companyGatewayCell" id="vCb-SM-3BC">
@@ -488,12 +488,169 @@
<outlet property="mapView" destination="xug-gE-FQz" id="zcG-aP-FLr"/>
<outlet property="nameLabel" destination="izu-lT-xWY" id="lfC-J4-u3o"/>
<segue destination="eOb-fR-aFg" kind="show" identifier="openDeviceDetailView" id="4nz-VA-pdT"/>
<segue destination="Kh2-JZ-Ct9" kind="show" identifier="openMaxWaterSystem" id="X79-lN-WJj"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Rio-1u-3O1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2001" y="-325"/>
</scene>
<!--Max Water System View Controller-->
<scene sceneID="6Zk-sY-e61">
<objects>
<viewController id="Kh2-JZ-Ct9" customClass="MaxWaterSystemViewController" customModule="pocloud" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="yIs-ci-nnM">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IXb-tG-hee">
<rect key="frame" x="0.0" y="64" width="375" height="80"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="LvW-UQ-MXT">
<rect key="frame" x="8" y="25" width="359" height="30"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7gt-Oj-XNj">
<rect key="frame" x="0.0" y="0.0" width="114.5" height="30"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<state key="normal" title="Start">
<color key="titleColor" red="0.19115509089999999" green="0.63786703349999996" blue="0.26147949700000001" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="startButtonPressed:" destination="Kh2-JZ-Ct9" eventType="touchUpInside" id="zmb-z0-43Z"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Txg-Ba-9sP">
<rect key="frame" x="122.5" y="0.0" width="114" height="30"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<state key="normal" title="Reset Faults">
<color key="titleColor" red="0.19115509089999999" green="0.63786703349999996" blue="0.26147949700000001" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="resetButtonPressed:" destination="Kh2-JZ-Ct9" eventType="touchUpInside" id="pp1-pa-dIJ"/>
<action selector="startButtonPressed:" destination="Kh2-JZ-Ct9" eventType="touchUpInside" id="IOP-Wu-8gU"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1gY-X0-Fpq">
<rect key="frame" x="244.5" y="0.0" width="114.5" height="30"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<state key="normal" title="Stop">
<color key="titleColor" red="0.67455112930000005" green="0.15692374110000001" blue="0.10914970929999999" alpha="1" colorSpace="calibratedRGB"/>
</state>
<connections>
<action selector="stopButtonPressed:" destination="Kh2-JZ-Ct9" eventType="touchUpInside" id="qq0-20-Dbg"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="1gY-X0-Fpq" firstAttribute="width" secondItem="Txg-Ba-9sP" secondAttribute="width" id="7yP-vW-Rvg"/>
<constraint firstItem="1gY-X0-Fpq" firstAttribute="width" secondItem="7gt-Oj-XNj" secondAttribute="width" id="88Q-sI-4Tg"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="aMc-N0-plk"/>
<constraint firstAttribute="bottom" secondItem="LvW-UQ-MXT" secondAttribute="bottom" constant="25" id="b6U-3x-rEv"/>
<constraint firstAttribute="trailing" secondItem="LvW-UQ-MXT" secondAttribute="trailing" constant="8" id="bqe-Li-6kh"/>
<constraint firstItem="LvW-UQ-MXT" firstAttribute="top" secondItem="IXb-tG-hee" secondAttribute="top" constant="25" id="duv-dy-UfT"/>
<constraint firstItem="LvW-UQ-MXT" firstAttribute="leading" secondItem="IXb-tG-hee" secondAttribute="leading" constant="8" id="yeO-sf-ffT"/>
</constraints>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="80" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="eD7-bx-SvD">
<rect key="frame" x="0.0" y="144" width="375" height="523"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="deviceChannelCell" id="dUo-d1-EQI" customClass="DeviceChannelTableCell" customModule="pocloud" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dUo-d1-EQI" id="dAi-UV-jeN">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" alignment="bottom" translatesAutoresizingMaskIntoConstraints="NO" id="nXZ-ID-zPe">
<rect key="frame" x="8" y="0.0" width="359" height="43"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" alignment="top" translatesAutoresizingMaskIntoConstraints="NO" id="2cy-48-y14">
<rect key="frame" x="0.0" y="0.0" width="224.5" height="43"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GRm-Zh-qFB">
<rect key="frame" x="0.0" y="0.0" width="224.5" height="21.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="18"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Timestamp" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MWr-1t-Ey5">
<rect key="frame" x="0.0" y="21.5" width="224.5" height="21.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="GRm-Zh-qFB" firstAttribute="leading" secondItem="2cy-48-y14" secondAttribute="leading" id="0fq-zC-wbL"/>
<constraint firstAttribute="trailing" secondItem="GRm-Zh-qFB" secondAttribute="trailing" id="Kac-un-jbH"/>
<constraint firstItem="MWr-1t-Ey5" firstAttribute="leading" secondItem="2cy-48-y14" secondAttribute="leading" id="QWW-zz-ARv"/>
<constraint firstAttribute="trailing" secondItem="MWr-1t-Ey5" secondAttribute="trailing" id="muZ-q8-gEy"/>
</constraints>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Value" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7ZM-sO-DrN">
<rect key="frame" x="224.5" y="0.0" width="134.5" height="43"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="7ZM-sO-DrN" secondAttribute="bottom" id="66D-kx-uj4"/>
<constraint firstItem="2cy-48-y14" firstAttribute="top" secondItem="nXZ-ID-zPe" secondAttribute="top" id="C7D-gL-2Da"/>
<constraint firstItem="7ZM-sO-DrN" firstAttribute="width" secondItem="2cy-48-y14" secondAttribute="width" multiplier="3:5" id="GbE-ZZ-KW2"/>
<constraint firstAttribute="bottom" secondItem="2cy-48-y14" secondAttribute="bottom" id="JRS-aO-dol"/>
<constraint firstItem="7ZM-sO-DrN" firstAttribute="top" secondItem="nXZ-ID-zPe" secondAttribute="top" id="OMD-Om-q0a"/>
<constraint firstItem="2cy-48-y14" firstAttribute="leading" secondItem="nXZ-ID-zPe" secondAttribute="leading" id="Rdi-qM-E5h"/>
<constraint firstAttribute="trailing" secondItem="7ZM-sO-DrN" secondAttribute="trailing" id="jKQ-Mf-QQC"/>
</constraints>
</stackView>
</subviews>
<constraints>
<constraint firstItem="nXZ-ID-zPe" firstAttribute="top" secondItem="dAi-UV-jeN" secondAttribute="top" id="4vc-RU-Cmf"/>
<constraint firstItem="nXZ-ID-zPe" firstAttribute="leading" secondItem="dAi-UV-jeN" secondAttribute="leading" constant="8" id="Doy-ty-3Bs"/>
<constraint firstAttribute="trailing" secondItem="nXZ-ID-zPe" secondAttribute="trailing" constant="8" id="XDT-nC-tbX"/>
<constraint firstAttribute="bottom" secondItem="nXZ-ID-zPe" secondAttribute="bottom" id="YxX-2s-TRy"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="nameLabel" destination="GRm-Zh-qFB" id="FAv-Qp-MtC"/>
<outlet property="timestampLabel" destination="MWr-1t-Ey5" id="gsL-WO-Jih"/>
<outlet property="valueLabel" destination="7ZM-sO-DrN" id="Zfo-Xr-3Aw"/>
</connections>
</tableViewCell>
</prototypes>
</tableView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="IXb-tG-hee" firstAttribute="leading" secondItem="1tQ-UN-8eY" secondAttribute="leading" id="4NC-4R-iPX"/>
<constraint firstItem="IXb-tG-hee" firstAttribute="top" secondItem="1tQ-UN-8eY" secondAttribute="top" id="53I-jk-VCB"/>
<constraint firstItem="IXb-tG-hee" firstAttribute="trailing" secondItem="1tQ-UN-8eY" secondAttribute="trailing" id="Ibw-N8-rBc"/>
<constraint firstItem="eD7-bx-SvD" firstAttribute="trailing" secondItem="1tQ-UN-8eY" secondAttribute="trailing" id="bgZ-et-vyL"/>
<constraint firstItem="eD7-bx-SvD" firstAttribute="leading" secondItem="1tQ-UN-8eY" secondAttribute="leading" id="c8L-su-QJV"/>
<constraint firstItem="eD7-bx-SvD" firstAttribute="top" secondItem="IXb-tG-hee" secondAttribute="bottom" id="f1S-tf-dZo"/>
<constraint firstItem="1tQ-UN-8eY" firstAttribute="bottom" secondItem="eD7-bx-SvD" secondAttribute="bottom" id="qxF-LU-WwW"/>
</constraints>
<viewLayoutGuide key="safeArea" id="1tQ-UN-8eY"/>
</view>
<connections>
<outlet property="resetFaultsButton" destination="Txg-Ba-9sP" id="LFS-LX-DOR"/>
<outlet property="startButton" destination="7gt-Oj-XNj" id="sXe-BK-gQu"/>
<outlet property="stopButton" destination="1gY-X0-Fpq" id="ztF-bm-Nr0"/>
<outlet property="tableView" destination="eD7-bx-SvD" id="Ddx-cc-Vzu"/>
<segue destination="peV-M2-goO" kind="show" identifier="goToChannelView" id="dMS-g8-ECB"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="ELU-S2-Sf9" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3141.5999999999999" y="-320.68965517241384"/>
</scene>
<!--Device Detail-->
<scene sceneID="q7c-Eh-Thg">
<objects>
@@ -905,6 +1062,7 @@
<connections>
<outlet property="searchBar" destination="z2g-Ak-4Pp" id="XeB-mp-w39"/>
<segue destination="eOb-fR-aFg" kind="show" identifier="openDeviceDetailView" id="wUb-ND-zk7"/>
<segue destination="Kh2-JZ-Ct9" kind="show" identifier="openMaxWaterSystem" id="WBp-6T-fMO"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="uhB-7k-gae" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -934,8 +1092,10 @@
</resources>
<inferredMetricsTieBreakers>
<segue reference="9Om-lw-Jgn"/>
<segue reference="4nz-VA-pdT"/>
<segue reference="dMS-g8-ECB"/>
<segue reference="wUb-ND-zk7"/>
<segue reference="WBp-6T-fMO"/>
<segue reference="bJo-JM-CaN"/>
<segue reference="LUR-lb-5qK"/>
<segue reference="qTd-vu-ryN"/>
</inferredMetricsTieBreakers>
</document>