diff --git a/Example/Source/Images.xcassets/ groups_light_lc_occupancy_mode.imageset/ groups_light_lc_occupancy_mode.png b/Example/Source/Images.xcassets/ groups_light_lc_occupancy_mode.imageset/ groups_light_lc_occupancy_mode.png new file mode 100644 index 000000000..d14858d8e Binary files /dev/null and b/Example/Source/Images.xcassets/ groups_light_lc_occupancy_mode.imageset/ groups_light_lc_occupancy_mode.png differ diff --git a/Example/Source/Images.xcassets/ groups_light_lc_occupancy_mode.imageset/Contents.json b/Example/Source/Images.xcassets/ groups_light_lc_occupancy_mode.imageset/Contents.json new file mode 100644 index 000000000..94256e4b5 --- /dev/null +++ b/Example/Source/Images.xcassets/ groups_light_lc_occupancy_mode.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : " groups_light_lc_occupancy_mode.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Example/Source/Images.xcassets/groups_light_lc_mode.imageset/Contents.json b/Example/Source/Images.xcassets/groups_light_lc_mode.imageset/Contents.json new file mode 100644 index 000000000..13586233b --- /dev/null +++ b/Example/Source/Images.xcassets/groups_light_lc_mode.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "groups_light_lc_mode.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Example/Source/Images.xcassets/groups_light_lc_mode.imageset/groups_light_lc_mode.png b/Example/Source/Images.xcassets/groups_light_lc_mode.imageset/groups_light_lc_mode.png new file mode 100644 index 000000000..decc69f4f Binary files /dev/null and b/Example/Source/Images.xcassets/groups_light_lc_mode.imageset/groups_light_lc_mode.png differ diff --git a/Example/Source/UI/Base.lproj/Groups.storyboard b/Example/Source/UI/Base.lproj/Groups.storyboard index 95fe448d1..0d6649895 100644 --- a/Example/Source/UI/Base.lproj/Groups.storyboard +++ b/Example/Source/UI/Base.lproj/Groups.storyboard @@ -1,9 +1,9 @@ - + - + @@ -13,24 +13,24 @@ - + - + - - + + - + diff --git a/Example/Source/View Controllers/Groups/GroupControlViewController.swift b/Example/Source/View Controllers/Groups/GroupControlViewController.swift index 222e8f9e7..8707c8afd 100644 --- a/Example/Source/View Controllers/Groups/GroupControlViewController.swift +++ b/Example/Source/View Controllers/Groups/GroupControlViewController.swift @@ -33,7 +33,7 @@ import NordicMesh private class Section { let applicationKey: ApplicationKey - var items: [(modelId: UInt32, models: [Model])] = [] + var items: [(modelId: UInt32, messageIndex: Int, models: [Model])] = [] init(_ applicationKey: ApplicationKey) { self.applicationKey = applicationKey @@ -71,7 +71,8 @@ class GroupControlViewController: ProgressCollectionViewController { + "- Generic OnOff Server,\n" + "- Generic Level Server,\n" + "- Scene Server,\n" - + "- Scene Setup Server.\n\n" + + "- Scene Setup Server,\n" + + "- Light LC Server.\n\n" + "This limitation only applies to the app,\n" + "not the underlying mesh library.", messageImage: #imageLiteral(resourceName: "baseline-groups")) @@ -89,10 +90,12 @@ class GroupControlViewController: ProgressCollectionViewController { section = Section(key) sections.append(section) } - if let index = section.items.firstIndex(where: { $0.modelId == model.modelId }) { - section.items[index].models.append(model) - } else { - section.items.append((modelId: model.modelId, models: [model])) + for messageIndex in 0.. UICollectionViewCell { let section = sections[indexPath.section] let item = section.items[indexPath.row] - let identifier = String(format: "%08X", item.modelId) + let identifier = String(format: "%08X_%d", item.modelId, item.messageIndex) let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! ModelGroupCell cell.group = group cell.applicationKey = section.applicationKey @@ -242,13 +245,28 @@ extension GroupControlViewController: MeshNetworkDelegate { private extension Model { + // Whether the model has dedicated UI for sending messages. var isSupported: Bool { return modelIdentifier == .genericOnOffServerModelId || modelIdentifier == .genericLevelServerModelId || modelIdentifier == .sceneServerModelId || - modelIdentifier == .sceneSetupServerModelId + modelIdentifier == .sceneSetupServerModelId || + modelIdentifier == .lightLCServerModelId } + // Number of different messages which may be sent to a model + // using dedicated UI. + var messageCount: Int { + guard isSupported else { return 0 } + if modelIdentifier == .lightLCServerModelId { + // Light LC Mode, Light LC Occupancy Mode, Light LC Light OnOff + return 3 + } + return 1 + } + + // The 32-bit Model Identifier, including the Company ID (or 0x0000) + // for Bluetooth SIG assigned models. var modelId: UInt32 { let companyId = isBluetoothSIGAssigned ? 0 : companyIdentifier! return (UInt32(companyId) << 16) | UInt32(modelIdentifier) diff --git a/Example/Source/View Controllers/Groups/Model/LightLCLightOnOffGroupCell.swift b/Example/Source/View Controllers/Groups/Model/LightLCLightOnOffGroupCell.swift new file mode 100644 index 000000000..797e9aadd --- /dev/null +++ b/Example/Source/View Controllers/Groups/Model/LightLCLightOnOffGroupCell.swift @@ -0,0 +1,80 @@ +/* +* Copyright (c) 2019, Nordic Semiconductor +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this +* list of conditions and the following disclaimer in the documentation and/or +* other materials provided with the distribution. +* +* 3. Neither the name of the copyright holder nor the names of its contributors may +* be used to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import NordicMesh + +class LightLCLightOnOffGroupCell: ModelGroupCell { + + // MARK: - Outlets and Actions + + @IBOutlet weak var icon: UIImageView! + @IBOutlet weak var title: UILabel! + + @IBOutlet weak var onButton: UIButton! + @IBAction func onTapped(_ sender: UIButton) { + sendLightLCLightOnOffMessage(turnOn: true) + } + @IBOutlet weak var offButton: UIButton! + @IBAction func offTapped(_ sender: UIButton) { + sendLightLCLightOnOffMessage(turnOn: false) + } + + // MARK: - Implementation + + override func reload() { + // On iOS 12.x tinted icons are initially black. + // Forcing adjustment mode fixes the bug. + icon.tintAdjustmentMode = .normal + + let numberOfDevices = models.count + if numberOfDevices == 1 { + title.text = "1 device" + } else { + title.text = "\(numberOfDevices) devices" + } + + let localProvisioner = MeshNetworkManager.instance.meshNetwork?.localProvisioner + let isEnabled = localProvisioner?.hasConfigurationCapabilities ?? false + + onButton.isEnabled = isEnabled + offButton.isEnabled = isEnabled + } +} + +private extension LightLCLightOnOffGroupCell { + + func sendLightLCLightOnOffMessage(turnOn: Bool) { + let label = turnOn ? "Enabling occupancy sensor binging..." : "Disabling occupancy sensor binging..." + delegate?.send(LightLCLightOnOffSetUnacknowledged(turnOn), + description: label, using: applicationKey) + } + +} diff --git a/Example/Source/View Controllers/Groups/Model/LightLCModeGroupCell.swift b/Example/Source/View Controllers/Groups/Model/LightLCModeGroupCell.swift new file mode 100644 index 000000000..afba77974 --- /dev/null +++ b/Example/Source/View Controllers/Groups/Model/LightLCModeGroupCell.swift @@ -0,0 +1,80 @@ +/* +* Copyright (c) 2019, Nordic Semiconductor +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this +* list of conditions and the following disclaimer in the documentation and/or +* other materials provided with the distribution. +* +* 3. Neither the name of the copyright holder nor the names of its contributors may +* be used to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import NordicMesh + +class LightLCModeGroupCell: ModelGroupCell { + + // MARK: - Outlets and Actions + + @IBOutlet weak var icon: UIImageView! + @IBOutlet weak var title: UILabel! + + @IBOutlet weak var onButton: UIButton! + @IBAction func onTapped(_ sender: UIButton) { + sendLightLCModeMessage(turnOn: true) + } + @IBOutlet weak var offButton: UIButton! + @IBAction func offTapped(_ sender: UIButton) { + sendLightLCModeMessage(turnOn: false) + } + + // MARK: - Implementation + + override func reload() { + // On iOS 12.x tinted icons are initially black. + // Forcing adjustment mode fixes the bug. + icon.tintAdjustmentMode = .normal + + let numberOfDevices = models.count + if numberOfDevices == 1 { + title.text = "1 device" + } else { + title.text = "\(numberOfDevices) devices" + } + + let localProvisioner = MeshNetworkManager.instance.meshNetwork?.localProvisioner + let isEnabled = localProvisioner?.hasConfigurationCapabilities ?? false + + onButton.isEnabled = isEnabled + offButton.isEnabled = isEnabled + } +} + +private extension LightLCModeGroupCell { + + func sendLightLCModeMessage(turnOn: Bool) { + let label = turnOn ? "Enabling binding..." : "Disabling binding..." + delegate?.send(LightLCModeSetUnacknowledged(turnOn), + description: label, using: applicationKey) + } + +} diff --git a/Example/Source/View Controllers/Groups/Model/LightLCOccupancyModeGroupCell.swift b/Example/Source/View Controllers/Groups/Model/LightLCOccupancyModeGroupCell.swift new file mode 100644 index 000000000..775270b65 --- /dev/null +++ b/Example/Source/View Controllers/Groups/Model/LightLCOccupancyModeGroupCell.swift @@ -0,0 +1,80 @@ +/* +* Copyright (c) 2019, Nordic Semiconductor +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this +* list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this +* list of conditions and the following disclaimer in the documentation and/or +* other materials provided with the distribution. +* +* 3. Neither the name of the copyright holder nor the names of its contributors may +* be used to endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +import UIKit +import NordicMesh + +class LightLCOccupancyModeGroupCell: ModelGroupCell { + + // MARK: - Outlets and Actions + + @IBOutlet weak var icon: UIImageView! + @IBOutlet weak var title: UILabel! + + @IBOutlet weak var onButton: UIButton! + @IBAction func onTapped(_ sender: UIButton) { + sendLightLCOccupancyModeMessage(turnOn: true) + } + @IBOutlet weak var offButton: UIButton! + @IBAction func offTapped(_ sender: UIButton) { + sendLightLCOccupancyModeMessage(turnOn: false) + } + + // MARK: - Implementation + + override func reload() { + // On iOS 12.x tinted icons are initially black. + // Forcing adjustment mode fixes the bug. + icon.tintAdjustmentMode = .normal + + let numberOfDevices = models.count + if numberOfDevices == 1 { + title.text = "1 device" + } else { + title.text = "\(numberOfDevices) devices" + } + + let localProvisioner = MeshNetworkManager.instance.meshNetwork?.localProvisioner + let isEnabled = localProvisioner?.hasConfigurationCapabilities ?? false + + onButton.isEnabled = isEnabled + offButton.isEnabled = isEnabled + } +} + +private extension LightLCOccupancyModeGroupCell { + + func sendLightLCOccupancyModeMessage(turnOn: Bool) { + let label = turnOn ? "Enabling occupancy sensor binging..." : "Disabling occupancy sensor binging..." + delegate?.send(LightLCOccupancyModeSetUnacknowledged(turnOn), + description: label, using: applicationKey) + } + +} diff --git a/Example/nRF Mesh.xcodeproj/project.pbxproj b/Example/nRF Mesh.xcodeproj/project.pbxproj index 17a3edb65..a93623d4f 100644 --- a/Example/nRF Mesh.xcodeproj/project.pbxproj +++ b/Example/nRF Mesh.xcodeproj/project.pbxproj @@ -171,6 +171,9 @@ A7B526A7277B210000F5C60D /* GenericPowerOnOffSetupViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B526A5277B210000F5C60D /* GenericPowerOnOffSetupViewCell.swift */; }; A7B526A8277B210000F5C60D /* GenericPowerOnOffSetup.xib in Resources */ = {isa = PBXBuildFile; fileRef = A7B526A6277B210000F5C60D /* GenericPowerOnOffSetup.xib */; }; C5D0E3889048D952B61974DA /* Pods_nRF_Mesh.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41E003FABAE6DD367FEC93B5 /* Pods_nRF_Mesh.framework */; }; + F03414832B912F95009DD792 /* LightLCModeGroupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03414822B912F95009DD792 /* LightLCModeGroupCell.swift */; }; + F03414852B9131BA009DD792 /* LightLCOccupancyModeGroupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03414842B9131BA009DD792 /* LightLCOccupancyModeGroupCell.swift */; }; + F03414872B91373E009DD792 /* LightLCLightOnOffGroupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03414862B91373E009DD792 /* LightLCLightOnOffGroupCell.swift */; }; F063C01A29C2774300A445A9 /* SelectKeysViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F063C01929C2774300A445A9 /* SelectKeysViewController.swift */; }; F063C01D29C27EAB00A445A9 /* IntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F063C01C29C27EAB00A445A9 /* IntroViewController.swift */; }; F085C9FB29C496AB00C2DF89 /* SelectModelsForBindingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F085C9FA29C496AB00C2DF89 /* SelectModelsForBindingViewController.swift */; }; @@ -377,6 +380,9 @@ A7B526A6277B210000F5C60D /* GenericPowerOnOffSetup.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GenericPowerOnOffSetup.xib; sourceTree = ""; }; C775BBAAAC42952ED786BABF /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; E7CB7DCE18F6E38D2240310F /* nRFMeshProvision.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = nRFMeshProvision.podspec; path = ../nRFMeshProvision.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + F03414822B912F95009DD792 /* LightLCModeGroupCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightLCModeGroupCell.swift; sourceTree = ""; }; + F03414842B9131BA009DD792 /* LightLCOccupancyModeGroupCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightLCOccupancyModeGroupCell.swift; sourceTree = ""; }; + F03414862B91373E009DD792 /* LightLCLightOnOffGroupCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightLCLightOnOffGroupCell.swift; sourceTree = ""; }; F063C01929C2774300A445A9 /* SelectKeysViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectKeysViewController.swift; sourceTree = ""; }; F063C01C29C27EAB00A445A9 /* IntroViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroViewController.swift; sourceTree = ""; }; F085C9FA29C496AB00C2DF89 /* SelectModelsForBindingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectModelsForBindingViewController.swift; sourceTree = ""; }; @@ -437,6 +443,9 @@ 521215302316C36B0059FB3D /* GenericLevelGroupCell.swift */, 5292F8A6250652BB00EA0F2A /* SceneServerGroupCell.swift */, 5292F8A8250654C600EA0F2A /* SceneSetupServerGroupCell.swift */, + F03414822B912F95009DD792 /* LightLCModeGroupCell.swift */, + F03414842B9131BA009DD792 /* LightLCOccupancyModeGroupCell.swift */, + F03414862B91373E009DD792 /* LightLCLightOnOffGroupCell.swift */, ); path = Model; sourceTree = ""; @@ -1285,11 +1294,13 @@ A7B526A7277B210000F5C60D /* GenericPowerOnOffSetupViewCell.swift in Sources */, F063C01A29C2774300A445A9 /* SelectKeysViewController.swift in Sources */, 5212152D231545D40059FB3D /* ModelGroupCell.swift in Sources */, + F03414852B9131BA009DD792 /* LightLCOccupancyModeGroupCell.swift in Sources */, F09DE4CA29DC68900073969A /* MulticolorProgressView.swift in Sources */, 52A8EA8D24E6A470004C14C7 /* SetHeartbeatPublicationViewController.swift in Sources */, A7A59BD7277225E70045A8ED /* GenericPowerOnOffViewCell.swift in Sources */, F0FB18F72B8E90CF00683C3F /* LightLCViewCell.swift in Sources */, F085CA0829D70ACB00C2DF89 /* ConfigurationViewController.swift in Sources */, + F03414872B91373E009DD792 /* LightLCLightOnOffGroupCell.swift in Sources */, 52547F26232A3F470002AA7B /* ProvisionersViewController.swift in Sources */, 529EC8DA2A309C640056BB48 /* CompanyIdentifier.swift in Sources */, 5267FDCD2A02937E00ECD38B /* CustomConfigCell.swift in Sources */, @@ -1303,6 +1314,7 @@ 5288C20A2371A89900321ED3 /* SimpleOnOffClientDelegate.swift in Sources */, 529B759C2243BA10008F1CE7 /* NetworkKeysViewController.swift in Sources */, 521599D525E91F9300F602FA /* SensorValueCell.swift in Sources */, + F03414832B912F95009DD792 /* LightLCModeGroupCell.swift in Sources */, 52518B3122E0B18400A1FD1E /* GroupsViewController.swift in Sources */, 52A8EA9424EE6D18004C14C7 /* HeartbeatSubscriptionPeriodCell.swift in Sources */, 5283528222806D7C0097B949 /* NetworkKeySelectionViewController.swift in Sources */,