From 81065988935edb1b2090ed06714711dfa35ee347 Mon Sep 17 00:00:00 2001 From: John Carter Date: Wed, 14 Oct 2020 11:49:14 +0200 Subject: [PATCH] Update to docs --- .../project.pbxproj | 2 +- .../c8y-lib/api/ManagedObjectService.swift | 11 +- Sources/c8y-lib/custom/model/Model.swift | 6 +- Sources/c8y-lib/devices/Device.swift | 480 ++++++++++++----- Sources/c8y-lib/devices/EditableDevice.swift | 4 +- Sources/c8y-lib/devices/EditableGroup.swift | 4 +- Sources/c8y-lib/devices/Group.swift | 368 +++++++++---- Sources/c8y-lib/devices/MutableDevice.swift | 504 +++++++++++++----- Sources/c8y-lib/priv/AnyC8yObject.swift | 6 +- Sources/c8y-lib/priv/GroupLoader.swift | 2 +- .../c8y-lib/priv/MockedAssetCollection.swift | 12 +- 11 files changed, 978 insertions(+), 421 deletions(-) diff --git a/Cumulocity Client Library.xcodeproj/project.pbxproj b/Cumulocity Client Library.xcodeproj/project.pbxproj index e325fd0..366102c 100644 --- a/Cumulocity Client Library.xcodeproj/project.pbxproj +++ b/Cumulocity Client Library.xcodeproj/project.pbxproj @@ -369,10 +369,10 @@ children = ( F0A7EC2725344BCA00472615 /* AssetCollection.swift */, F0A7EC2825344BCA00472615 /* Device.swift */, + F0A7EC2C25344BCA00472615 /* MutableDevice.swift */, F0A7EC2925344BCA00472615 /* EditableDevice.swift */, F0A7EC2A25344BCA00472615 /* EditableGroup.swift */, F0A7EC2B25344BCA00472615 /* Group.swift */, - F0A7EC2C25344BCA00472615 /* MutableDevice.swift */, ); path = devices; sourceTree = ""; diff --git a/Sources/c8y-lib/api/ManagedObjectService.swift b/Sources/c8y-lib/api/ManagedObjectService.swift index 1a5ad9a..c3ac10c 100644 --- a/Sources/c8y-lib/api/ManagedObjectService.swift +++ b/Sources/c8y-lib/api/ManagedObjectService.swift @@ -253,12 +253,13 @@ public class C8yManagedObjectsService: JcConnectionRequest AnyPublisher, APIError> { return try super._execute(method: JcConnectionRequest.Method.POST, resourcePath: C8Y_MANAGED_OBJECTS_API, contentType: "application/json", request: object).map({ (response) -> JcRequestResponse in - let location: String = response.httpHeaders![JC_HEADER_LOCATION] as! String - let id = String(location[location.index(location.lastIndex(of: "/")!, offsetBy: 1)...]) - var updatedObject = object - updatedObject.updateId(id) - return JcRequestResponse(response, content: updatedObject) + let location: String = response.httpHeaders![JC_HEADER_LOCATION] as! String + let id = String(location[location.index(location.lastIndex(of: "/")!, offsetBy: 1)...]) + var updatedObject = object + updatedObject.updateId(id) + + return JcRequestResponse(response, content: updatedObject) }).eraseToAnyPublisher() } diff --git a/Sources/c8y-lib/custom/model/Model.swift b/Sources/c8y-lib/custom/model/Model.swift index f36919c..659ba84 100644 --- a/Sources/c8y-lib/custom/model/Model.swift +++ b/Sources/c8y-lib/custom/model/Model.swift @@ -49,7 +49,7 @@ public struct C8yModel: C8yCustomAsset, Hashable { public private(set) var id: String = "" public private(set) var name: String = "" - public private(set) var category: C8yDeviceCategory = .Unknown + public private(set) var category: C8yDevice.DeviceCategory = .Unknown public private(set) var description: String? = nil public private(set) var link: String? = "" internal private(set) var imageId: String? = nil @@ -64,7 +64,7 @@ public struct C8yModel: C8yCustomAsset, Hashable { case imageId } - init(_ id: String, name: String, category: C8yDeviceCategory, link: String?, description: String? = nil, image: UIImage? = nil) { + init(_ id: String, name: String, category: C8yDevice.DeviceCategory, link: String?, description: String? = nil, image: UIImage? = nil) { self.id = id self.name = name @@ -83,7 +83,7 @@ public struct C8yModel: C8yCustomAsset, Hashable { self.link = try container.decode(String.self, forKey: .link) if (container.contains(.category)) { - self.category = try C8yDeviceCategory(rawValue: container.decode(String.self, forKey: .category))! + self.category = try C8yDevice.DeviceCategory(rawValue: container.decode(String.self, forKey: .category))! } } diff --git a/Sources/c8y-lib/devices/Device.swift b/Sources/c8y-lib/devices/Device.swift index 64a0a01..1df0cc5 100644 --- a/Sources/c8y-lib/devices/Device.swift +++ b/Sources/c8y-lib/devices/Device.swift @@ -15,80 +15,81 @@ let JC_MANAGED_OBJECT_WEBLINK = "xWebLink" let C8Y_UNDEFINED_SUPPLIER = "generic" let C8Y_UNDEFINED_MODEL = "" -public enum C8yDeviceCategory: String, CaseIterable, Hashable, Identifiable, Encodable { - case Unknown - case Group - case Gauge - case Temperature - case Motion - case Accelerator - case Light - case Humidity - case Moisture - case Distance - case Current - case ElectricMeter - case GasMeter - case Thermostat - case Motor - case Camera - case Alarm - case Lock - case Network - case Router - case Phone - case Computer - - public var id: C8yDeviceCategory {self} -} +/** +Encapsulates a c8y `C8yManagedObject` managed object and treats it as a device exposing attributes and methods typically attributed to a device +such as `serialNumber`, `model` +Also includes a number of custom atributes to better categorise devices such as `deviceCategory`, `attachments` and `relayState` +*/ public struct C8yDevice: C8yObject { + /** + Used to categorise the device type + */ + public enum DeviceCategory: String, CaseIterable, Hashable, Identifiable, Encodable { + case Unknown + case Group + case Gauge + case Temperature + case Motion + case Accelerator + case Light + case Humidity + case Moisture + case Distance + case Current + case ElectricMeter + case GasMeter + case Thermostat + case Motor + case Camera + case Alarm + case Lock + case Network + case Router + case Phone + case Computer + + public var id: DeviceCategory {self} + } + + /** + client side id, required by SwiftUI for display purposes + */ public var id = UUID().uuidString + /** + Dictionary of all related external id's. + Not populated by default, unless you use the class `C8yAssetCollection` to manage your groups and devices + */ public var externalIds: [String:C8yExternalId] = [String:C8yExternalId]() - - public static func == (lhs: C8yDevice, rhs: C8yDevice) -> Bool { - lhs.c8yId == rhs.c8yId - } - - public func isDifferent(_ device: C8yDevice) -> Bool { - - return self.operationalLevel != device.operationalLevel - || self.attachments.count != device.attachments.count - || self.isDeployed != device.isDeployed - || self.firmware != device.firmware - || self.lastMessage != device.lastMessage - || self.lastUpdated != device.lastUpdated - || self.deviceCategory != device.deviceCategory - || self.name != device.name - || self.isNew != device.isNew - || self.webLink != device.webLink - || self.model != device.model - || self.network != device.network - || self.notes != device.notes - || self.requiredResponseInterval != device.requiredResponseInterval - || self.revision != device.revision - || self.position == nil && device.position != nil - || (self.position != nil && self.position!.isDifferent(device.position)) - } + /** + Implemented in accordance to protocol `C8yObject`, always returns .device + */ public var groupCategory: C8yGroupCategory { return .device } + /** + Implemented in accordance to protocol `C8yObject`, always returns .na as it is a device + */ public var orgCategory: C8yOrganisationCategory { get { return .na } } - public var deviceCategory: C8yDeviceCategory { + /** + Returns the category to which the device belongs. + Represented by a custom attribute 'xC8yDeviceCategory' in the wrapped managed object + */ + public var deviceCategory: DeviceCategory { get { if (self.wrappedManagedObject.properties[C8Y_MANAGED_OBJECTS_XDEVICE_CATEGORY] != nil) { - return C8yDeviceCategory(rawValue: (self.wrappedManagedObject.properties[C8Y_MANAGED_OBJECTS_XDEVICE_CATEGORY] as! C8yStringWrapper).value) ?? .Unknown + return DeviceCategory(rawValue: (self.wrappedManagedObject.properties[C8Y_MANAGED_OBJECTS_XDEVICE_CATEGORY] as! C8yStringWrapper).value) ?? .Unknown } else if self.wrappedManagedObject.sensorType.count > 0 { - return C8yDeviceCategory(rawValue: self.wrappedManagedObject.sensorType[0].rawValue.substring(from: 3))! + return DeviceCategory(rawValue: self.wrappedManagedObject.sensorType[0].rawValue.substring(from: 3))! } else { return .Unknown } @@ -100,6 +101,16 @@ public struct C8yDevice: C8yObject { } } + /** + Convenience attribute to determin if the device is operating correctly or not. + + nominal - available and no alarms present + operating - available with warning alarms + failing - avaiable with major alarms + error - unavailable or available with critical alarms + offline - unavailable, no alarms present + maintenance - either unavailable or unavailable and all alarms ignored + */ public var operationalLevel: C8yOperationLevel { get { if (self.wrappedManagedObject.availability?.status == .MAINTENANCE || self.wrappedManagedObject.activeAlarmsStatus == nil || self.wrappedManagedObject.activeAlarmsStatus?.total == 0) { @@ -112,26 +123,34 @@ public struct C8yDevice: C8yObject { } else { return .undeployed } - } - else if (self.wrappedManagedObject.activeAlarmsStatus?.critical ?? 0 > 0) { - return .error - } - else if (self.wrappedManagedObject.activeAlarmsStatus?.major ?? 0 > 0) { - return .failing - } else if (self.wrappedManagedObject.activeAlarmsStatus?.minor ?? 0 > 0) { - return .operating - } else if (self.wrappedManagedObject.activeAlarmsStatus?.warning ?? 0 > 0) { - return .operating - } else { - return .undeployed - } + } else { + + if (self.wrappedManagedObject.activeAlarmsStatus?.critical ?? 0 > 0 || self.wrappedManagedObject.availability?.status == .UNAVAILABLE) { + return .error + } + else if (self.wrappedManagedObject.activeAlarmsStatus?.major ?? 0 > 0) { + return .failing + } else if (self.wrappedManagedObject.activeAlarmsStatus?.minor ?? 0 > 0) { + return .operating + } else if (self.wrappedManagedObject.activeAlarmsStatus?.warning ?? 0 > 0) { + return .operating + } else { + return .undeployed + } + } } } + /** + Returns number of child devices + */ public var deviceCount: Int { return self.children.count } + /** + Returns number of child devices that are available + */ public var onlineCount: Int { var count: Int = 0 @@ -148,11 +167,17 @@ public struct C8yDevice: C8yObject { return count } + /** + Returns number of child devices that are unavailable + */ public var offlineCount: Int { return self.deviceCount - self.onlineCount } + /** + Total number of alarms associated with this device + */ public var alarmsCount: Int { get { @@ -173,24 +198,43 @@ public struct C8yDevice: C8yObject { } } + /** + Returns true if the associated device is a switch/relay type device + */ public var isRelay: Bool { return self.wrappedManagedObject.relayState != nil } + /** + Reflects current state of relay either open, closed or pending + Refer to `C8yMutableDevice` if you want to change the relay state + */ public var relayState: C8yManagedObject.RelayStateType? { get { return self.wrappedManagedObject.relayState } } + /** + Returns the Cumulocity derived status of the device. + Cumulocity determines the availability of the device based on the last time it received any data from the device. + Cumulocity flags the device as UNAVAILABLE If nothing has been received within the devices `requiredResponseInterval` time period. + Unless If the `requiredResponseInterval` is set to -1, in which case it returns the status of MAINTENANCE. + + Refer to `C8yMutableDevice` if you want to change the `requriedResponseInterval` or POST a managed object to c8y using the + constructor `C8yManagedObject.init(_:requiredAvailability:)` and service `C8yManagedObjectService.post(_:)` + */ public var status: C8yManagedObject.AvailabilityStatus { get { return self.wrappedManagedObject.availability?.status ?? .UNKNOWN } } - public var serialNumber: String? { + /** + Returns the device's serial number if available + */ + public internal(set) var serialNumber: String? { get { return self.externalIds["c8y_Serial"]?.externalId @@ -207,7 +251,10 @@ public struct C8yDevice: C8yObject { } } - public var supplier: String { + /** + String value describing the supplier of the device or 'generic' if not defined + */ + public internal(set) var supplier: String { get { if (self.wrappedManagedObject.hardware == nil || self.wrappedManagedObject.hardware!.supplier == nil) { return C8Y_UNDEFINED_SUPPLIER @@ -225,7 +272,10 @@ public struct C8yDevice: C8yObject { } } - public var model: String { + /** + String value describing the model of the device or an empty string if not defined + */ + public internal(set) var model: String { get { if (self.wrappedManagedObject.hardware == nil || self.wrappedManagedObject.hardware!.model == nil) { return C8Y_UNDEFINED_MODEL @@ -243,7 +293,10 @@ public struct C8yDevice: C8yObject { } } - public var revision: String? { + /** + String value describing the revision of the device or nil if not available + */ + public internal(set) var revision: String? { get { return self.wrappedManagedObject.hardware?.revision } @@ -257,12 +310,19 @@ public struct C8yDevice: C8yObject { } } + /** + String value describing the device's firmware version or nil if not available + */ public var firmware: String? { get { return self.wrappedManagedObject.firmware?.version } } + /** + String list of operation types that are supported by this device. + e.g. c8y_Restart etc. + */ public var supportedOperations: [String] { get { if (self.wrappedManagedObject.supportedOperations != nil) { @@ -273,6 +333,9 @@ public struct C8yDevice: C8yObject { } } + /** + Network settings describing what network the device uses to communicate. + */ public var network: C8yAssignedNetwork { get { return self.wrappedManagedObject.network ?? C8yAssignedNetwork() @@ -282,6 +345,26 @@ public struct C8yDevice: C8yObject { } } + /** + Used in relation to `network` to determine if the device has been provisioned within the required network. + This attribute should be updated to reflect whether this has been done. The precise details for provisioning are network specific + and may in part be manual. + + Refer to the class `C8yNetworks` for more information + */ + public internal(set) var isDeployed: Bool { + get { + return self.network.type == nil || self.network.type == C8yNetworkType.none.rawValue || self.network.isProvisioned + } + set(v) { + + self.network.isProvisioned = v + } + } + + /** + Arbritary text associated with the device or nil if note available. + */ public var notes: String? { get { return self.wrappedManagedObject.notes @@ -291,24 +374,43 @@ public struct C8yDevice: C8yObject { } } + /** + Date/time that the Managed Object represeting this device was last updated in Cumulocity + */ public var lastUpdated: Date? { get { return self.wrappedManagedObject.lastUpdated } } + /** + Date/time that Cumulocity last received some kind of activity from the device. + This is used to determine the devices `status` in conjunction with `requiredResponseInterval` + */ public var lastMessage: Date? { get { return self.wrappedManagedObject.availability?.lastMessage } } + /** + Value in seconds used to determine device availability, i.e. the device is considered unavailable if no activity is received from the device within + the time period given here. The device is considered to be in maintenance mode if this is set to -1. Incidentally all alarms triggers are + ignored if this value indicates maintenance mode. + + Refer to `C8yMutableDevice` if you want to change the `requriedResponseInterval` or POST a managed object to c8y using the + constructor `C8yManagedObject.init(_:requiredAvailability:)` and service `C8yManagedObjectService.post(_:)` + */ public var requiredResponseInterval: Int? { get { return self.wrappedManagedObject.requiredAvailability?.responseInterval } } + /** + Custom attribute to allow a web url to be associated with the device. + Useful if you want to provide a link to external technical documentation etc. The attribute is stored in the c8y managed object as 'xWebLink' + */ public var webLink: String? { get { return (self.wrappedManagedObject.properties[JC_MANAGED_OBJECT_WEBLINK] as? C8yStringWrapper)?.value @@ -321,16 +423,12 @@ public struct C8yDevice: C8yObject { } } - public internal(set) var isDeployed: Bool { - get { - return self.network.type == nil || self.network.type == C8yNetworkType.none.rawValue || self.network.isProvisioned - } - set(v) { - - self.network.isProvisioned = v - } - } - + /** + This attribute only applies to devices that connect to Cumulocity using push notifications rather than the more typical polling mechanism + whereby Cumulocity through an agent queries the device. + + If applicable returns true if the device is currently connected and sending data, false indicates the device has disconnected. + */ public var connected: Bool { get { if (self.wrappedManagedObject.status != nil) { @@ -341,6 +439,9 @@ public struct C8yDevice: C8yObject { } } + /** + Alarm summary for device. + */ public internal(set) var alarms: C8yManagedObject.ActiveAlarmsStatus? { get { return self.wrappedManagedObject.activeAlarmsStatus @@ -350,27 +451,63 @@ public struct C8yDevice: C8yObject { } } + /** + Defines the type of measurements that can be collected for this device and gives an indication to how they should be displayed + */ public var dataPoints: C8yDataPoints? { get { return self.wrappedManagedObject.dataPoints } } + /** + Represents the wrapped Managed Object that defines this device + */ public var wrappedManagedObject: C8yManagedObject + + /** + String representing the hierachy in which device belongs, i.e. list the parent group in which device is nested. + This is only provided if you used `C8yAssetCollection` to fetch the device + */ public internal(set) var hierachy: String? + + /** + List of attachment references associated with this device. The attachments themselves can be + fetched via the `C8yBinariesService` using the references here. + + The attachments are stored in the managed object in c8y using the attribute 'xAttachmentIds' + */ public internal(set) var attachments: [String] = [] + + /** + List of child devices associated with this device, only applicable for router or gateway type devices. + */ public internal(set) var children: [AnyC8yObject] = [] - init() { + /** + Default constructor for an empty device + */ + internal init() { self.wrappedManagedObject = C8yManagedObject("_none_") self.wrappedManagedObject.isDevice = true } - + + /** + Creates a new empty device with the given external id and type + + - parameter externalId: string representing external id + - parameter type: description of external id type e.g. 'c8y_Serial' + */ public init(externalId: String, type: String) { self.init() self.externalIds[type] = C8yExternalId(withExternalId: externalId, ofType: type) } + /** + Creates a device based on the underlying managed object + + - parameter m: The managed object representing the device + */ public init(_ m: C8yManagedObject) { self.wrappedManagedObject = m @@ -384,33 +521,68 @@ public struct C8yDevice: C8yObject { } } - init(_ m: C8yManagedObject, location: String) { + internal init(_ c8yId: String?, serialNumber: String?, withName name: String, type: String, supplier: String?, model: String?, notes: String?, requiredResponseInterval: Int, revision: String, category: DeviceCategory?) { + + self.wrappedManagedObject = C8yManagedObject(deviceWithSerialNumber: serialNumber, name: name, type: type, supplier: supplier, model: model!, notes: notes, revision: revision, requiredResponseInterval: requiredResponseInterval) + + if (c8yId != nil) { + self.wrappedManagedObject.updateId(c8yId!) + } + + if (category != nil) { + self.deviceCategory = category! + } + } + + internal init(_ c8yId: String) { + + self.wrappedManagedObject = C8yManagedObject(c8yId) + self.wrappedManagedObject.isDevice = true + } + + internal init(_ m: C8yManagedObject, hierachy: String) { self.init(m) self.wrappedManagedObject.isDevice = true - self.hierachy = location - } - - public init(_ c8yId: String) { - - self.wrappedManagedObject = C8yManagedObject(c8yId) - self.wrappedManagedObject.isDevice = true - } - - init(_ c8yId: String?, serialNumber: String?, withName name: String, type: String, supplier: String?, model: String?, notes: String?, requiredResponseInterval: Int, revision: String, category: C8yDeviceCategory?) { - - self.wrappedManagedObject = C8yManagedObject(deviceWithSerialNumber: serialNumber, name: name, type: type, supplier: supplier, model: model!, notes: notes, revision: revision, requiredResponseInterval: requiredResponseInterval) - - if (c8yId != nil) { - self.wrappedManagedObject.updateId(c8yId!) - } - - if (category != nil) { - self.deviceCategory = category! - } + self.hierachy = hierachy } + public static func == (lhs: C8yDevice, rhs: C8yDevice) -> Bool { + lhs.c8yId == rhs.c8yId + } + + /** + Convenience method to determine if the given device matches all of the same attributes as this device + */ + public func isDifferent(_ device: C8yDevice) -> Bool { + + return self.operationalLevel != device.operationalLevel + || self.attachments.count != device.attachments.count + || self.isDeployed != device.isDeployed + || self.firmware != device.firmware + || self.lastMessage != device.lastMessage + || self.lastUpdated != device.lastUpdated + || self.deviceCategory != device.deviceCategory + || self.name != device.name + || self.isNew != device.isNew + || self.webLink != device.webLink + || self.model != device.model + || self.network != device.network + || self.notes != device.notes + || self.requiredResponseInterval != device.requiredResponseInterval + || self.revision != device.revision + || self.position == nil && device.position != nil + || (self.position != nil && self.position!.isDifferent(device.position)) + } + + /** + Returns a string representing the default external id and type if provided or if not the c8y internal id. + + Format is key='value' e.g. + c8y_Serial=122434344 + c8y_Id=9393 + */ public func defaultIdAndType() -> String { var idString: String @@ -420,12 +592,15 @@ public struct C8yDevice: C8yObject { } else if (self.serialNumber != nil) { idString = "c8y_Serial=\(self.serialNumber!)" } else { - idString = "c8yId=\(self.c8yId ?? "unasigned")" + idString = "c8y_Id=\(self.c8yId ?? "unasigned")" } return idString } - + + /** + Returns the default external id if provided or if not the c8y internal id. + */ public func defaultId() -> String? { var idString: String? @@ -441,16 +616,73 @@ public struct C8yDevice: C8yObject { return idString } + /** + Returns true if the given external id matches this device + + - parameter forExternalId: the value, must match the value for the associated type + - parameter type: describes external id, must match a type given in `externalIds` + - returns: true if a match is found, false otherwise + */ public func match(forExternalId id: String, type: String?) -> Bool { return (self.externalIds[type ?? "c8y_Serial"] != nil && self.externalIds[type ?? "c8y_Serial"]!.externalId == id) } + /** + Returns true if an external id and type can be found in the formatted string, which then matches one of the devices. + + String could be formatted as + + c8y_Serial=3434343 + or + 3434343 (if no separator given) + + - parameter line: formatted text string containing the id and type + - parameter separator: Optional separator determing how the type and value are provided. If nil assume type is not provided and will attempt to match one of the lines to any external id + - returns: true if a match was made + */ + public func matchRawStringIdentifier(line: String, separator: String.Element?) -> Bool { + + if (separator != nil) { + + let parts = line.split(separator: separator!) + let type: String = String(parts[0]) + let ref: String = String(parts[1]) + var id = self.externalIds[type] + + if (id == nil) { + id = self.externalIds[type.lowercased()] + } + + return id != nil && id?.externalId == ref.lowercased().replacingOccurrences(of: " ", with: "").replacingOccurrences(of:":", with:"") + } else { + + for id in self.externalIds.values { + if (id.externalId == line) { + return true + } + } + + return false + } + } + + /** + Returns a UIImage representing a QR code of the default id of this device + + - returns: UIImage representing a QR code + */ public func generateQRCodeImage() throws -> UIImage { return try self.generateQRCodeImage(forType: nil) } + /** + Returns a UIImage representing a QR code for the given external id type of this device. + Resorts to the default id of the device If the external id is not found for the given type. + + - returns: UIImage representing a QR code + */ public func generateQRCodeImage(forType type: String?) throws -> UIImage { var idString: String @@ -506,40 +738,4 @@ public struct C8yDevice: C8yObject { return UIImage(systemName: "xmark.circle") ?? UIImage() } - - public func qrCodeScannedLineDoesMatchExternalId(line: String, separator: String.Element?) -> Bool { - - if (separator != nil) { - - let parts = line.split(separator: separator!) - let type: String = String(parts[0]) - let ref: String = String(parts[1]) - var id = self.externalIds[type] - - if (id == nil) { - id = self.externalIds[type.lowercased()] - } - - return id != nil && id?.externalId == ref.lowercased().replacingOccurrences(of: " ", with: "").replacingOccurrences(of:":", with:"") - } else { - - for id in self.externalIds.values { - if (id.externalId == line) { - return true - } - } - - return false - } - } - - public func availableMetrics() -> [C8yDataPoints.DataPoint]? { - - if (self.dataPoints != nil) { - return self.dataPoints!.dataPoints - } else { - return nil - } - } - } diff --git a/Sources/c8y-lib/devices/EditableDevice.swift b/Sources/c8y-lib/devices/EditableDevice.swift index ea8db5e..39c3b53 100644 --- a/Sources/c8y-lib/devices/EditableDevice.swift +++ b/Sources/c8y-lib/devices/EditableDevice.swift @@ -129,7 +129,7 @@ public class C8yEditableDevice: ObservableObject, Equatable { } } - @Published public var category: C8yDeviceCategory = .Unknown { + @Published public var category: C8yDevice.DeviceCategory = .Unknown { didSet { self.emitDidChange(self.category.rawValue) } @@ -215,7 +215,7 @@ public class C8yEditableDevice: ObservableObject, Equatable { _deviceWrapper = deviceWrapper } - public init(_ id: String, name: String, supplierName: String?, modelName: String, category: C8yDeviceCategory, operations: [String], revision: String?, firmware: String?, requiredResponseInterval: Int) { + public init(_ id: String, name: String, supplierName: String?, modelName: String, category: C8yDevice.DeviceCategory, operations: [String], revision: String?, firmware: String?, requiredResponseInterval: Int) { self.externalId = id self.externalIdType = "UUID" diff --git a/Sources/c8y-lib/devices/EditableGroup.swift b/Sources/c8y-lib/devices/EditableGroup.swift index 16c0015..a404b97 100644 --- a/Sources/c8y-lib/devices/EditableGroup.swift +++ b/Sources/c8y-lib/devices/EditableGroup.swift @@ -218,7 +218,7 @@ public class C8yEditableGroup: ObservableObject { var group = C8yGroup(self.c8yId, name: self.name, category: self.category, parentGroupName: parentGroupName, notes: notes.isEmpty ? nil : notes) - group.info = C8yGroupInfo(orgName: self.orgName, subName: nil, address: nil, contact: nil, planning: nil) + group.info = C8yGroup.Info(orgName: self.orgName, subName: nil, address: nil, contact: nil, planning: nil) if (self.lat != 0 && self.lng != 0) { group.position = C8yManagedObject.Position(lat: self.lat, lng: self.lng, alt: self.alt) @@ -245,7 +245,7 @@ public class C8yEditableGroup: ObservableObject { group.groupCategory = self.category //group.orgCategory = self.orgCategory - group.info = C8yGroupInfo(orgName: self.orgName, subName: nil, address: nil, contact: nil, planning: nil) + group.info = C8yGroup.Info(orgName: self.orgName, subName: nil, address: nil, contact: nil, planning: nil) if (self.lat != 0 && self.lng != 0) { group.position = C8yManagedObject.Position(lat: self.lat, lng: self.lng, alt: self.alt) diff --git a/Sources/c8y-lib/devices/Group.swift b/Sources/c8y-lib/devices/Group.swift index adf15ad..688c2bb 100644 --- a/Sources/c8y-lib/devices/Group.swift +++ b/Sources/c8y-lib/devices/Group.swift @@ -11,16 +11,28 @@ import Combine import UIKit +/** +Encapsulates a c8y `C8yManagedObject` managed object and treats it as a group exposing attributes and methods typically attributed to managing a group. + +Also includes a number of custom atributes to better categorise devices such as `groupCategory`, `info` etc. +*/ public struct C8yGroup: C8yObject { + /** + client side id, required by SwiftUI for display purposes + */ public var id = UUID().uuidString + /** + Dictionary of all related external id's. + Not populated by default, unless you use the class `C8yAssetCollection` to manage your groups and devices + */ public var externalIds: [String:C8yExternalId] = [String:C8yExternalId]() - - public static func == (lhs: C8yGroup, rhs: C8yGroup) -> Bool { - lhs.c8yId == rhs.c8yId - } - + + /** + Implemented in accordance to protocol `C8yObject` in order to categorise the type of group + e.g. physical building, room etc. or logical folder, division etc. + */ public var groupCategory: C8yGroupCategory { get { if (self.wrappedManagedObject.properties[C8Y_MANAGED_OBJECTS_XGROUP_CATEGORY] != nil) { @@ -50,9 +62,16 @@ public struct C8yGroup: C8yObject { } } + /** + String representing the hierachy in which group belongs, i.e. list the parent group in which device is nested. + This is only provided if you used `C8yAssetCollection` to fetch the device + */ public internal(set) var hierachy: String? - public var info: C8yGroupInfo { + /** + Custom attribute to locate the group if it represents a physical category such as Site, Building or Room. + */ + public var info: Info { didSet { if (self.info.address != nil) { self.wrappedManagedObject.properties[JC_MANAGED_OBJECT_ADDRESS] = self.info.address! @@ -72,6 +91,9 @@ public struct C8yGroup: C8yObject { } } + /** + Returns a list of all the subgroups associated with this group + */ public var subGroups: [C8yGroup] { get { var subGroups: [C8yGroup] = [] @@ -87,6 +109,9 @@ public struct C8yGroup: C8yObject { } } + /** + Returns a list of all the subgroups and devices associated with this group + */ public internal(set) var children: [AnyC8yObject] = [AnyC8yObject]() public var deviceCount: Int { @@ -101,24 +126,45 @@ public struct C8yGroup: C8yObject { return count } + /** + Represents the wrapped Managed Object that defines this group + */ public var wrappedManagedObject: C8yManagedObject - public init(_ c8yId: String) { - // fake group - - self.init(C8yManagedObject(name: "top", type: "", notes: ""), parentGroupName: nil) - self.wrappedManagedObject.updateId(c8yId) - } - + /** + Constructor to create a group for the given c8y managed object + */ public init(_ obj: C8yManagedObject) { self.init(obj, parentGroupName: nil) } - init(_ c8yId: String?, name: String, category: C8yGroupCategory, parentGroupName: String?, notes: String?) { + /** + Constructor to define a new group with the given attributes + - parameter c8yId: required if you want to create a group for an existing c8y group, nil if you want to create a new group + - parameter name: The name attributed to the group + - parameter isTopLevelGroup: if true the group will be visible in groups menu navigation on the left hand side of the Cumulocity web app. Otherwise will only be available as a sub-folder once you have assigned it to a parent group + - parameter notes: optional notes to be associated with the group + */ + internal init(_ c8yId: String?, name: String, isTopLevelGroup: Bool, category: C8yGroupCategory, notes: String?) { + + self.init(C8yManagedObject(name: name, type: isTopLevelGroup ? C8Y_MANAGED_OBJECTS_GROUP_TYPE : C8Y_MANAGED_OBJECTS_SUBGROUP_TYPE, notes: notes), parentGroupName: nil) + + self.groupCategory = category + + if (c8yId != nil) { + self.wrappedManagedObject.updateId(c8yId!) + } + + self.wrappedManagedObject.properties[C8Y_MANAGED_OBJECTS_XGROUP_CATEGORY] = C8yStringWrapper(category.rawValue) + } + + internal init(_ c8yId: String?, name: String, category: C8yGroupCategory, parentGroupName: String?, notes: String?) { self.init(C8yManagedObject(name: name, type: parentGroupName == nil ? C8Y_MANAGED_OBJECTS_GROUP_TYPE : C8Y_MANAGED_OBJECTS_SUBGROUP_TYPE, notes: notes), parentGroupName: parentGroupName) + self.groupCategory = category + if (c8yId != nil) { self.wrappedManagedObject.updateId(c8yId!) } @@ -126,23 +172,40 @@ public struct C8yGroup: C8yObject { self.wrappedManagedObject.properties[C8Y_MANAGED_OBJECTS_XGROUP_CATEGORY] = C8yStringWrapper(category.rawValue) } - init(_ obj: C8yManagedObject, parentGroupName: String?) { + internal init(_ c8yId: String) { + // fake group + + self.init(C8yManagedObject(name: "top", type: "", notes: ""), parentGroupName: nil) + self.wrappedManagedObject.updateId(c8yId) + } + + internal init(_ obj: C8yManagedObject, parentGroupName: String?) { self.hierachy = parentGroupName self.wrappedManagedObject = obj let orgName = obj.properties[C8Y_MANAGED_OBJECTS_XORG_NAME] != nil ? (obj.properties[C8Y_MANAGED_OBJECTS_XORG_NAME] as! C8yStringWrapper).value : "undefined" - self.info = C8yGroupInfo(orgName: orgName, subName: nil, address: obj.properties[JC_MANAGED_OBJECT_ADDRESS] as? C8yAddress, + self.info = Info(orgName: orgName, subName: nil, address: obj.properties[JC_MANAGED_OBJECT_ADDRESS] as? C8yAddress, contact: obj.properties[JC_MANAGED_OBJECT_CONTACT] as? C8yContactInfo, planning: obj.properties[JC_MANAGED_OBJECT_PLANNING] as? C8yPlanning) } + public static func == (lhs: C8yGroup, rhs: C8yGroup) -> Bool { + lhs.c8yId == rhs.c8yId + } + + /** + Convenience method to determine if the given group matches all of the same attributes as this group + */ public func isDifferent(_ group: C8yGroup) -> Bool { return group.info.isDifferent(group.info) || (position == nil && group.position != nil) || (position != nil && position!.isDifferent(group.position)) } + /** + Convenience method to determine if the given device matches on of the devices associated with this group + */ public func isDifferent(_ device: C8yDevice) -> Bool { let obj: AnyC8yObject? = self.childWith(c8yId: device.c8yId!, returnParent: false) @@ -155,6 +218,13 @@ public struct C8yGroup: C8yObject { } } + /** + Returns a string representing the default external id and type if provided or if not the c8y internal id. + + Format is key='value' e.g. + c8y_Serial=122434344 + c8y_Id=9393 + */ public func defaultIdAndType() -> String { var idType = "c8yId" @@ -168,6 +238,9 @@ public struct C8yGroup: C8yObject { return "\(idType)=\(id ?? "unassigned")" } + /** + Returns the default external id if provided or if not the c8y internal id. + */ public func defaultId() -> String? { var id = self.c8yId @@ -179,6 +252,11 @@ public struct C8yGroup: C8yObject { return id } + /** + Returns a UIImage representing a QR code of the default id of this device + + - returns: UIImage representing a QR code + */ public func generateQRCodeImage() throws -> UIImage { return try self.generateQRCodeImage(forType: nil) } @@ -192,11 +270,13 @@ public struct C8yGroup: C8yObject { } } - public func contains(_ c8yId: String) -> Bool { - - return self.objectOf(c8yId: c8yId) != nil - } - + /** + Returns true if the given external id matches one for this group + + - parameter forExternalId: the value, must match the value for the associated type + - parameter type: describes external id, must match a type given in `externalIds` + - returns: true if a match is found, false otherwise + */ public func match(forExternalId id: String, type: String?) -> Bool { if (type == nil || type == "c8yId") { @@ -206,7 +286,33 @@ public struct C8yGroup: C8yObject { } } - func groupFor(ref: String) -> C8yGroup? { + /** + Returns true if a sub-asset with the given internal id is found in this group or one its children + - parameter c8yId: internal id of the asset to check for + - returns: true if found somewhere in groups children + */ + public func contains(_ c8yId: String) -> Bool { + + return self.objectOf(c8yId: c8yId) != nil + } + + /** + returns the sub-group matching the given external id in this group or one of its children + - parameter ref: either name or internal id of asset for which we want to find its parent + - returns: parent group of sub-asset or nil if the asset does not exist + */ + public func group(forExternalId id: String, ofType type: String) -> C8yGroup? { + + return self.finder(nil, ext: id, ofType: type) + } + + /** + returns the sub-group matching the given reference which could be the name of the asset or its internal id + Will also check sub-groups continuously + - parameter ref: either name or internal id of asset for which we want to find its parent + - returns: parent group of sub-asset or nil if the asset does not exist + */ + func group(ref: String) -> C8yGroup? { var found: C8yGroup? = nil @@ -215,7 +321,7 @@ public struct C8yGroup: C8yObject { found = self } else if (c.type == .C8yGroup) { - found = (c.wrappedValue() as C8yGroup).groupFor(ref: ref) + found = (c.wrappedValue() as C8yGroup).group(ref: ref) } if (found != nil) { @@ -226,45 +332,64 @@ public struct C8yGroup: C8yObject { return found } + /** + returns the device within this group or one of its children for the given id + - parameter c8yId: internal id of the device or group to be searched for + - returns: Found device or nil if not found + */ + public func device(withC8yId c8yId: String?) -> C8yDevice? { + + return self.finder(c8yId, ext: nil, ofType: nil) + } + + /** + returns the device within this group or one of its children for the given external id + + - parameter forExternalId: the value, must match the value for the associated type + - parameter type: describes external id, must match a type given in `externalIds` + - returns: true if a match is found, false otherwise + */ + public func device(forExternalId id: String, ofType type: String) -> C8yDevice? { + + return self.finder(nil, ext: id, ofType: type) + } + + /** + Will return the parent group of the asset referred to by the given internal id. + This could be the group itself if the asset is an immediate child of this group or a sub-group if not + - parameter c8yId: internal id of the device or group to be searched for + - returns: Found asset or nil if not found + */ func parentOf(c8yId: String) -> AnyC8yObject? { return childWith(c8yId: c8yId, returnParent : true) } + /** + returns the asset within this group or one of its children whether it is a group + or device. + - parameter c8yId: internal id of the device or group to be searched for + - returns: Found asset or nil if not found + */ func objectOf(c8yId: String) -> AnyC8yObject? { return childWith(c8yId: c8yId, returnParent: false) } - private func childWith(c8yId: String, returnParent: Bool) -> AnyC8yObject? { - - var found: AnyC8yObject? = nil - - if (self.c8yId == c8yId) { - return AnyC8yObject(self) - } - else if (self.hasChildren) { - - for c in self.children { - if (c.type == .C8yDevice && (c.c8yId == c8yId || (c.wrappedValue() as C8yDevice).name == c8yId)) { - found = returnParent ? AnyC8yObject(self) : c - } - else if (c.type == .C8yGroup) { - found = (c.wrappedValue() as C8yGroup).childWith(c8yId: c8yId, returnParent: returnParent) - } - - if (found != nil) { - break - } - } - } - - return found - } - + /** + Adds the given asset to the group + - parameter object: asset to be added to the group + */ public mutating func addToGroup(_ object: T) { _ = self._update(object, updateIfPresent: true) } + /** + Adds the given asset to the sub-group within this group one of its children + + - parameter c8yIdOfSubGroup: internal id of the sub group + - parameter object: asset to be added to the group + - returns: true if the sub group was found and the asset added, false if the sub group cannot be found + */ public mutating func addToGroup(c8yIdOfSubGroup c8yId: String, object: T) -> Bool { if (self.c8yId == c8yId) { @@ -290,32 +415,27 @@ public struct C8yGroup: C8yObject { return false } + /** + Removes the specified asset from the group or sub group of one of its children + - parameter c8yId: id of the asset to be removed + - returns: true if the asset was found and removed + */ public mutating func removeFromGroup(_ c8yId: String) -> Bool { return self.replace(c8yId, object: C8yGroup("")) } + /** + Replaces the current asset in the group or sub group of one of its children + - parameter object: The object to be replaced + - returns: true if the asset was found and replaced, false if not + */ public mutating func replaceInGroup(_ object: T) -> Bool { return self.replace(object.c8yId!, object: object) } - - public func device(forId id: String?) -> C8yDevice? { - - return self.finder(id, ext: nil, ofType: nil) - } - - public func device(forExternalId id: String, ofType type: String) -> C8yDevice? { - - return self.finder(nil, ext: id, ofType: type) - } - - public func group(forExternalId id: String, ofType type: String) -> C8yGroup? { - - return self.finder(nil, ext: id, ofType: type) - } - public func indexOfManagedObject(_ obj: C8yManagedObject) -> Int { + private func indexOfManagedObject(_ obj: C8yManagedObject) -> Int { var index = -1 @@ -329,7 +449,7 @@ public struct C8yGroup: C8yObject { return index } - func finder(_ id: String?, ext: String?, ofType type: String?) -> T? { + private func finder(_ id: String?, ext: String?, ofType type: String?) -> T? { var found: T? = nil @@ -356,6 +476,32 @@ public struct C8yGroup: C8yObject { return found } + private func childWith(c8yId: String, returnParent: Bool) -> AnyC8yObject? { + + var found: AnyC8yObject? = nil + + if (self.c8yId == c8yId) { + return AnyC8yObject(self) + } + else if (self.hasChildren) { + + for c in self.children { + if (c.type == .C8yDevice && (c.c8yId == c8yId || (c.wrappedValue() as C8yDevice).name == c8yId)) { + found = returnParent ? AnyC8yObject(self) : c + } + else if (c.type == .C8yGroup) { + found = (c.wrappedValue() as C8yGroup).childWith(c8yId: c8yId, returnParent: returnParent) + } + + if (found != nil) { + break + } + } + } + + return found + } + private mutating func replace(_ c8yId: String, object: T) -> Bool { var matched: Bool = false @@ -426,50 +572,50 @@ public struct C8yGroup: C8yObject { return false } } -} - + + public struct Info { + + public internal(set) var orgName: String + public internal(set) var subName: String? + + public internal(set) var contractRef: String? + + public internal(set) var address: C8yAddress? + + public internal(set) var planning: C8yPlanning? + + public internal(set) var siteOwner: C8yContactInfo? + public internal(set) var adminOwner: C8yContactInfo? + + public init(orgName: String, subName: String?, address: C8yAddress?, contact: C8yContactInfo?, planning: C8yPlanning?) { + + self.orgName = orgName + self.subName = subName + self.address = address + self.siteOwner = contact + self.planning = planning + + self.adminOwner = nil + self.contractRef = nil + } + + public func isDifferent(_ info: Info?) -> Bool { + + if (info == nil) { + return true + } else { + return self.orgName != info?.orgName + || self.subName != info?.subName + || self.contractRef != info?.contractRef + || self.address == nil && info?.address != nil + || self.address == nil && info?.address != nil + || self.siteOwner == nil && info?.siteOwner != nil + || self.planning == nil && info?.planning != nil + || (self.address != nil && self.address!.isDifferent(info!.address)) + || (self.siteOwner != nil && self.siteOwner!.isDifferent(info!.siteOwner)) + || (self.planning != nil && self.planning!.isDifferent(info!.planning)) + } + } + } -public struct C8yGroupInfo { - - public internal(set) var orgName: String - public internal(set) var subName: String? - - public internal(set) var contractRef: String? - - public internal(set) var address: C8yAddress? - - public internal(set) var planning: C8yPlanning? - - public internal(set) var siteOwner: C8yContactInfo? - public internal(set) var adminOwner: C8yContactInfo? - - public init(orgName: String, subName: String?, address: C8yAddress?, contact: C8yContactInfo?, planning: C8yPlanning?) { - - self.orgName = orgName - self.subName = subName - self.address = address - self.siteOwner = contact - self.planning = planning - - self.adminOwner = nil - self.contractRef = nil - } - - public func isDifferent(_ info: C8yGroupInfo?) -> Bool { - - if (info == nil) { - return true - } else { - return self.orgName != info?.orgName - || self.subName != info?.subName - || self.contractRef != info?.contractRef - || self.address == nil && info?.address != nil - || self.address == nil && info?.address != nil - || self.siteOwner == nil && info?.siteOwner != nil - || self.planning == nil && info?.planning != nil - || (self.address != nil && self.address!.isDifferent(info!.address)) - || (self.siteOwner != nil && self.siteOwner!.isDifferent(info!.siteOwner)) - || (self.planning != nil && self.planning!.isDifferent(info!.planning)) - } - } } diff --git a/Sources/c8y-lib/devices/MutableDevice.swift b/Sources/c8y-lib/devices/MutableDevice.swift index 020c79e..d0d2889 100644 --- a/Sources/c8y-lib/devices/MutableDevice.swift +++ b/Sources/c8y-lib/devices/MutableDevice.swift @@ -11,10 +11,25 @@ import Combine import CoreLocation +/** +Presents a `C8Device` device that can be observed for changed within in a SwiftUI View directly. +Static device data is still available via the wrapped `device` attribute. In addition it provides dynamic data that can be observed for changes + +Dynamic data is mostly related to dynamically updated metrics, gps position, etc. +You can also choose to continuously update a preferred metric and obtain the latest battery level if applicable. + +Use the `EditableDevice` class if you want to provide a view to allow a user to edit a device. +*/ public class C8yMutableDevice: ObservableObject { - + + /** + Wrapped device to which we want to add dynamic data + */ @Published public var device: C8yDevice + /** + Current GPS location of device + */ @Published public var position: CLLocationCoordinate2D? = nil { didSet { if (position != nil) { @@ -23,6 +38,9 @@ public class C8yMutableDevice: ObservableObject { } } + /** + Set to true if you want to load latest metrics for the device, value will reset back to false once reload has completed + */ @Published public var reloadMetrics: Bool = false { didSet { @@ -32,6 +50,9 @@ public class C8yMutableDevice: ObservableObject { } } + /** + Set to true if you want to load latest logs for the device, value will reset back to false once reload has completed + */ @Published public var reloadLogs: Bool = false { didSet { @@ -41,6 +62,9 @@ public class C8yMutableDevice: ObservableObject { } } + /** + Set to true if you want to load latest alarms for the device, value will reset back to false once reload has completed + */ @Published public var reloadAlarms: Bool = false { didSet { if (self.reloadAlarms) { @@ -49,6 +73,9 @@ public class C8yMutableDevice: ObservableObject { } } + /** + Set to true if you want to load latest operations history for the device, value will reset back to false once reload has completed + */ @Published public var reloadOperations: Bool = false { didSet { if (self.reloadOperations) { @@ -57,22 +84,64 @@ public class C8yMutableDevice: ObservableObject { } } + /** + Returns the current battery level if available (returns -2 if not applicable) + */ @Published public private(set) var batteryLevel: Double = -2 + /** + Returns the primary metric for this device e.g. Temperature, ambiance etc. + This attribute is not observable as it can change too frequently, instead use the method `primaryMetricPublisher(preferredMetric:refreshInterval:)` + along with the 'onReceive' SwiftUI event to ensure you can update your view fragment efficiently e.g. + + ``` + VStack(alignment: .leading) { + Text("termperature is \(self.$primaryMeasurement.min)") + }.onReceive(self.deviceWrapper.primaryMetricPublisher(preferredMetric: self.preferredMetric, refreshInterval: self.deviceRefreshInterval)) { v in + + print("received measurement: \(v) for \(self.deviceWrapper.device.name)") + self.primaryMeasurement = v + } + ``` + */ public private(set) var primaryMetric: Measurement = Measurement() + /** + Returns the primary metric history + */ @Published public private(set) var primaryMetricHistory: MeasurementSeries = MeasurementSeries() + /** + Returns all available measurements captured by Cumulocity for this device + */ @Published public var measurements: [String:[C8yMeasurement]] = [:] + /** + Returns all the latest events received by Cumulocity for the device + */ @Published public var events: [C8yEvent] = [] + /** + Returns all the latest alarms received by Cumulocity for the device + */ @Published public var alarms: [C8yAlarm] = [] + /** + Returns a list of all operations that are pending or completed that have been submitted to Cumulocity for this device + */ @Published public var operationHistory: [C8yOperation] = [] + /** + Indicates whether there is currently a background thread in place to periodically fetch the latest preferred metric and battery level + Use the method `startMonitorForPrimaryMetric(_:refreshInterval:)` + */ public private(set) var isMonitoring: Bool = false + /** + Convenience attribute to try and detect if a device is currently being restarted, i.e. someone submitted a 'c8y_Restart' operation + that is now flagged in Cumulocity as in the state 'PENDING' or 'EXECUTING', in which case this attribute returns true. + Will be false once we receive an operation update for 'c8y_Restart' in `operationHistory` with an ulterior date and the state of either 'COMPLETED' or 'FAILED' + */ @Published public var isRestarting: Bool = false { didSet { if (self.isRestarting) { @@ -84,8 +153,15 @@ public class C8yMutableDevice: ObservableObject { } private var _restartTime: Date? = nil + /** + Convenience attribute that caches the last binary file associated with the device that was fetched from Cumulocity + via the method `attachmentForId(id)` or posted via the method `addAttachment(filename:fileType:content:)` + */ public internal(set) var lastAttachment: JcMultiPartContent.ContentPart? = nil + /** + Associated Cumulocity connection info that allows this object to fetch/post data + */ public var conn: C8yCumulocityConnection? private var _deviceMetricsTimer: JcRepeatingTimer? @@ -97,10 +173,18 @@ public class C8yMutableDevice: ObservableObject { private var _cachedResponseInterval: Int = 30 + /** + Default constructor representing a new `C8yDevice` + */ public init() { self.device = C8yDevice() } - + + /** + Constructor to create a mutable device for the give device + - parameter device: Device to which we want to fetch mutable data + - parameter connection: Connection details in order to connect to Cumulocity + */ public init(_ device: C8yDevice, connection: C8yCumulocityConnection) { self.device = device @@ -118,6 +202,12 @@ public class C8yMutableDevice: ObservableObject { private var _loadBatteryAndPrimaryMetricValuesDONE: Bool = false private var _preferredMetric: String? = nil + /** + Provides a publisher that can be used to listen for periodic updates to primary metric + - parameter preferredMetric: label of the measurement to periodically fetched requires both name and series separated by a dot '.' e.g. 'Temperature.T' + - parameter refreshInterval: period in seconds in which to refresh values + - returns: Publisher that will issue updated metrics periodically + */ public func primaryMetricPublisher(preferredMetric: String?, refreshInterval: Double = -1) -> AnyPublisher { if (_loadBatteryAndPrimaryMetricValuesDONE && self._preferredMetric == preferredMetric) { @@ -156,6 +246,10 @@ public class C8yMutableDevice: ObservableObject { return _monitorPublisher!.eraseToAnyPublisher() } + /** + Submits an operation to Cumulocity to ask the device to restart + *NOTE* This will only work if supported by the device and its agent and might take several minutes or even hours before it is enacted + */ public func restart() { if (self.isRestarting) { @@ -190,6 +284,11 @@ public class C8yMutableDevice: ObservableObject { } } + /** + Sets the device's requiredResponseInterval to -1 to trigger Cumulocity's maintenance mode. + Maintenance mode deactivates all of the devices alarms to avoid false flags. + Calling this method if already in maintenance mode sets the requiredResponseInterval back to the last know value or 30 minutes if not known. + */ public func toggleMaintainanceMode() throws { if (device.wrappedManagedObject.requiredAvailability == nil) { @@ -211,6 +310,10 @@ public class C8yMutableDevice: ObservableObject { }.store(in: &self._cancellable) } + /** + Submits an operation to switch the relay and also synchronises the device relay attribute `C8yDevice.relayState`. + *NOTE* - Only applicable if the device or agent supports it. + */ public func toggleRelay() throws { var state: C8yManagedObject.RelayStateType = .CLOSED @@ -258,7 +361,13 @@ public class C8yMutableDevice: ObservableObject { }.store(in: &self._cancellable) } - public func updateDeviceProperty(withKey key: String, value: String) throws { + /** + Updates the server side Cumulocity Managed Object based on the properties provided here. + - parameter withKey: name of the managed object attribute to updated/added + - parameter value: The value to be assigned + - throws: Invalid key/value pair + */ + public func updateDeviceProperty(withKey key: String, value: String) throws { try C8yManagedObjectsService(self.conn!).put(C8yManagedObject.init(self.device.c8yId!, properties: Dictionary(uniqueKeysWithValues: zip([key], [value])))) .receive(on: RunLoop.main) @@ -269,122 +378,132 @@ public class C8yMutableDevice: ObservableObject { }).store(in: &self._cancellable) } - public func provision(completionHandler: @escaping (Error?) -> Void) { + /** + Provisions the netwok connection for the device. + The implementation is provided via the appropriate network type `C8yNetworks.provision(_:conn)` + + This method does nothing If no specific network type is specified i.e. the device connects over standard ip public network + + - returns: Publisher with updated device + - throws: If network is invalid or not recognised + */ + public func provision() throws -> AnyPublisher { - do { - try C8yNetworks.provision(device, conn: self.conn!) - .receive(on: RunLoop.main) - .sink(receiveCompletion: { completion in - switch completion { - case .failure(let error): - completionHandler(error) - case .finished: - completionHandler(nil) - } - }, receiveValue: { device in - self.device = device - }).store(in: &self._cancellable) - } catch { - completionHandler(error) - } + return try C8yNetworks.provision(device, conn: self.conn!) + .receive(on: RunLoop.main) + .map({device -> C8yDevice in + self.device = device + return device + }).mapError({ error -> CommError in + return CommError(reason: error.localizedDescription) + }).eraseToAnyPublisher() } - public func deprovision(completionHandler: @escaping (Error?) -> Void) { + /** + Deprovisions the netwok connection from the device. + The implementation is provided via the appropriate network type `C8yNetworks.deprovision(_:conn)` + + This method does nothing If no specific network type is specified i.e. the device connects over standard ip public network + + - returns: Publisher with updated device + - throws: If network is invalid or not recognised + */ + public func deprovision()throws -> AnyPublisher { - do { - try C8yNetworks.deprovision(device, conn: self.conn!) - .receive(on: RunLoop.main) - .sink(receiveCompletion: { completion in - switch completion { - case .failure(let error): - completionHandler(error) - case .finished: - completionHandler(nil) - } - }, receiveValue: { device in - self.device = device - }).store(in: &self._cancellable) - } catch { - completionHandler(error) - } + return try C8yNetworks.deprovision(device, conn: self.conn!) + .receive(on: RunLoop.main) + .map({device -> C8yDevice in + self.device = device + return device + }).mapError({ error -> CommError in + return CommError(reason: error.localizedDescription) + }).eraseToAnyPublisher() } - public func runOperation(_ op: C8yOperation) throws -> AnyPublisher { - - let result = PassthroughSubject() - + /** + Submits the given operation to Cumulocity and records it in `operationHistory` + The operation will have an initial status of PENDING + + - parameter operation: The operation to be posted to Cumulocity for eventual execution by the device. + - returns: Publisher with cumulocity internal id of new operation. + - throws: Invalid operation + */ + public func runOperation(_ op: C8yOperation) throws -> AnyPublisher { + try C8yOperationService(self.conn!).post(operation: op) - .receive(on: RunLoop.main) - .sink(receiveCompletion: { (completion) in - // nothing to do - switch completion { - case .failure(let error): - result.send(error.localizedDescription) - default: - result.send(nil) - } - }) { (response) in - self.operationHistory.insert(op, at: 0) - self.objectWillChange.send() - }.store(in: &self._cancellable) - - return result.eraseToAnyPublisher() + .receive(on: RunLoop.main) + .map({ response -> String in + + let nop = response.content + self.operationHistory.insert(nop!, at: 0) + return nop!.id! + + }).mapError({ error -> CommError in + return CommError(reason: error.localizedDescription) + }).eraseToAnyPublisher() } - public func postNewAlarm(type: String, severity: C8yAlarm.Severity, text: String, completionHandler: @escaping (C8yAlarm?) -> Void) throws { + /** + Submits the new alarm to Cumulocity and records it in `alarms` + The alarm will have an initial status of ACTIVE + + - parameter type: Describes the type of alarm being submitted. + - parameter severity: Either CRITICAL, MAJOR, MINOR or WARNING + - parameter text: Detailed description of alarm + - returns: Publisher with new cumulocity alarm + - throws: Invalid alarm + */ + public func postNewAlarm(type: String, severity: C8yAlarm.Severity, text: String) throws -> AnyPublisher { var alarm = C8yAlarm(forSource: self.device.c8yId!, type: type, description: text, status: C8yAlarm.Status.ACTIVE, severity: severity) - try C8yAlarmsService(self.conn!).post(alarm) + return try C8yAlarmsService(self.conn!).post(alarm) .receive(on: RunLoop.main) - .sink(receiveCompletion: { completion in + .map({ (response) -> C8yAlarm in - switch completion { - case .failure: - completionHandler(nil) - default: - print("done") - } - - }, receiveValue: { response in - if (response.content != nil) { - alarm.id = response.content! - self.alarms.append(alarm) + alarm.id = response.content! + self.alarms.append(alarm) - self.device.alarms = self.alarmSummary(self.alarms) - - completionHandler(alarm) - } else { - completionHandler(nil) - } - }).store(in: &self._cancellable) + self.device.alarms = self.alarmSummary(self.alarms) + + return alarm + + }).mapError({ error -> CommError in + return CommError(reason: error.localizedDescription) + }).eraseToAnyPublisher() } - public func updateAlarm(_ alarm: C8yAlarm, completionHandler: @escaping (Bool) -> Void) throws { + /** + Updates the existing alarm to Cumulocity and the copy in `alarms` + + - parameter alarm: Alarm to be updated in Cumulocity + - returns: Publisher with updated cumulocity alarm + - throws: Invalid alarm + */ + public func updateAlarm(_ alarm: C8yAlarm) throws -> AnyPublisher { - try C8yAlarmsService(self.conn!).put(alarm) + return try C8yAlarmsService(self.conn!).put(alarm) .receive(on: RunLoop.main) - .sink(receiveCompletion: { - completion in - switch completion { - case .failure: - completionHandler(false) - default: - for z in self.alarms.indices { - if self.alarms[z].id == alarm.id { - self.alarms[z] = alarm - } + .map({ response -> C8yAlarm in + + for z in self.alarms.indices { + if self.alarms[z].id == alarm.id { + self.alarms[z] = alarm } - - self.device.alarms = self.alarmSummary(self.alarms) - - completionHandler(true) } - }) { (result) in - // do nothing - }.store(in: &self._cancellable) + + self.device.alarms = self.alarmSummary(self.alarms) + + return alarm + }).mapError({ error -> CommError in + return CommError(reason: error.localizedDescription) + }).eraseToAnyPublisher() } + /** + Convenience method to create a Managed Object containing only the device's GPS position + - returns: Returns a `C8yDevice` instance referencing only the device internal id and it's GPS position + */ public func toDevicePositionUpdate() -> C8yDevice { var pd: C8yDevice = C8yDevice(self.device.c8yId!) @@ -398,6 +517,10 @@ public class C8yMutableDevice: ObservableObject { return pd } + /** + Fetches latest device metrics, views will be updated automatically via published attribute `measurements` + You must ensure that your SwiftUI View references this class object either as a @ObservedObject or @StateObject + */ public func updateMetricsForToday() { self.fetchAllMetricsForToday() .receive(on: RunLoop.main) @@ -409,11 +532,17 @@ public class C8yMutableDevice: ObservableObject { default: print("done") } + + self.reloadMetrics = false }) { results in self.measurements = results }.store(in: &self._cancellable) } + /** + Fetches latest device metrics from Cumulocity + - returns: Publisher containing latest device measurements + */ public func fetchAllMetricsForToday() -> AnyPublisher<[String:[C8yMeasurement]], JcConnectionRequest.APIError> { return C8yMeasurementsService(self.conn!).get(forSource: self.device.c8yId!, pageNum: 0, from: Date().advanced(by: -86400), to: Date(), reverseDateOrder: true).map({response in @@ -437,6 +566,10 @@ public class C8yMutableDevice: ObservableObject { }).eraseToAnyPublisher() } + /** + Fetches latest device event logs, views will be updated automatically via published attribute `events` + You must ensure that your SwiftUI View references this class object either as a @ObservedObject or @StateObject + */ public func updateEventLogsForToday() { self.fetchEventLogsForToday() @@ -449,11 +582,17 @@ public class C8yMutableDevice: ObservableObject { default: print("done") } + + self.reloadLogs = false }) { results in self.events = results }.store(in: &self._cancellable) } + /** + Fetches latest device events from Cumulocity + - returns: Publisher containing latest device events + */ public func fetchEventLogsForToday() -> AnyPublisher<[C8yEvent], JcConnectionRequest.APIError> { return C8yEventsService(self.conn!).get(source: self.device.c8yId!, pageNum: 0).map({response in @@ -463,6 +602,10 @@ public class C8yMutableDevice: ObservableObject { }).eraseToAnyPublisher() } + /** + Fetches latest device operation history, views will be updated automatically via published attribute `operationHistory` + You must ensure that your SwiftUI View references this class object either as a @ObservedObject or @StateObject + */ public func updateOperationHistory() { self.fetchOperationHistory() @@ -475,6 +618,8 @@ public class C8yMutableDevice: ObservableObject { default: print("done") } + + self.reloadOperations = false }) { results in self.operationHistory = results.reversed() @@ -498,6 +643,10 @@ public class C8yMutableDevice: ObservableObject { }.store(in: &self._cancellable) } + /** + Fetches latest device operation history from Cumulocity + - returns: Publisher containing latest operation history + */ public func fetchOperationHistory() -> AnyPublisher<[C8yOperation], JcConnectionRequest.APIError> { return C8yOperationService(self.conn!).get(self.device.c8yId!).map({response in @@ -505,6 +654,10 @@ public class C8yMutableDevice: ObservableObject { }).eraseToAnyPublisher() } + /** + Fetches latest device alarms, views will be updated automatically via published attribute `alarms` + You must ensure that your SwiftUI View references this class object either as a @ObservedObject or @StateObject + */ public func updateAlarmsForToday() { self.fetchActiveAlarmsForToday() @@ -517,11 +670,17 @@ public class C8yMutableDevice: ObservableObject { default: print("done") } + + self.reloadAlarms = false }) { results in self.alarms = results }.store(in: &self._cancellable) } + /** + Fetches latest device alarms from Cumulocity + - returns: Publisher containing latest alarms + */ public func fetchActiveAlarmsForToday() -> AnyPublisher<[C8yAlarm], JcConnectionRequest.APIError> { return C8yAlarmsService(self.conn!).get(source: self.device.c8yId!, status: .ACTIVE, pageNum: 0) @@ -538,6 +697,10 @@ public class C8yMutableDevice: ObservableObject { }).eraseToAnyPublisher() } + /** + Fetches latest device prefered metric from Cumulocity + - returns: Publisher containing latest preferred metric + */ public func fetchMostRecentPrimaryMetric(_ preferredMetric: String?) -> AnyPublisher { if (device.c8yId! != "_new_") { @@ -552,8 +715,8 @@ public class C8yMutableDevice: ObservableObject { mType = String(parts[0]) mSeries = String(parts[1]) } - } else if (self.primaryDataPoints(device).count > 0) { - let metric: [C8yDataPoints.DataPoint] = self.primaryDataPoints(device) + } else if (self.primaryDataPoint().count > 0) { + let metric: [C8yDataPoints.DataPoint] = self.primaryDataPoint() mType = metric[0].reference mSeries = metric[0].value.series } @@ -574,49 +737,13 @@ public class C8yMutableDevice: ObservableObject { return Just(C8yMutableDevice.Measurement()).eraseToAnyPublisher() // dummy } } - - private func getExternalIds() { - - C8yManagedObjectsService(self.conn!).externalIDsForManagedObject(device.wrappedManagedObject.id!).sink(receiveCompletion: { completion in - - }, receiveValue: { response in - self.device.setExternalIds(response.content!.externalIds) - - }).store(in: &self._cancellable) - } - - private func populatePrimaryMetric(_ m: C8yMeasurementSeries, type: String) -> Measurement { - if (m.values.count > 0) { - self.primaryMetric = Measurement(min: m.values.last!.values[0].min, max: m.values.last!.values[0].max, unit: m.series.last!.unit, label: m.series.last!.name, type: type) - - var v: [Double] = [] - var t: [String] = [] - - for r in m.values { - v.append(r.values[0].min) - t.append(r.time.timeString()) - } - - DispatchQueue.main.async { - self.primaryMetricHistory = MeasurementSeries(name: m.series.last!.name, label: type, unit: m.series.last!.unit, yValues: v, xValues: t) - } - } - - print("setting primary metric to \(String(describing: self.primaryMetric.min)) for \(self.device.name)") - - return self.primaryMetric - } - - private func getMeasurementSeries(_ device: C8yDevice, type: String, series: String, interval: Double, connection: C8yCumulocityConnection) -> AnyPublisher.APIError> { - - print("Fetching metrics for device \(device.name), \(type), \(series)") - - return C8yMeasurementsService(connection).getSeries(forSource: device.c8yId!, type: type, series: series, from: Date().addingTimeInterval(-interval), to: Date(), aggregrationType: .MINUTELY).map({response in - return response.content! - }).eraseToAnyPublisher() - } - + /** + Initiates a background thread to periodically refetch the preferred metric from Cumulocity. + Changes will be issued via the publisher returned from the method `primaryMetricPublisher(preferredMetric:refreshInterval:)` + - parameter preferredMetric: label of the measurement to periodically fetched requires both name and series separated by a dot '.' e.g. 'Temperature.T', if not provided will attempt to use first data point in `dataPoints` + - parameter refreshInterval: period in seconds in which to refresh values + */ public func startMonitorForPrimaryMetric(_ preferredMetric: String?, refreshInterval: Double) { self._monitorPublisher = CurrentValueSubject(Measurement()) @@ -639,7 +766,7 @@ public class C8yMutableDevice: ObservableObject { self._deviceMetricsTimer!.suspend() } - if (self._refreshInterval > -1 && (preferredMetric != nil || self.primaryDataPoints(device).count > 0)) { + if (self._refreshInterval > -1 && (preferredMetric != nil || self.primaryDataPoint().count > 0)) { self._deviceMetricsTimer = JcRepeatingTimer(timeInterval: self._refreshInterval) self.isMonitoring = true @@ -657,6 +784,10 @@ public class C8yMutableDevice: ObservableObject { } } + /** + Stops the background thread for the preferred metric refresh and operation history. The thread must have been started by either `startMonitorForPrimaryMetric(_:refreshInterval)` or + `primaryMetricPublisher(preferredMetric:refreshInterval:)` + */ public func stopMonitoring() { if (self._deviceMetricsTimer != nil) { @@ -678,6 +809,10 @@ public class C8yMutableDevice: ObservableObject { } } + /** + Starts a background thread to refresh operation history periodically + - parameter refreshInterval: period in seconds in which to refresh values + */ public func startMonitoringForOperationHistory(_ interval: TimeInterval = -1) { if (self._deviceOperationHistoryTimer != nil) { @@ -700,6 +835,11 @@ public class C8yMutableDevice: ObservableObject { } } + /** + Returns the current status for given operation type, Will return only latest valeu if multiple operations exist for the same type + - parameter type: The type of operation to be queried + - returns: The latest operation for the given type or nil if none found + */ public func statusForOperation(_ type: String) -> C8yOperation? { var op: C8yOperation? = nil @@ -715,6 +855,14 @@ public class C8yMutableDevice: ObservableObject { return op } + /** + Downloads a binary attachment with the given id from Cumolocity and also caches the result in `lastAttachment` + + You can obtain a list of attachments related to this device via the attribute`C8yDevice.attachments`, If the attachment was uploaded via `addAttachment(filename:fileType:content)` + + - parameter id: c8y Internal id of binary attachment to be downloaded + - returns: Publisher containing binary data + */ public func attachmentForId(id: String) -> AnyPublisher.APIError> { if (self.lastAttachment == nil || self.lastAttachment?.id != id) { @@ -730,7 +878,15 @@ public class C8yMutableDevice: ObservableObject { } } - public func addAttachment(filename: String, fileType: String, content: Data) -> AnyPublisher.APIError> { + /** + Uploads the given binary content to Cumulocity and updates the managed object associated with this device to record the resulting binary attachment id. + The resulting id can be referenced via the string list attribute `C8yDevice.attachments`, which in turn is stored in Cumulocity via the attribute `C8Y_MANAGED_OBJECTS_ATTACHMENTS` + - parameter filename: name of file from which data originated + - parameter fileType: content type e.g. application/json or application/png + - parameter content: Binary data encoded in a Data object + - returns: Publisher with cumulocity internal id associated with uploaded binary data + */ + public func addAttachment(filename: String, fileType: String, content: Data) -> AnyPublisher.APIError> { var fname = filename @@ -750,10 +906,21 @@ public class C8yMutableDevice: ObservableObject { self.device.wrappedManagedObject.properties[C8Y_MANAGED_OBJECTS_ATTACHMENTS] = C8yStringWrapper(String.make(array: self.device.attachments)!) - return self.lastAttachment! + return self.lastAttachment!.id! }).eraseToAnyPublisher() } + /** + Replaces the existing attachment reference and uploades the content to Cumulocity. The existing attachment reference is replaced with the new one. + It does delete the existing binary attachment from Cumulocity only the reference to existing attachment contained in the managed object attrbiute `C8Y_MANAGED_OBJECTS_ATTACHMENTS` + and the device's `C8ytDevice.attachments` attribute + + - parameter index: Index into array `C8yDevice.attachments` to indicate which attachment should be replaced. + - parameter filename: name of file from which data originated + - parameter fileType: content type e.g. application/json or application/png + - parameter content: Binary data encoded in a Data object + - returns: Publisher with cumulocity internal id associated with uploaded binary data + */ public func replaceAttachment(index: Int, filename: String, fileType: String, content: Data) -> AnyPublisher.APIError> { return C8yBinariesService(self.conn!).post(name: filename, contentType: fileType, content: content).map({ response in @@ -769,7 +936,7 @@ public class C8yMutableDevice: ObservableObject { }).eraseToAnyPublisher() } - func primaryDataPoints(_ device: C8yDevice) -> [C8yDataPoints.DataPoint] { + private func primaryDataPoint() -> [C8yDataPoints.DataPoint] { //TODO: link this to C8yModels @@ -780,7 +947,49 @@ public class C8yMutableDevice: ObservableObject { } } - func makeError(_ response: JcRequestResponse) -> Error? { + private func getExternalIds() { + + C8yManagedObjectsService(self.conn!).externalIDsForManagedObject(device.wrappedManagedObject.id!).sink(receiveCompletion: { completion in + + }, receiveValue: { response in + self.device.setExternalIds(response.content!.externalIds) + + }).store(in: &self._cancellable) + } + + private func populatePrimaryMetric(_ m: C8yMeasurementSeries, type: String) -> Measurement { + + if (m.values.count > 0) { + self.primaryMetric = Measurement(min: m.values.last!.values[0].min, max: m.values.last!.values[0].max, unit: m.series.last!.unit, label: m.series.last!.name, type: type) + + var v: [Double] = [] + var t: [String] = [] + + for r in m.values { + v.append(r.values[0].min) + t.append(r.time.timeString()) + } + + DispatchQueue.main.async { + self.primaryMetricHistory = MeasurementSeries(name: m.series.last!.name, label: type, unit: m.series.last!.unit, yValues: v, xValues: t) + } + } + + print("setting primary metric to \(String(describing: self.primaryMetric.min)) for \(self.device.name)") + + return self.primaryMetric + } + + private func getMeasurementSeries(_ device: C8yDevice, type: String, series: String, interval: Double, connection: C8yCumulocityConnection) -> AnyPublisher.APIError> { + + print("Fetching metrics for device \(device.name), \(type), \(series)") + + return C8yMeasurementsService(connection).getSeries(forSource: device.c8yId!, type: type, series: series, from: Date().addingTimeInterval(-interval), to: Date(), aggregrationType: .MINUTELY).map({response in + return response.content! + }).eraseToAnyPublisher() + } + + private func makeError(_ response: JcRequestResponse) -> Error? { if (response.status != .SUCCESS) { if (response.httpMessage != nil) { @@ -858,4 +1067,9 @@ public class C8yMutableDevice: ObservableObject { return C8yManagedObject.ActiveAlarmsStatus(warning: w, minor: mr, major: mj, critical: c) } + + public struct CommError: Error { + + public var reason: String? + } } diff --git a/Sources/c8y-lib/priv/AnyC8yObject.swift b/Sources/c8y-lib/priv/AnyC8yObject.swift index 12bc4e4..9e01dec 100644 --- a/Sources/c8y-lib/priv/AnyC8yObject.swift +++ b/Sources/c8y-lib/priv/AnyC8yObject.swift @@ -14,7 +14,7 @@ import CoreImage.CIFilterBuiltins let C8Y_MANAGED_OBJECTS_XORG_CATEGORY = "xOrgCategory" let C8Y_MANAGED_OBJECTS_XORG_NAME = "xOrgName" let C8Y_MANAGED_OBJECTS_XGROUP_CATEGORY = "xGroupCategory" -let C8Y_MANAGED_OBJECTS_XDEVICE_CATEGORY = "xDeviceCategory" +let C8Y_MANAGED_OBJECTS_XDEVICE_CATEGORY = "xC8yDeviceCategory" /** Wrapper to allow objects deviced from `C8yObject` to managed as a collection. @@ -133,7 +133,7 @@ public protocol C8yObject: Identifiable, Equatable { var groupCategory: C8yGroupCategory { get } var orgCategory: C8yOrganisationCategory { get } - var deviceCategory: C8yDeviceCategory { get } + var deviceCategory: C8yDevice.DeviceCategory { get } var operationalLevel: C8yOperationLevel { get } var status: C8yManagedObject.AvailabilityStatus { get } @@ -266,7 +266,7 @@ extension C8yObject { } - public var deviceCategory: C8yDeviceCategory { + public var deviceCategory: C8yDevice.DeviceCategory { get { return .Group } diff --git a/Sources/c8y-lib/priv/GroupLoader.swift b/Sources/c8y-lib/priv/GroupLoader.swift index 14f310b..9edc223 100644 --- a/Sources/c8y-lib/priv/GroupLoader.swift +++ b/Sources/c8y-lib/priv/GroupLoader.swift @@ -138,7 +138,7 @@ class GroupLoader { func processDeviceObject(_ m: C8yManagedObject) { - var device = C8yDevice(m, location: self._path ?? "") + var device = C8yDevice(m, hierachy: self._path ?? "") C8yManagedObjectsService(_conn).externalIDsForManagedObject(device.wrappedManagedObject.id!).sink(receiveCompletion: { completion in diff --git a/Sources/c8y-lib/priv/MockedAssetCollection.swift b/Sources/c8y-lib/priv/MockedAssetCollection.swift index c92ee21..8f3e042 100644 --- a/Sources/c8y-lib/priv/MockedAssetCollection.swift +++ b/Sources/c8y-lib/priv/MockedAssetCollection.swift @@ -47,27 +47,27 @@ public class C8yMockedAssetCollection : C8yAssetCollection { public func testDevice() -> C8yDevice { - return saintJaques!.device(forId: "D0001")! + return saintJaques!.device(withC8yId: "D0001")! } public func testDevice2() -> C8yDevice { - return saintJaques!.device(forId: "D0002")! + return saintJaques!.device(withC8yId: "D0002")! } public func testDevice3() -> C8yDevice { - return saintJaques!.device(forId: "D0003")! + return saintJaques!.device(withC8yId: "D0003")! } public func testDevice4() -> C8yDevice { - return saintJaques!.device(forId: "D0003")! + return saintJaques!.device(withC8yId: "D0003")! } public func testDevice7() -> C8yDevice { - return shop!.device(forId: "D0007")! + return shop!.device(withC8yId: "D0007")! } public func testGroup() -> C8yGroup { @@ -197,7 +197,7 @@ public class C8yMockedAssetCollection : C8yAssetCollection { return newGroup } - private func makeDevice(_ c8yId: String?, name: String, model: String, category: C8yDeviceCategory, status: C8yManagedObject.AvailabilityStatus, criticalAlarms: Int, majorAlarms: Int, minorAlarms: Int, warningAlarms: Int) -> C8yDevice { + private func makeDevice(_ c8yId: String?, name: String, model: String, category: C8yDevice.DeviceCategory, status: C8yManagedObject.AvailabilityStatus, criticalAlarms: Int, majorAlarms: Int, minorAlarms: Int, warningAlarms: Int) -> C8yDevice { var device: C8yDevice = C8yDevice(c8yId, serialNumber: "123456789", withName: name, type: "c8y_device", supplier: "apple", model: model, notes: "Mocked device, how now now brown cow", requiredResponseInterval: 30, revision: "1.1.1", category: category)