├── Tests └── LinuxMain.swift ├── Examples ├── RxTxExample │ ├── Tests │ │ ├── LinuxMain.swift │ │ └── RxTxExampleTests │ │ │ ├── XCTestManifests.swift │ │ │ └── RxTxExampleTests.swift │ ├── README.md │ ├── Package.swift │ └── Sources │ │ └── RxTxExample │ │ └── main.swift └── ATCommandExample │ ├── Tests │ ├── LinuxMain.swift │ └── ATCommandExampleTests │ │ ├── XCTestManifests.swift │ │ └── ATCommandExampleTests.swift │ ├── README.md │ ├── Sources │ └── ATCommandExample │ │ └── main.swift │ └── Package.swift ├── Sources └── SwiftyXBee │ ├── Helpers │ ├── Extensions │ │ ├── Bool+Extensions.swift │ │ ├── String+Extensions.swift │ │ ├── Int+Extensions.swift │ │ └── Array+Extensions.swift │ ├── XBeeSerial.swift │ └── Constants.swift │ ├── Models │ ├── APIFrame │ │ ├── APIFrameProtocols.swift │ │ ├── StartDelimiter.swift │ │ ├── NetworkAddress.swift │ │ ├── DeviceAddress.swift │ │ ├── Checksum.swift │ │ ├── FrameLength.swift │ │ ├── APIFrame.swift │ │ ├── ATCommandResponse.swift │ │ └── ATCommand.swift │ ├── UART │ │ └── SerialConnection.swift │ └── APIFrameData │ │ ├── ATCommandData.swift │ │ ├── ATCommandResponseData.swift │ │ ├── ZigBeeTransmitRequestData.swift │ │ ├── ZigBeeReceivePacketData.swift │ │ └── ZigBeeTransmitStatusData.swift │ └── SwiftyXBee.swift ├── Package.swift ├── LICENSE ├── .gitignore └── README.md /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftyXBeeTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SwiftyXBeeTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Examples/RxTxExample/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import RxTxExampleTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += RxTxExampleTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Examples/ATCommandExample/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import ATCommandExampleTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += ATCommandExampleTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Examples/RxTxExample/Tests/RxTxExampleTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(RxTxExampleTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Examples/ATCommandExample/Tests/ATCommandExampleTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(ATCommandExampleTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Helpers/Extensions/Bool+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bool+Extensions.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 9/11/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Bool { 11 | var uint8: UInt8 { 12 | return self ? 0x01 : 0x00 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Helpers/Extensions/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Extensions.swift 3 | // SwiftyGPIO 4 | // 5 | // Created by Samuel Cornejo on 9/11/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension String { 11 | var byteArray: [UInt8] { 12 | return [UInt8](self.utf8) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Examples/RxTxExample/README.md: -------------------------------------------------------------------------------- 1 | # RxTxExample 2 | 3 | A simple example of how to use XBee Rx and Tx API Frames. XBee (ZigBee Series 2) radios must be configured in API mode and **AP mode set to 2 (escape bytes)**. 4 | 5 | ⚠️ If you want to run the example code, make sure to use an actual XBee serial number when defining: 6 | ```swift 7 | let deviceAddress = DeviceAddress(address: 0x0013A20012345678) 8 | ``` 9 | You can find the XBee's serial number on the back of the device, or by copying it directly from the [XCTU tool](https://www.digi.com/resources/documentation/digidocs/90001526/tasks/t_download_and_install_xctu.htm). 10 | -------------------------------------------------------------------------------- /Examples/ATCommandExample/README.md: -------------------------------------------------------------------------------- 1 | # ATCommandExample 2 | 3 | A simple example of how to use AT Command and AT Command Response API Frames. XBee (ZigBee Series 2) radios must be configured in API mode and **AP mode set to 2 (escape bytes)**. 4 | 5 | :warning: For a complete list of available AT Commands, please refer to the [XBee's datasheet](https://www.digi.com/resources/documentation/digidocs/pdfs/90002002.pdf). 6 | 7 | All the AT Commands that can be configured/queried with **SwiftyXBee** can be configured/queried using the [XCTU tool](https://www.digi.com/resources/documentation/digidocs/90001526/tasks/t_download_and_install_xctu.htm) as well. 8 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrame/APIFrameProtocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIFrameProtocols.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/16/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol BaseFrameData { 11 | /// Indicates the Frame Type of the API Frame. 12 | var frameType: FrameType { get set } 13 | } 14 | 15 | public protocol SerialWritable { 16 | /// The data to be written on the Serial port 17 | var serialData: [CChar] { get } 18 | } 19 | 20 | public protocol EscapedSerialWritable: SerialWritable { 21 | /// The escaped data to be written on the Serial port 22 | var escapedSerialData: [CChar] { get } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrame/StartDelimiter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StartDelimiter.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/16/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct StartDelimiter: SerialWritable { 11 | // MARK: Variables Declaration 12 | 13 | /// The raw value 14 | public var rawValue: UInt8 15 | 16 | /// The data to be written to the Serial port 17 | public var serialData: [CChar] { 18 | return [CChar(bitPattern: rawValue)] 19 | } 20 | 21 | // MARK: Initializer 22 | public init(for rawValue: UInt8 = Constant.startDelimiter) { 23 | self.rawValue = rawValue 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/UART/SerialConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerialConnection.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/12/19. 6 | // 7 | 8 | import SwiftyGPIO 9 | 10 | public struct SerialConnection { 11 | // MARK: Variables Declaration 12 | public let speed: UARTSpeed 13 | public let bitsPerChar: CharSize 14 | public let stopBits: StopBits 15 | public let parity: ParityType 16 | 17 | // MARK: Initializer 18 | public init(speed: UARTSpeed = .S9600, bitsPerChar: CharSize = .Eight, stopBits: StopBits = .One, parity: ParityType = .None) { 19 | self.speed = speed 20 | self.bitsPerChar = bitsPerChar 21 | self.stopBits = stopBits 22 | self.parity = parity 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrame/NetworkAddress.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkAddress.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/22/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum SpecialNetworkAddress: UInt16 { 11 | case broadcastMessage = 0xFFFE 12 | } 13 | 14 | public struct NetworkAddress { 15 | // MARK: Variables Declaration 16 | private(set) var value: [UInt8] 17 | 18 | // MARK: Initializers 19 | public init(address: UInt16) { 20 | self.value = address.byteArray 21 | } 22 | 23 | public init(address: [UInt8]) { 24 | self.value = address 25 | } 26 | 27 | public init(specialAddress: SpecialNetworkAddress) { 28 | self.init(address: specialAddress.rawValue) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrame/DeviceAddress.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceAddress.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/21/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum SpecialDeviceAddress: UInt64 { 11 | case coordinator = 0x0000000000000000 12 | case broadcastMessage = 0x000000000000FFFF 13 | } 14 | 15 | public struct DeviceAddress { 16 | // MARK: Variables Declaration 17 | private(set) var value: [UInt8] 18 | 19 | // MARK: Initializers 20 | public init(address: UInt64) { 21 | self.value = address.byteArray 22 | } 23 | 24 | public init(address: [UInt8]) { 25 | self.value = address 26 | } 27 | 28 | public init(specialAddress: SpecialDeviceAddress) { 29 | self.init(address: specialAddress.rawValue) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Examples/RxTxExample/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "RxTxExample", 8 | dependencies: [ 9 | // Dependencies declare other packages that this package depends on. 10 | .package(url: "https://github.com/samco182/SwiftyXBee", from: "1.1.0"), 11 | ], 12 | targets: [ 13 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 14 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 15 | .target( 16 | name: "RxTxExample", 17 | dependencies: ["SwiftyXBee"]), 18 | .testTarget( 19 | name: "RxTxExampleTests", 20 | dependencies: ["RxTxExample"]), 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /Examples/ATCommandExample/Sources/ATCommandExample/main.swift: -------------------------------------------------------------------------------- 1 | import SwiftyXBee 2 | 3 | // Initialize xbee instance 4 | let serialConnection = SerialConnection(speed: .S9600, bitsPerChar: .Eight, stopBits: .One, parity: .None) 5 | let xbee = SwiftyXBee(for: .RaspberryPi3, serialConnection: serialConnection) 6 | 7 | // Writing AT Command 8 | print("Writing AT Command: Node Identifier(NI)...") 9 | 10 | let nodeIdentifier = "XBee Test" 11 | xbee.sendATCommand(.addressing(.ni(.write(nodeIdentifier))), frameId: .sendNoACK) 12 | 13 | // Reading AT Command 14 | xbee.sendATCommand(.addressing(.ni(.read))) 15 | 16 | print("Reading AT Command: Node Identifier(NI)...") 17 | 18 | do { 19 | let atCommandResponse = try xbee.readATCommandResponse() 20 | print("AT Command Response received: \(atCommandResponse.frameData.commandData) = \(atCommandResponse.frameData.commandData.string)") 21 | } catch let error { 22 | print("Error receiving packet: \(error)") 23 | } 24 | -------------------------------------------------------------------------------- /Examples/ATCommandExample/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ATCommandExample", 8 | dependencies: [ 9 | // Dependencies declare other packages that this package depends on. 10 | .package(url: "https://github.com/samco182/SwiftyXBee", from: "1.1.0"), 11 | ], 12 | targets: [ 13 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 14 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 15 | .target( 16 | name: "ATCommandExample", 17 | dependencies: ["SwiftyXBee"]), 18 | .testTarget( 19 | name: "ATCommandExampleTests", 20 | dependencies: ["ATCommandExample"]), 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftyXBee", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "SwiftyXBee", 12 | targets: ["SwiftyXBee"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | .package(url: "https://github.com/uraimo/SwiftyGPIO.git", from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "SwiftyXBee", 23 | dependencies: ["SwiftyGPIO"]), 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrame/Checksum.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Checksum.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/16/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Checksum: EscapedSerialWritable { 11 | // MARK: Variables Declaration 12 | 13 | /// The raw value 14 | public var rawValue: UInt8 15 | 16 | /// The data to be written to the Serial port 17 | public var serialData: [CChar] { 18 | return [CChar(bitPattern: rawValue)] 19 | } 20 | 21 | /// The escaped data to be written to the Serial port 22 | public var escapedSerialData: [CChar] { 23 | return [rawValue].escapeDataIfNeeded() 24 | } 25 | 26 | // MARK: Initializers 27 | public init(for rawValue: UInt8) { 28 | self.rawValue = rawValue 29 | } 30 | 31 | public init(for rawData: [CChar]) { 32 | let sum = rawData.map({ UInt8(bitPattern: $0) }).addAll() 33 | let lowestByte = sum & Constant.hexConversionByte 34 | self.rawValue = Constant.hexConversionByte.uint8 - lowestByte.uint8 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Samuel Cornejo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrame/FrameLength.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FrameLength.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/13/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct FrameLength: EscapedSerialWritable { 11 | // MARK: Variables Declaration 12 | 13 | /// The packet's length MSB 14 | public var msb: UInt8 15 | 16 | /// The packet's length LSB 17 | public var lsb: UInt8 18 | 19 | /// The data to be written to the Serial port 20 | public var serialData: [CChar] { 21 | return [msb, lsb].map({ CChar(bitPattern: $0) }) 22 | } 23 | 24 | /// The escaped data to be written to the Serial port 25 | public var escapedSerialData: [CChar] { 26 | return [msb, lsb].escapeDataIfNeeded() 27 | } 28 | 29 | private enum DataOffset { 30 | static let msb = 1 31 | static let lsb = 2 32 | } 33 | 34 | // MARK: Initializers 35 | public init(for rawData: [UInt8]) { 36 | self.msb = rawData[DataOffset.msb] 37 | self.lsb = rawData[DataOffset.lsb] 38 | } 39 | 40 | public init(for rawData: [CChar]) { 41 | self.msb = (rawData.count.uint8 >> 8) & Constant.hexConversionByte.uint8 42 | self.lsb = rawData.count.uint8 & Constant.hexConversionByte.uint8 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Examples/RxTxExample/Sources/RxTxExample/main.swift: -------------------------------------------------------------------------------- 1 | import SwiftyXBee 2 | 3 | // Initialize xbee instance 4 | let serialConnection = SerialConnection(speed: .S9600, bitsPerChar: .Eight, stopBits: .One, parity: .None) 5 | let xbee = SwiftyXBee(for: .RaspberryPi3, serialConnection: serialConnection) 6 | 7 | // Reading a Receive Packet API Frame 8 | print("Receiving packet...") 9 | 10 | do { 11 | let readingPacket = try xbee.readRFDataPacket(maxTimeout: 5) // Wait up to 5 seconds for available data 12 | print("Packet received: \(readingPacket.frameData.receivedData)") 13 | } catch let error { 14 | print("Error receiving packet: \(error)") 15 | } 16 | 17 | // Sending a Transmit Request API Frame 18 | print("Sending packet...") 19 | 20 | let deviceAddress = DeviceAddress(address: 0x0013A20012345678) // For the example to work, replace this for an actual XBee Serial number 21 | let networkAddress = NetworkAddress(address: 0xFFFE) // You can use this address if you don't know the destination's network address 22 | xbee.sendTransmitRequest(to: deviceAddress, network: networkAddress, message: "This is my message to send!") 23 | 24 | // Reading a Transmit Status API Frame 25 | do { 26 | let readingPacket = try xbee.readTransmitStatus() 27 | print("Packet sent. Status: \(readingPacket.frameData.deliveryStatus!)") 28 | } catch let error { 29 | print("Error reading status: \(error)") 30 | } 31 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Helpers/Extensions/Int+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int+Extensions.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/18/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension UInt64 { 11 | var uint8: UInt8 { 12 | return UInt8(self) 13 | } 14 | 15 | var uint32: UInt32 { 16 | return UInt32(self) 17 | } 18 | 19 | var uint16: UInt16 { 20 | return UInt16(self) 21 | } 22 | 23 | var byteArray: [UInt8] { 24 | return stride(from: 56, to: -8, by: -8).map({ ((self >> $0) & Constant.hexConversionByte).uint8 }) 25 | } 26 | } 27 | 28 | public extension UInt32 { 29 | var uint8: UInt8 { 30 | return UInt8(self) 31 | } 32 | 33 | var byteArray: [UInt8] { 34 | return stride(from: 24, to: -8, by: -8).map({ ((self >> $0) & Constant.hexConversionByte.uint32).uint8 }) 35 | } 36 | } 37 | 38 | public extension UInt16 { 39 | var uint8: UInt8 { 40 | return UInt8(self) 41 | } 42 | 43 | var byteArray: [UInt8] { 44 | return stride(from: 8, to: -8, by: -8).map({ ((self >> $0) & Constant.hexConversionByte.uint16).uint8 }) 45 | } 46 | } 47 | 48 | public extension Int { 49 | var uint8: UInt8 { 50 | return UInt8(self) 51 | } 52 | } 53 | 54 | public extension CChar { 55 | var uint8: UInt8 { 56 | return UInt8(self) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrameData/ATCommandData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATCommandData.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 9/11/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct ATCommandData: BaseFrameData, EscapedSerialWritable { 11 | // MARK: Variables Declaration 12 | 13 | /// The packet's frame type 14 | public var frameType: FrameType 15 | 16 | /// The packet's frame id 17 | public var frameId: FrameId 18 | 19 | /// The AT Command itself 20 | public var command: ATCommand 21 | 22 | /// The data to be written to the Serial port 23 | public var serialData: [CChar] { 24 | return getWritableSerialData().map({ CChar(bitPattern: $0) }) 25 | } 26 | 27 | /// The escaped data to be written to the Serial port 28 | public var escapedSerialData: [CChar] { 29 | return getWritableSerialData().escapeDataIfNeeded() 30 | } 31 | 32 | // MARK: Initializer 33 | public init(frameType: FrameType = .atCommand, 34 | frameId: FrameId = .sendACK, 35 | command: ATCommand) { 36 | self.frameType = frameType 37 | self.frameId = frameId 38 | self.command = command 39 | } 40 | 41 | // MARK: Private Methods 42 | private func getWritableSerialData() -> [UInt8] { 43 | return [frameType.rawValue, frameId.rawValue] + command.rawValue 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Examples/RxTxExample/Tests/RxTxExampleTests/RxTxExampleTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class RxTxExampleTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | 10 | // Some of the APIs that we use below are available in macOS 10.13 and above. 11 | guard #available(macOS 10.13, *) else { 12 | return 13 | } 14 | 15 | let fooBinary = productsDirectory.appendingPathComponent("RxTxExample") 16 | 17 | let process = Process() 18 | process.executableURL = fooBinary 19 | 20 | let pipe = Pipe() 21 | process.standardOutput = pipe 22 | 23 | try process.run() 24 | process.waitUntilExit() 25 | 26 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 27 | let output = String(data: data, encoding: .utf8) 28 | 29 | XCTAssertEqual(output, "Hello, world!\n") 30 | } 31 | 32 | /// Returns path to the built products directory. 33 | var productsDirectory: URL { 34 | #if os(macOS) 35 | for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { 36 | return bundle.bundleURL.deletingLastPathComponent() 37 | } 38 | fatalError("couldn't find the products directory") 39 | #else 40 | return Bundle.main.bundleURL 41 | #endif 42 | } 43 | 44 | static var allTests = [ 45 | ("testExample", testExample), 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /Examples/ATCommandExample/Tests/ATCommandExampleTests/ATCommandExampleTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class ATCommandExampleTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | 10 | // Some of the APIs that we use below are available in macOS 10.13 and above. 11 | guard #available(macOS 10.13, *) else { 12 | return 13 | } 14 | 15 | let fooBinary = productsDirectory.appendingPathComponent("ATCommandExample") 16 | 17 | let process = Process() 18 | process.executableURL = fooBinary 19 | 20 | let pipe = Pipe() 21 | process.standardOutput = pipe 22 | 23 | try process.run() 24 | process.waitUntilExit() 25 | 26 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 27 | let output = String(data: data, encoding: .utf8) 28 | 29 | XCTAssertEqual(output, "Hello, world!\n") 30 | } 31 | 32 | /// Returns path to the built products directory. 33 | var productsDirectory: URL { 34 | #if os(macOS) 35 | for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { 36 | return bundle.bundleURL.deletingLastPathComponent() 37 | } 38 | fatalError("couldn't find the products directory") 39 | #else 40 | return Bundle.main.bundleURL 41 | #endif 42 | } 43 | 44 | static var allTests = [ 45 | ("testExample", testExample), 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Helpers/Extensions/Array+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Extensions.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/14/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Array where Element == UInt8 { 11 | /// String representation of byte array 12 | var string: String { 13 | guard let string = String(bytes: self, encoding: .utf8) else { return "" } 14 | return string 15 | } 16 | 17 | /// Adds up all the elements in an array of UInt8. 18 | /// 19 | /// - Returns: The sum of all the elements 20 | /// - Note: This is a workaround method. For some reason, using Array's Reduce inside a while loop, 21 | /// in a armv7 board causes the follwing error: **illegal hardware instruction swift run**. 22 | func addAll() -> UInt64 { 23 | var total = UInt64(0) 24 | forEach({ total += UInt64($0)}) 25 | return total 26 | } 27 | 28 | /// Adds escape bytes if needed. 29 | /// 30 | /// - Returns: Data with possible escape bytes included 31 | /// - Note: According to XBee documentation, API Mode 2 needs to escape a specific set of bytes. 32 | func escapeDataIfNeeded() -> [CChar] { 33 | var data: [UInt8] = [] 34 | 35 | for byte in self { 36 | if EscapedBytes.allCases.contains(where: { $0.rawValue == byte }) { 37 | data.append(EscapedBytes.escape.rawValue) 38 | data.append(byte ^ Constant.escapeByteXOR) 39 | } else { 40 | data.append(byte) 41 | } 42 | } 43 | 44 | return data.map({ CChar(bitPattern: $0) }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrame/APIFrame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIFrame.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/16/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum APIFrameError: Error { 11 | case rawDataIsNotAPIFrame 12 | case startDelimiterNotAvailable 13 | case checksumNotAvailable 14 | } 15 | 16 | public struct APIFrame { 17 | // MARK: Variables Declaration 18 | 19 | /// The unique number indicating the beginning of an API Frame 20 | public var delimiter: StartDelimiter 21 | 22 | /// The overall length of the data frame 23 | public var length: FrameLength 24 | 25 | /// Data specific to each type of message the XBee receives 26 | public var frameData: T 27 | 28 | /// Byte to check and see if there was a transmission error 29 | public var checksum: Checksum 30 | 31 | // MARK: Initializers 32 | public init(rawData: [UInt8], frameData: T) throws { 33 | guard rawData.count > Constant.minimumRawDataLength else { throw APIFrameError.rawDataIsNotAPIFrame } 34 | guard let startDelimeter = rawData.first else { throw APIFrameError.startDelimiterNotAvailable } 35 | guard let checksum = rawData.last else { throw APIFrameError.checksumNotAvailable } 36 | 37 | self.delimiter = StartDelimiter(for: startDelimeter) 38 | self.length = FrameLength(for: rawData) 39 | self.frameData = frameData 40 | self.checksum = Checksum(for: checksum) 41 | } 42 | 43 | public init(delimiter: StartDelimiter = StartDelimiter(), length: FrameLength, frameData: T, checksum: Checksum) { 44 | self.delimiter = delimiter 45 | self.length = length 46 | self.frameData = frameData 47 | self.checksum = checksum 48 | } 49 | } 50 | 51 | extension APIFrame: SerialWritable where T: EscapedSerialWritable { 52 | public var serialData: [CChar] { 53 | return delimiter.serialData + length.escapedSerialData + frameData.escapedSerialData + checksum.escapedSerialData 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrameData/ATCommandResponseData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATCommandResponseData.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 9/12/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct ATCommandResponseData: BaseFrameData { 11 | // MARK: Variable Declaration 12 | 13 | /// The packet's frame type 14 | public var frameType: FrameType = .atCommandResponse 15 | 16 | /// The packet's frame id 17 | public var frameId: FrameId? 18 | 19 | /// The packet's command name 20 | public var command: ATCommand? 21 | 22 | /// The packet's command processing status 23 | public var commandStatus: ATCommandStatus? 24 | 25 | /// Register data in binary format. 26 | /// - Note: If the register was set, this field is not returned. 27 | public var commandData: [UInt8] = [] 28 | 29 | private enum DataOffset { 30 | static let frameType = 3 31 | static let frameId = 4 32 | static let atCommand = 5 33 | static let commandStatus = 7 34 | static let commandData = 8 35 | } 36 | 37 | // MARK: Initializer 38 | public init(rawData: [UInt8]) { 39 | self.frameType = getFrameType(from: rawData) 40 | self.frameId = getFrameId(from: rawData) 41 | self.command = getCommand(from: rawData) 42 | self.commandStatus = getCommandStatus(from: rawData) 43 | self.commandData = getCommandData(from: rawData) 44 | } 45 | 46 | // MARK: Private Methods 47 | private func getFrameType(from data: [UInt8]) -> FrameType { 48 | return FrameType(rawValue: data[DataOffset.frameType]) ?? .atCommandResponse 49 | } 50 | 51 | private func getFrameId(from data: [UInt8]) -> FrameId? { 52 | return FrameId(rawValue: data[DataOffset.frameId]) 53 | } 54 | 55 | private func getCommand(from data: [UInt8]) -> ATCommand? { 56 | let command = data.enumerated().filter({ $0.offset >= DataOffset.atCommand && $0.offset < DataOffset.commandStatus }).map({ $0.element }) 57 | return ATCommand(with: command) 58 | } 59 | 60 | private func getCommandStatus(from data: [UInt8]) -> ATCommandStatus? { 61 | return ATCommandStatus(rawValue: data[DataOffset.commandStatus]) 62 | } 63 | 64 | private func getCommandData(from data: [UInt8]) -> [UInt8] { 65 | let commandData = data.enumerated().filter({ $0.offset >= DataOffset.commandData && $0.offset < data.endIndex - 1}).map({ $0.element }) 66 | return commandData 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/osx,swift,carthage 2 | 3 | ### OSX ### 4 | *.DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | # Thumbnails 11 | ._* 12 | # Files that might appear in the root of a volume 13 | .DocumentRevisions-V100 14 | .fseventsd 15 | .Spotlight-V100 16 | .TemporaryItems 17 | .Trashes 18 | .VolumeIcon.icns 19 | .com.apple.timemachine.donotpresent 20 | # Directories potentially created on remote AFP share 21 | .AppleDB 22 | .AppleDesktop 23 | Network Trash Folder 24 | Temporary Items 25 | .apdisk 26 | 27 | 28 | ### Swift ### 29 | # Xcode 30 | # 31 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 32 | 33 | *.xcodeproj 34 | 35 | ## Build generated 36 | build/ 37 | DerivedData/ 38 | 39 | ## Various settings 40 | *.pbxuser 41 | !default.pbxuser 42 | *.mode1v3 43 | !default.mode1v3 44 | *.mode2v3 45 | !default.mode2v3 46 | *.perspectivev3 47 | !default.perspectivev3 48 | xcuserdata/ 49 | 50 | ## Other 51 | *.moved-aside 52 | *.xcuserstate 53 | 54 | ## Obj-C/Swift specific 55 | *.hmap 56 | *.ipa 57 | *.dSYM.zip 58 | *.dSYM 59 | 60 | ## Playgrounds 61 | timeline.xctimeline 62 | playground.xcworkspace 63 | 64 | # Swift Package Manager 65 | # 66 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 67 | # Packages/ 68 | .build/ 69 | Package.resolved 70 | 71 | # CocoaPods 72 | # 73 | # We recommend against adding the Pods directory to your .gitignore. However 74 | # you should judge for yourself, the pros and cons are mentioned at: 75 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 76 | # 77 | # Pods/ 78 | 79 | # Carthage 80 | # 81 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 82 | # Carthage/Checkouts 83 | 84 | Carthage/Build 85 | 86 | # fastlane 87 | # 88 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 89 | # screenshots whenever they are needed. 90 | # For more information about the recommended setup visit: 91 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 92 | 93 | fastlane/report.xml 94 | fastlane/Preview.html 95 | fastlane/screenshots 96 | fastlane/test_output 97 | 98 | 99 | ### Carthage ### 100 | # Carthage - A simple, decentralized dependency manager for Cocoa 101 | Carthage/Checkouts/ 102 | Carthage/Build/ 103 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrameData/ZigBeeTransmitRequestData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZigBeeTransmitRequestData.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/16/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct ZigBeeTransmitRequestData: BaseFrameData, EscapedSerialWritable { 11 | // MARK: Variables Declaration 12 | 13 | /// The packet's frame type 14 | public var frameType: FrameType 15 | 16 | /// The packet's frame id 17 | public var frameId: FrameId 18 | 19 | /// The 64-bit address of the destination device. Set to 0x0000000000000000 if destination is coordinator. 20 | public var destinationDeviceAddress: DeviceAddress 21 | 22 | /// The 16 bit network address of the destination device. Set to 0xFFFE if address is unknown or if sending broadcast. 23 | public var destinationNetworkAddress: NetworkAddress 24 | 25 | /// The maximum number of hops a broadcast transmission can take 26 | public var broadcastRadius: UInt8 27 | 28 | /// Transmission options 29 | public var transmissionOption: TransmissionOption 30 | 31 | /// The transmission data itself 32 | public var transmissionData: String 33 | 34 | /// The data to be written to the Serial port 35 | public var serialData: [CChar] { 36 | return getWritableSerialData().map({ CChar(bitPattern: $0) }) 37 | } 38 | 39 | /// The escaped data to be written to the Serial port 40 | public var escapedSerialData: [CChar] { 41 | return getWritableSerialData().escapeDataIfNeeded() 42 | } 43 | 44 | // MARK: Initializer 45 | public init(frameType: FrameType = .transmitRequest, 46 | frameId: FrameId = .sendACK, 47 | destinationDeviceAddress: DeviceAddress, 48 | destinationNetworkAddress: NetworkAddress, 49 | broadcastRadius: UInt8 = 0x00, 50 | transmissionOption: TransmissionOption = .unusedBits, 51 | transmissionData: String) { 52 | self.frameType = frameType 53 | self.frameId = frameId 54 | self.destinationDeviceAddress = destinationDeviceAddress 55 | self.destinationNetworkAddress = destinationNetworkAddress 56 | self.broadcastRadius = broadcastRadius 57 | self.transmissionOption = transmissionOption 58 | self.transmissionData = transmissionData 59 | } 60 | 61 | // MARK: Private Methods 62 | private func getWritableSerialData() -> [UInt8] { 63 | return [frameType.rawValue, frameId.rawValue] + destinationDeviceAddress.value + destinationNetworkAddress.value + [broadcastRadius, transmissionOption.rawValue] + transmissionData.utf8 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrameData/ZigBeeReceivePacketData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZigBeeReceivePacketData.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/13/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct ZigBeeReceivePacketData: BaseFrameData { 11 | // MARK: Variables Declaration 12 | 13 | /// The packet's frame type 14 | public var frameType: FrameType = .receivePacket 15 | 16 | /// The 64-bit address of the sender. Set to 0xFFFFFFFFFFFFFFFF if the sender’s 64-bit address is unknown 17 | public var sourceDeviceAddress: DeviceAddress? 18 | 19 | /// The 16 bit network address of the sender 20 | public var sourceNetworkAddress: NetworkAddress? 21 | 22 | /// Information about the transmission acknowledgement status 23 | public var receiveOptions: ReceiveOptions? 24 | 25 | /// The received data itself 26 | public var receivedData: String = "" 27 | 28 | private enum DataOffset { 29 | static let frameType = 3 30 | static let sourceAddress64Bit = 4 31 | static let sourceAddress16Bit = 12 32 | static let receiveOptions = 14 33 | static let receivedData = 15 34 | } 35 | 36 | // MARK: Initializer 37 | public init(rawData: [UInt8]) { 38 | self.frameType = getFrameType(from: rawData) 39 | self.sourceDeviceAddress = getSourceDeviceAddress(from: rawData) 40 | self.sourceNetworkAddress = getSourceNetworkAddress(from: rawData) 41 | self.receiveOptions = getReceiveOptions(from: rawData) 42 | self.receivedData = getReceivedData(from: rawData) 43 | } 44 | 45 | // MARK: Private Methods 46 | private func getFrameType(from data: [UInt8]) -> FrameType { 47 | return FrameType(rawValue: data[DataOffset.frameType]) ?? .receivePacket 48 | } 49 | 50 | private func getSourceDeviceAddress(from data: [UInt8]) -> DeviceAddress { 51 | let sourceAddress = data.enumerated().filter({ $0.offset >= DataOffset.sourceAddress64Bit && $0.offset < DataOffset.sourceAddress16Bit }).map({ $0.element }) 52 | return DeviceAddress(address: sourceAddress) 53 | } 54 | 55 | private func getSourceNetworkAddress(from data: [UInt8]) -> NetworkAddress { 56 | let sourceAddress = data.enumerated().filter({ $0.offset >= DataOffset.sourceAddress16Bit && $0.offset < DataOffset.receiveOptions }).map({ $0.element }) 57 | return NetworkAddress(address: sourceAddress) 58 | } 59 | 60 | private func getReceiveOptions(from data: [UInt8]) -> ReceiveOptions? { 61 | return ReceiveOptions(rawValue: data[DataOffset.receiveOptions]) 62 | } 63 | 64 | private func getReceivedData(from data: [UInt8]) -> String { 65 | let receivedData = data.enumerated().filter({ $0.offset >= DataOffset.receivedData && $0.offset < data.endIndex - 1}).map({ $0.element }) 66 | return String(bytes: receivedData, encoding: .utf8) ?? "" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Helpers/XBeeSerial.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XBeeSerial.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/13/19. 6 | // 7 | 8 | import Foundation 9 | import SwiftyGPIO 10 | 11 | enum XBeeSerialError: Error { 12 | case checksumFailure 13 | case noSerialDataAvailable 14 | } 15 | 16 | public struct XBeeSerial { 17 | // MARK: Variable Declaration 18 | private var data: [UInt8] 19 | 20 | // MARK: Initializer 21 | public init(data: [UInt8] = []) { 22 | self.data = data 23 | } 24 | 25 | // MARK: Public Methods 26 | 27 | /// Reads all the available bytes in the serial port. 28 | /// 29 | /// - Parameters: 30 | /// - serial: The serial port to extract data from 31 | /// - maxTimeout: The maximum time to wait for data availability 32 | /// - Returns: All the bytes extracted from the serial port 33 | /// - Throws: Serial port reading errors 34 | public mutating func readData(from serial: UARTInterface, maxTimeout: TimeInterval) throws -> [UInt8] { 35 | let start = Date() 36 | while try !serial.hasAvailableData() && abs(start.timeIntervalSinceNow) < maxTimeout { } 37 | guard try serial.hasAvailableData() else { throw XBeeSerialError.noSerialDataAvailable } 38 | 39 | data = [] 40 | while try dataIsIncomplete() { 41 | let readData = serial.readData().map({ UInt8(bitPattern: $0) }) 42 | data += readData 43 | removeEscapeByteIfNeeded() 44 | } 45 | 46 | return data 47 | } 48 | 49 | /// Writes data to the serial port. 50 | /// 51 | /// - Parameters: 52 | /// - data: The data to be written to the serial port 53 | /// - serial: The serial port to write data to 54 | public func writeData(_ data: [CChar], to serial: UARTInterface) { 55 | serial.writeData(data) 56 | } 57 | 58 | // MARK: Private Methods 59 | private mutating func removeEscapeByteIfNeeded() { 60 | guard let indexToRemove = data.firstIndex(where: { $0 == EscapedBytes.escape.rawValue }), let lastByte = data.last, indexToRemove != data.firstIndex(of: lastByte) else { return } 61 | data.remove(at: indexToRemove) 62 | data[indexToRemove] = data[indexToRemove] ^ Constant.escapeByteXOR 63 | } 64 | 65 | public func dataIsIncomplete() throws -> Bool { 66 | guard data.count > Constant.checksumExcludedBytesCount, data.count == data[LengthConstant.msbIndex] + data[LengthConstant.lsbIndex] + LengthConstant.totalExcludedBytes else { return true } 67 | guard isValidChecksum() else { throw XBeeSerialError.checksumFailure } 68 | return false 69 | } 70 | 71 | private func isValidChecksum() -> Bool { 72 | let allBytes = data.enumerated().filter({ $0.offset >= Constant.checksumExcludedBytesCount }).map({ $0.element }).addAll() 73 | return allBytes & Constant.hexConversionByte == Constant.hexConversionByte 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Helpers/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/13/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Constant { 11 | public static let defaultReadDataTimeout: Double = 1 12 | public static let minimumRawDataLength = 4 13 | public static let startDelimiter: UInt8 = 0x7E 14 | public static let escapeByteXOR: UInt8 = 0x20 15 | public static let hexConversionByte: UInt64 = 0xFF 16 | public static let checksumExcludedBytesCount = 3 17 | } 18 | 19 | public enum LengthConstant { 20 | public static let msbIndex = 1 21 | public static let lsbIndex = 2 22 | public static let totalExcludedBytes: UInt8 = 4 23 | } 24 | 25 | public enum EscapedBytes: UInt8, CaseIterable { 26 | case frameDelimiter = 0x7E 27 | case escape = 0x7D 28 | case xon = 0x11 29 | case xoff = 0x13 30 | } 31 | 32 | public enum FrameType: UInt8 { 33 | case transmitRequest = 0x10 34 | case transmitStatus = 0x8B 35 | case receivePacket = 0x90 36 | case atCommand = 0x08 37 | case atCommandResponse = 0x88 38 | } 39 | 40 | // ZigBee Receive Packet Frame 41 | public enum ReceiveOptions: UInt8 { 42 | case acknowledgedPacket = 0x01 43 | case broadcastPacket = 0x02 44 | case encryptedPacket = 0x20 45 | case endDevicePacket = 0x40 46 | } 47 | 48 | // ZigBee Transmit Request Frame 49 | public enum FrameId: UInt8 { 50 | case sendNoACK = 0x00 51 | case sendACK = 0x01 52 | } 53 | 54 | public enum TransmissionOption: UInt8 { 55 | case unusedBits = 0x00 56 | case disableACK = 0x01 57 | case enableAPSEncryption = 0x20 58 | case extendedTransmissionTimeout = 0x40 59 | } 60 | 61 | // ZigBee Transmit Status Frame 62 | public enum DeliveryStatus: UInt8 { 63 | case success = 0x00 64 | case macACKFailure = 0x01 65 | case ccaFailure = 0x02 66 | case packetPurgedWithoutTransmission = 0x03 67 | case wifiTransceiverPhysicalError = 0x04 68 | case invalidDestinationEndpoint = 0x15 69 | case noBuffers = 0x18 70 | case networkACKFailure = 0x21 71 | case notJoinedToNetwork = 0x22 72 | case selfAddressed = 0x23 73 | case addressNotFound = 0x24 74 | case routeNotFound = 0x25 75 | case broadcastFailedToHear = 0x26 76 | case invalidBindingTableIndex = 0x2B 77 | case invalidEndpoint = 0x2C 78 | case attemptedBroadcastWithAPS = 0x2D 79 | case attemptedUnicastWithAPS = 0x2E 80 | case softwareError = 0x31 81 | case resourceError = 0x32 82 | case dataPayloadTooLarge = 0x74 83 | case indirectMessageUnrequested = 0x75 84 | case clientSocketCreationFailed = 0x76 85 | case keyNotAuthorized = 0xBB 86 | } 87 | 88 | public enum DiscoveryStatus: UInt8 { 89 | case noDiscoveryOverhead = 0x00 90 | case addressDiscovery = 0x01 91 | case routeDiscovery = 0x02 92 | case addressAndRoute = 0x03 93 | case extendedTimeoutDiscovery = 0x40 94 | } 95 | 96 | // AT Command Response Frame 97 | public enum ATCommandStatus: UInt8 { 98 | case ok = 0x00 99 | case error = 0x01 100 | case invalidCommand = 0x02 101 | case invalidParameter = 0x03 102 | case txFailure = 0x04 103 | } 104 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrameData/ZigBeeTransmitStatusData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZigBeeTransmitStatusData.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/21/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct ZigBeeTransmitStatusData: BaseFrameData { 11 | // MARK: Variables Declaration 12 | 13 | /// The packet's frame type 14 | public var frameType: FrameType = .transmitStatus 15 | 16 | /// The packet's frame id 17 | public var frameId: FrameId? 18 | 19 | /// If successful, this is the 16-bit network address the packet was delivered to. If not successful, this 20 | /// address matches the destination network address that was provided in the Transmit Request frame. 21 | public var destinationNetworkAddress: NetworkAddress? 22 | 23 | /// The number of application transmission retries that took place. 24 | public var transmitRetryCount: UInt8? 25 | 26 | /// The status of the delivery. If byte is 0x00, it was successfull, otherwise, the status indicates the kind of issue that prevented delivery. 27 | public var deliveryStatus: DeliveryStatus? 28 | 29 | /// Describes how much overhead it took to discover the route for the transmission. 30 | public var discoveryStatus: DiscoveryStatus? 31 | 32 | private enum DataOffset { 33 | static let frameType = 3 34 | static let frameId = 4 35 | static let destinationAddress16Bit = 5 36 | static let transmitRetryCount = 7 37 | static let deliveryStatus = 8 38 | static let discoveryStatus = 9 39 | } 40 | 41 | // MARK: Initializer 42 | public init(rawData: [UInt8]) { 43 | self.frameType = getFrameType(from: rawData) 44 | self.frameId = getFrameId(from: rawData) 45 | self.destinationNetworkAddress = getDestinationNetworkAddress(from: rawData) 46 | self.transmitRetryCount = getTransmitRetryCount(from: rawData) 47 | self.deliveryStatus = getDeliveryStatus(from: rawData) 48 | self.discoveryStatus = getDiscoveryStatus(from: rawData) 49 | } 50 | 51 | // MARK: Private Methods 52 | private func getFrameType(from data: [UInt8]) -> FrameType { 53 | return FrameType(rawValue: data[DataOffset.frameType]) ?? .transmitStatus 54 | } 55 | 56 | private func getFrameId(from data: [UInt8]) -> FrameId? { 57 | return FrameId(rawValue: data[DataOffset.frameId]) 58 | } 59 | 60 | private func getDestinationNetworkAddress(from data: [UInt8]) -> NetworkAddress { 61 | let destinationAddress = data.enumerated().filter({ $0.offset >= DataOffset.destinationAddress16Bit && $0.offset < DataOffset.transmitRetryCount }).map({ $0.element }) 62 | return NetworkAddress(address: destinationAddress) 63 | } 64 | 65 | private func getTransmitRetryCount(from data: [UInt8]) -> UInt8 { 66 | return data[DataOffset.transmitRetryCount] 67 | } 68 | 69 | private func getDeliveryStatus(from data: [UInt8]) -> DeliveryStatus? { 70 | return DeliveryStatus(rawValue: data[DataOffset.deliveryStatus]) 71 | } 72 | 73 | private func getDiscoveryStatus(from data: [UInt8]) -> DiscoveryStatus? { 74 | return DiscoveryStatus(rawValue: data[DataOffset.discoveryStatus]) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/SwiftyXBee.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyXBee.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 7/11/19. 6 | // 7 | 8 | #if os(Linux) 9 | import Glibc 10 | #else 11 | import Darwin.C 12 | #endif 13 | 14 | import Foundation 15 | import SwiftyGPIO 16 | 17 | public class SwiftyXBee { 18 | // MARK: Variables 19 | private let uart: UARTInterface 20 | private lazy var serial = XBeeSerial() 21 | 22 | // MARK: Initializers 23 | public init(for board: SupportedBoard, serialConnection: SerialConnection = SerialConnection()) { 24 | let uarts = SwiftyGPIO.UARTs(for: board)! 25 | self.uart = uarts[0] 26 | self.uart.configureInterface(speed: serialConnection.speed, bitsPerChar: serialConnection.bitsPerChar, stopBits: serialConnection.stopBits, parity: serialConnection.parity) 27 | } 28 | 29 | public convenience init() { 30 | self.init(for: .RaspberryPi3) 31 | } 32 | 33 | // MARK: Public Methods 34 | 35 | /// Sends a ZigBee Transmit Request to other remote radio. 36 | /// 37 | /// - Parameters: 38 | /// - deviceAddress: 64-bit address of the destination device 39 | /// - networkAddress: 16-bit network address of the destination device 40 | /// - frameId: The packet's frame id 41 | /// - broadcastRadius: The maximum number of hops a broadcast transmission can take 42 | /// - transmissionOption: Transmission option 43 | /// - message: The data to be sent to the destination device 44 | /// - Note: 45 | /// - __0x0000000000000000__ is the reserved 64-bit address for the coordinator. 46 | /// - __0xFFFE__ is the default address if network address is unknown, or if sending a broadcast. 47 | public func sendTransmitRequest(to deviceAddress: DeviceAddress, network networkAddress: NetworkAddress, frameId: FrameId = .sendACK, broadcastRadius: UInt8 = 0x00, transmissionOption: TransmissionOption = .unusedBits, message: String) { 48 | let frameData = ZigBeeTransmitRequestData(frameId: frameId, destinationDeviceAddress: deviceAddress, destinationNetworkAddress: networkAddress, broadcastRadius: broadcastRadius, transmissionOption: transmissionOption, transmissionData: message) 49 | let packetLength = FrameLength(for: frameData.serialData) 50 | let checksum = Checksum(for: frameData.serialData) 51 | let apiFrame = APIFrame(length: packetLength, frameData: frameData, checksum: checksum) 52 | writeSerialData(apiFrame.serialData) 53 | } 54 | 55 | /// Reads and process an RF data packet. 56 | /// 57 | /// - Parameter maxTimeout: The maximum time to wait before checking the serial port for data 58 | /// - Returns: A Receive Packet API Frame 59 | /// - Throws: Any error while reading the RF data packet 60 | public func readRFDataPacket(maxTimeout: TimeInterval = Constant.defaultReadDataTimeout) throws -> APIFrame { 61 | let rawData = try readSerialData(maxTimeout: maxTimeout) 62 | let frameData = ZigBeeReceivePacketData(rawData: rawData) 63 | return try APIFrame(rawData: rawData, frameData: frameData) 64 | } 65 | 66 | /// Reads the transmission status after issuing a Transmit Request API Frame. 67 | /// 68 | /// - Parameter maxTimeout: The maximum time to wait before checking the serial port for data 69 | /// - Returns: A Transmit Status API Frame 70 | /// - Throws: Any error while reading the transmit status packet 71 | /// - Note: If delivery status is 0x00, the transmission was successfully delivered to the destination address. 72 | /// Otherwise, the number received in this byte will indicate the kind of issue that prevented the delivery. 73 | public func readTransmitStatus(maxTimeout: TimeInterval = Constant.defaultReadDataTimeout) throws -> APIFrame { 74 | let rawData = try readSerialData(maxTimeout: maxTimeout) 75 | let frameData = ZigBeeTransmitStatusData(rawData: rawData) 76 | return try APIFrame(rawData: rawData, frameData: frameData) 77 | } 78 | 79 | /// Sends an AT Command to configure/query the local radio's parameters. 80 | /// 81 | /// - Parameters: 82 | /// - command: The AT command to be sent 83 | /// - frameID: The packet's frame id 84 | public func sendATCommand(_ command: ATCommand, frameId: FrameId = .sendACK) { 85 | let frameData = ATCommandData(frameId: frameId, command: command) 86 | let packetLength = FrameLength(for: frameData.serialData) 87 | let checksum = Checksum(for: frameData.serialData) 88 | let apiFrame = APIFrame(length: packetLength, frameData: frameData, checksum: checksum) 89 | writeSerialData(apiFrame.serialData) 90 | } 91 | 92 | /// Reads and process an AT Command response 93 | /// 94 | /// - Parameter maxTimeout: The maximum time to wait before checking the serial port for data 95 | /// - Returns: An AT Command Response Frame 96 | /// - Throws: Any error while reading the RF data packet 97 | public func readATCommandResponse(maxTimeout: TimeInterval = Constant.defaultReadDataTimeout) throws -> APIFrame { 98 | let rawData = try readSerialData(maxTimeout: maxTimeout) 99 | let frameData = ATCommandResponseData(rawData: rawData) 100 | return try APIFrame(rawData: rawData, frameData: frameData) 101 | } 102 | 103 | /// Reads the serial port. 104 | /// 105 | /// - Parameter maxTimeout: The maximum time to wait before checking the serial port for data 106 | /// - Returns: All the available data in the serial port 107 | /// - Throws: Any errors while reading the serial port 108 | public func readSerialData(maxTimeout: TimeInterval = Constant.defaultReadDataTimeout) throws -> [UInt8] { 109 | return try serial.readData(from: uart, maxTimeout: maxTimeout) 110 | } 111 | 112 | /// Writes data to the serial port. 113 | /// 114 | /// - Parameter data: The data to be written to the serial port 115 | public func writeSerialData(_ data: [CChar]) { 116 | serial.writeData(data, to: uart) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrame/ATCommandResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATCommandResponse.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 9/12/19. 6 | // 7 | 8 | import Foundation 9 | 10 | extension ATCommand { 11 | init?(with value: [UInt8]) { 12 | switch value.string { 13 | // Networking 14 | case "ID": 15 | self = .networking(.id(.read)) 16 | case "SC": 17 | self = .networking(.sc(.read)) 18 | case "SD": 19 | self = .networking(.sd(.read)) 20 | case "ZS": 21 | self = .networking(.zs(.read)) 22 | case "NJ": 23 | self = .networking(.nj(.read)) 24 | case "NW": 25 | self = .networking(.nw(.read)) 26 | case "JV": 27 | self = .networking(.jv(.read)) 28 | case "JN": 29 | self = .networking(.jn(.read)) 30 | case "OP": 31 | self = .networking(.op) 32 | case "OI": 33 | self = .networking(.oi) 34 | case "CH": 35 | self = .networking(.ch) 36 | case "NC": 37 | self = .networking(.nc) 38 | case "CE": 39 | self = .networking(.ce(.read)) 40 | case "DO": 41 | self = .networking(.do(.read)) 42 | case "DC": 43 | self = .networking(.dc(.read)) 44 | // Addressing 45 | case "SH": 46 | self = .addressing(.sh) 47 | case "SL": 48 | self = .addressing(.sl) 49 | case "MY": 50 | self = .addressing(.my) 51 | case "MP": 52 | self = .addressing(.mp) 53 | case "DH": 54 | self = .addressing(.dh(.read)) 55 | case "DL": 56 | self = .addressing(.dl(.read)) 57 | case "NI": 58 | self = .addressing(.ni(.read)) 59 | case "NH": 60 | self = .addressing(.nh(.read)) 61 | case "BH": 62 | self = .addressing(.bh(.read)) 63 | case "AR": 64 | self = .addressing(.ar(.read)) 65 | case "DD": 66 | self = .addressing(.dd(.read)) 67 | case "NT": 68 | self = .addressing(.nt(.read)) 69 | case "NO": 70 | self = .addressing(.no(.read)) 71 | case "NP": 72 | self = .addressing(.np) 73 | case "CR": 74 | self = .addressing(.cr(.read)) 75 | // ZigBee Addressing 76 | case "SE": 77 | self = .zigBeeAddressing(.se(.read)) 78 | case "DE": 79 | self = .zigBeeAddressing(.de(.read)) 80 | case "CI": 81 | self = .zigBeeAddressing(.ci(.read)) 82 | case "TO": 83 | self = .zigBeeAddressing(.to(.read)) 84 | // RF Interfacing 85 | case "PL": 86 | self = .rfInterfacing(.pl(.read)) 87 | case "PM": 88 | self = .rfInterfacing(.pm(.read)) 89 | case "PP": 90 | self = .rfInterfacing(.pp) 91 | // Security 92 | case "EE": 93 | self = .security(.ee(.read)) 94 | case "EO": 95 | self = .security(.eo(.read)) 96 | case "KY": 97 | self = .security(.ky(.read)) 98 | case "NK": 99 | self = .security(.nk(.read)) 100 | // Serial Interfacing 101 | case "BD": 102 | self = .serialInterfacing(.bd(.read)) 103 | case "NB": 104 | self = .serialInterfacing(.nb(.read)) 105 | case "SB": 106 | self = .serialInterfacing(.sb(.read)) 107 | case "RO": 108 | self = .serialInterfacing(.ro(.read)) 109 | case "D6": 110 | self = .serialInterfacing(.d6(.read)) 111 | case "D7": 112 | self = .serialInterfacing(.d7(.read)) 113 | case "AP": 114 | self = .serialInterfacing(.ap(.read)) 115 | case "AO": 116 | self = .serialInterfacing(.ao(.read)) 117 | // AT Command Options 118 | case "CT": 119 | self = .atCommandOptions(.ct(.read)) 120 | case "GT": 121 | self = .atCommandOptions(.gt(.read)) 122 | case "CC": 123 | self = .atCommandOptions(.cc(.read)) 124 | // Sleep Modes 125 | case "SP": 126 | self = .sleepModes(.sp(.read)) 127 | case "SN": 128 | self = .sleepModes(.sn(.read)) 129 | case "SM": 130 | self = .sleepModes(.sm(.read)) 131 | case "ST": 132 | self = .sleepModes(.st(.read)) 133 | case "SO": 134 | self = .sleepModes(.so(.read)) 135 | case "WH": 136 | self = .sleepModes(.wh(.read)) 137 | case "PO": 138 | self = .sleepModes(.po(.read)) 139 | // IO Settings 140 | case "D0": 141 | self = .ioSettings(.d0(.read)) 142 | case "D1": 143 | self = .ioSettings(.d1(.read)) 144 | case "D2": 145 | self = .ioSettings(.d2(.read)) 146 | case "D3": 147 | self = .ioSettings(.d3(.read)) 148 | case "D4": 149 | self = .ioSettings(.d4(.read)) 150 | case "D5": 151 | self = .ioSettings(.d5(.read)) 152 | case "D8": 153 | self = .ioSettings(.d8(.read)) 154 | case "D9": 155 | self = .ioSettings(.d9(.read)) 156 | case "P0": 157 | self = .ioSettings(.p0(.read)) 158 | case "P1": 159 | self = .ioSettings(.p1(.read)) 160 | case "P2": 161 | self = .ioSettings(.p2(.read)) 162 | case "P3": 163 | self = .ioSettings(.p3(.read)) 164 | case "P4": 165 | self = .ioSettings(.p4(.read)) 166 | case "PR": 167 | self = .ioSettings(.pr(.read)) 168 | case "PD": 169 | self = .ioSettings(.pd(.read)) 170 | case "LT": 171 | self = .ioSettings(.lt(.read)) 172 | case "RP": 173 | self = .ioSettings(.rp(.read)) 174 | // IO Sampling 175 | case "IR": 176 | self = .ioSampling(.ir(.read)) 177 | case "IC": 178 | self = .ioSampling(.ic(.read)) 179 | case "V+": 180 | self = .ioSampling(.vPlus(.read)) 181 | // Diagnostic Commands 182 | case "VR": 183 | self = .diagnosticCommands(.vr) 184 | case "HV": 185 | self = .diagnosticCommands(.hv) 186 | case "AI": 187 | self = .diagnosticCommands(.ai) 188 | case "DB": 189 | self = .diagnosticCommands(.db) 190 | case "%V": 191 | self = .diagnosticCommands(.percentageV) 192 | default: 193 | return nil 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftyXBee 2 | 3 | ⚡️A Swift library for communicating with XBee (ZigBee Series 2) radios in API mode. 4 |

5 | 6 | 7 | 8 | 9 |

10 | 11 | 12 | ## Summary 13 | This is a [SwiftyGPIO](https://github.com/uraimo/SwiftyGPIO) based library for communicating with XBee radios in **API mode**, with support for Series 2 **only**. 14 | 15 | This is a **work in progress**. I started coding the library for a project I am currently working on, so, as of now, it only supports ATCommand, ATCommandResponse, Tx, Rx, and Transmit Status API Frames. If you want to contribute to this noble cause, please submit a PR and I will be more than happy to review it and merge it :smile:. 16 | 17 | For more information regarding the RF module, you can consult its [datasheet](https://www.digi.com/resources/documentation/digidocs/pdfs/90002002.pdf). 18 | 19 | ## API Mode 20 | XBees support two operation modes: API and AT. API mode requires that the device communicate through a structured interface (that is, data is communicated in frames in a defined order). The API specifies how the device sends and receives commands, command responses, and module status messages using a serial port Data Frame. On the other hand, in AT (transparent) mode, the XBee radio simply relays serial data to the receiving XBee, as identified by the DH+DL address. 21 | 22 | According to the documentation: 23 | > The XBee firmware supports two API operating modes: without escaped characters and with escaped characters. Use the AP command to enable either mode. To configure a device to one of these modes, set the following AP parameter values: 24 | > - AP = 1: API operation. 25 | > - AP = 2: API operation (with escaped characters—only possible on UART). 26 | 27 | ⚠️ This library is designed to work only in API mode, it **requires** the **AP mode set to 2 (escape bytes)**, as this setting offers the best reliability. 28 | 29 | ## Hardware Details 30 | - The XBee should be powered using **3.3 V**. 31 | - This library requires the XBee's **UART** to be configured with compatible settings for the baud rate, parity, start bits, stop bits, and data bits. 32 | 33 | This is the pin assignment to manually connect (no XBee shield) the device to any of the compatible boards: 34 | 35 | | XBee Pin | Board UART | 36 | | ------------------------ | --------------- | 37 | | VCC = **1** | VCC 3.3V | 38 | | DOUT = **2** | RX / Receive | 39 | | DIN / CONFIG = **3** | TX / Transmit | 40 | | GND = **10** | GND | 41 | 42 | The UART pins on the RaspberryPi (pin 14 TXD, pin 15 RXD) need to be enabled via `raspi-config` before you can use them (restart required). By enabling the UART pins, you will lose the ability to access the login shell over serial. 43 | 44 | ## Supported Boards 45 | Every board supported by [SwiftyGPIO](https://github.com/uraimo/SwiftyGPIO): RaspberryPis, BeagleBones, C.H.I.P., etc... 46 | 47 | To use this library, you'll need a Linux ARM board running [Swift 5.x](https://github.com/uraimo/buildSwiftOnARM) 🚗. 48 | 49 | The examples below will use a Raspberry Pi 3B+ board, but you can easily modify the examples to use one of the other supported boards. Full working demo projects for the RaspberryPi3B+ are available under the **Examples** directory. 50 | 51 | ## Installation 52 | First of all, makes sure your board is running **Swift 5.x** ⚠️! 53 | 54 | Since Swift 5.x supports Swift Package Manager, you only need to add SwiftXBee as a dependency in your project's `Package.swift` file: 55 | 56 | ```swift 57 | let package = Package( 58 | name: "MyProject", 59 | dependencies: [ 60 | .package(url: "https://github.com/samco182/SwiftyXBee", from: "1.0.0"), 61 | ] 62 | targets: [ 63 | .target( 64 | name: "MyProject", 65 | dependencies: ["SwiftyXBee"]), 66 | ] 67 | ) 68 | ``` 69 | Then run `swift package update` to install the dependency. 70 | 71 | ## Usage 72 | ### Initialization 73 | The first thing is to initialize an instance of `SwiftyXBee` with the same UART configuration as the one you used to setup the XBee devices on the XCTU tool or through the AT command mode. Once you have your `xbee` object initialized, you can send and receive data packets between XBees: 74 | 75 | ```swift 76 | import SwiftyXBee 77 | 78 | let serial = SerialConnection(speed: .S9600, bitsPerChar: .Eight, stopBits: .One, parity: .None) 79 | let xbee = SwiftyXBee(for: .RaspberryPi3, serialConnection: serial) 80 | ``` 81 | You can also initialize the `XBee` object with the following method: 82 | ```swift 83 | import SwiftyXBee 84 | 85 | let xbee = SwiftyXBee() 86 | ``` 87 | This initializer defaults to `.RaspberryPi3` as the selected board and serial connection with `speed: .S9600`, `bitsPerChar: .Eight`, `stopBits: .One`, and `parity: .None)`. 88 | 89 | ### AT Command Packet 90 | AT-type commands can be sent via API frames to configure your local radio. They can query the settings on the local radio or set parameters. These are all the same commands you typed in transparent/command mode. 91 | 92 | For this example, the local radio's *Node Identifier* parameter is been set and read. 93 | 94 | Setting AT Command parameter: 95 | ``` swift 96 | // The new Node Identifier 97 | let nodeIdentifier = "XBee Test" 98 | 99 | xbee.sendATCommand(.addressing(.ni(.write(nodeIdentifier))), frameId: .sendNoACK) 100 | ``` 101 | Reading AT Command parameter: 102 | ```swift 103 | xbee.sendATCommand(.addressing(.ni(.read))) 104 | 105 | do { 106 | let atCommandResponse = try xbee.readATCommandResponse() 107 | print("AT Command Response received: \(atCommandResponse.frameData.commandData) = \(atCommandResponse.frameData.commandData.string)") 108 | } catch let error { 109 | print("Error receiving packet: \(error)") 110 | } 111 | ``` 112 | 113 | ### Transmit Packets 114 | There a several different types of transmit (TX) packets available. But as mentioned above, as of now, the library only allows **Transmit Request** packets to be sent. A list of all TX packets can be found in the API [documentation](https://www.digi.com/resources/documentation/digidocs/pdfs/90002002.pdf). All classes that end in "Request" are TX packets. 115 | ```swift 116 | // The destination XBee 64-bit serial number 117 | let deviceAddress = DeviceAddress(address: 0x0013A20012345678) 118 | 119 | // The destination XBee 16-bit network address 120 | let networkAddress = NetworkAddress(address: 0xFFFE) 121 | 122 | // The actual message to be sent 123 | let message = "This is my message to send!" 124 | 125 | xbee.sendTransmitRequest(to: deviceAddress, network: networkAddress, message: message) 126 | ``` 127 | When a Transmit Request completes, if configured on the request's Frame Id, the receiver device will send a **Transmit Status** packet out of the serial interface. This message indicates if the Transmit Request was successful or if it failed. If you want to make sure your packet was correctly delivered, call the following code: 128 | ```swift 129 | do { 130 | let transmitStatus = try xbee.readTransmitStatus() 131 | print("Status received: \(readingPacket.frameData.deliveryStatus)") 132 | } catch let error { 133 | print("Error: \(error)") 134 | } 135 | ``` 136 | 137 | ### Receive Packet 138 | As with transmit (TX) packets, there are also several different types of receive (RX) packets available. But as of now, the library only allows **Receive Packet** API frame to be read. 139 | ```swift 140 | do { 141 | let readingPacket = try xbee.readRFDataPacket() 142 | print("Packet received: \(readingPacket.frameData.receivedData)") 143 | } catch let error { 144 | print("Error: \(error)") 145 | } 146 | ``` 147 | 148 | ## Note 🔎 149 | If you want to better understand how ZigBee communication protocol works, or the details (in a more comprehensive way) of the most common API frames XBee counts with, you could try reading [this book](https://www.amazon.com/gp/product/0596807732?ie=UTF8&tag=xbapra-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0596807732Building). 150 | -------------------------------------------------------------------------------- /Sources/SwiftyXBee/Models/APIFrame/ATCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATCommand.swift 3 | // SwiftyXBee 4 | // 5 | // Created by Samuel Cornejo on 9/11/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Action { 11 | case read 12 | case write(T) 13 | } 14 | 15 | public enum ATCommand { 16 | /// The networking settings 17 | case networking(Networking) 18 | /// The addressing settings 19 | case addressing(Addressing) 20 | /// The ZigBee protocol addressing settings 21 | case zigBeeAddressing(ZigBeeAddressing) 22 | /// The RF interface options 23 | case rfInterfacing(RFInterfacing) 24 | /// The security parameters 25 | case security(Security) 26 | /// The modem interfacing options 27 | case serialInterfacing(SerialInterfacing) 28 | /// The AT command mode behavior 29 | case atCommandOptions(ATCommandOptions) 30 | /// The low power options to support end device children 31 | case sleepModes(SleepModes) 32 | /// The DIO and ADC options 33 | case ioSettings(IOSettings) 34 | /// The IO sampling parameters 35 | case ioSampling(IOSampling) 36 | /// The access to diagnostic paramters 37 | case diagnosticCommands(DiagnosticCommands) 38 | 39 | public var rawValue: [UInt8] { 40 | switch self { 41 | case .networking(let command): 42 | return command.rawValue 43 | case .addressing(let command): 44 | return command.rawValue 45 | case .zigBeeAddressing(let command): 46 | return command.rawValue 47 | case .rfInterfacing(let command): 48 | return command.rawValue 49 | case .security(let command): 50 | return command.rawValue 51 | case .serialInterfacing(let command): 52 | return command.rawValue 53 | case .atCommandOptions(let command): 54 | return command.rawValue 55 | case .sleepModes(let command): 56 | return command.rawValue 57 | case .ioSettings(let command): 58 | return command.rawValue 59 | case .ioSampling(let command): 60 | return command.rawValue 61 | case .diagnosticCommands(let command): 62 | return command.rawValue 63 | } 64 | } 65 | 66 | // Networking 67 | public enum Networking { 68 | /// PAN ID 69 | /// - Note: 70 | /// - Valid range is __0 - 0xFFFFFFFFFFFFFFFF__. 71 | /// - Default is __0__. 72 | /// - For a router or end device, ID determines the network to join, but 0 allows it to join a network with any extended PAN ID. 73 | /// For a coordinator, ID selects extended PAN ID, but a value of 0 causes coordinator to randomly select the extended PAN ID. 74 | case id(Action) 75 | /// Scan Channels 76 | /// - Note: 77 | /// - Valid range is __0x1 - 0xFFFF__. 78 | /// - Default is __0x7FFF__. 79 | /// - List of channels to scan as bitfield: Bit 15 = Chan 0x1A . . . Bit 0 = Chan 0x0B. These channels apply when joining for routers and end devices. 80 | /// The coordinator uses these channels for active and energy scans when forming a network on startup. 81 | case sc(Action) 82 | /// Scan Duration Exponent 83 | /// - Note: 84 | /// - Valid range is __0x0 - 0x07__. 85 | /// - Default is __0x3__. 86 | /// - The exponent configures the duration of the active scan (PAN scan) on each channel in the SC channel mask when attempting 87 | /// to join a PAN. Scan Time = (SC * (2 ^ SD) * 15.36ms) + (38ms * SC) + 20ms. (SC=# channels) 88 | case sd(Action) 89 | /// ZigBee Stack Profile Settings 90 | /// - Note: 91 | /// - Valid range is __0x0 - 0x2__. 92 | /// - ZS must be written to flash; changing ZS and applying the change will automatically execute a WR command 93 | /// and reset the device. 0=Network Specific, 1=ZigBee-2006, 2=ZigBee-PRO 94 | case zs(Action) 95 | /// Node Join Time 96 | /// - Note: 97 | /// - Valid range is __0x0 - 0xFF__. 98 | /// - Default is __0xFF__. 99 | /// - The value of NJ determines the time (in seconds) that the device will allow other devices to join to it. If set to 0xFF, the device will always allow joining. 100 | case nj(Action) 101 | /// Network Watchdog Timeout 102 | /// - Note: 103 | /// - Valid range is __0x0 - 0x64FF__. 104 | /// - Default is __0x0__. 105 | /// - If set to a non-zero value, the network watchdog timer is enabled on a router. The router will leave the network if is does not receive valid 106 | /// communication within (3 * NW) minutes. The timer is reset each time data is received from or sent to a coordinator, or if a many-to-one broadcast is received. 107 | case nw(Action) 108 | /// Channel Verification Setting 109 | /// - Note: 110 | /// - Default is __false__. 111 | /// - If enabled, a router will verify a coordinator exists on the same channel after joining or power cycling to ensure it is operating on a valid 112 | /// channel, and will leave if a coordinator cannot be found (if NJ=0xFF). If disabled, the router will remain on the same channel through power cycles. 113 | case jv(Action) 114 | /// Join Notification Setting 115 | /// - Note: 116 | /// - Default is __false__. 117 | /// - If enabled, the module will transmit a broadcast node identification frame on power up and when joining. This action blinks the Assoc LED rapidly 118 | /// on all devices that receive the data, and sends an API frame out the UART of API devices. This function should be disabled for large networks. 119 | case jn(Action) 120 | /// Operating PAN ID 121 | case op 122 | /// Operating 16-bit PAN ID 123 | case oi 124 | /// Operating Channel 125 | /// - Note: The operating channel number (Uses 802.15.4 channel numbers). 126 | case ch 127 | /// Number of Remaining Children 128 | /// - Note: Remaining end device children that can join this device. If NC=0, the device cannot allow any more end device children to join. 129 | case nc 130 | /// Coordinator Enable 131 | /// - Note: 132 | /// - Default is __false__. 133 | case ce(Action) 134 | /// Device Options 135 | /// - Note: 136 | /// - Valid range is __0x0 - 0xFF__. 137 | /// - Default is __0x0__. 138 | /// - Bit0 - Reserved. Bit1 - Reserved. Bit2 - Enable Best Response Joining. Bit3 - Disable NULL Transport Key. Bit4 - Disable Ext.Timeout. 139 | /// Bit5 - Enable NoAck IO Sampling. Bit6 - Enable High RAM Concentrator. Bit7 - Enable ATNW to find new network before leaving the network. 140 | case `do`(Action) 141 | /// Device Controls 142 | /// - Note: 143 | /// - Valid range is __0x0 - 0xFFFF__. 144 | /// - Default is __0x0__. 145 | /// - Bit0 - Enable Joiner Global Link Key. Bit1 - NWK Leave Request Not Allowed. Bit 4 - Enable Verbose Joining Mode. Bit 7 - Enable FR after 60s of no beacon responses during join. 146 | case dc(Action) 147 | 148 | public var rawValue: [UInt8] { 149 | switch self { 150 | case .id(let action): 151 | guard case .write(let parameter) = action else { return "ID".byteArray } 152 | return "ID".byteArray + parameter.byteArray 153 | case .sc(let action): 154 | guard case .write(var parameter) = action else { return "SC".byteArray } 155 | parameter = parameter < 0x1 ? 0x1 : parameter 156 | return "SC".byteArray + parameter.byteArray 157 | case .sd(let action): 158 | guard case .write(var parameter) = action else { return "SD".byteArray } 159 | parameter = parameter > 0x07 ? 0x07 : parameter 160 | return "SD".byteArray + [parameter] 161 | case .zs(let action): 162 | guard case .write(var parameter) = action else { return "ZS".byteArray } 163 | parameter = parameter > 0x2 ? 0x2 : parameter 164 | return "ZS".byteArray + [parameter] 165 | case .nj(let action): 166 | guard case .write(let parameter) = action else { return "NJ".byteArray } 167 | return "NJ".byteArray + [parameter] 168 | case .nw(let action): 169 | guard case .write(var parameter) = action else { return "NW".byteArray } 170 | parameter = parameter > 0x64FF ? 0x64FF : parameter 171 | return "NW".byteArray + parameter.byteArray 172 | case .jv(let action): 173 | guard case .write(let isEnabled) = action else { return "JV".byteArray } 174 | return "JV".byteArray + [isEnabled.uint8] 175 | case .jn(let action): 176 | guard case .write(let isEnabled) = action else { return "JN".byteArray } 177 | return "JN".byteArray + [isEnabled.uint8] 178 | case .op: 179 | return "OP".byteArray 180 | case .oi: 181 | return "OI".byteArray 182 | case .ch: 183 | return "CH".byteArray 184 | case .nc: 185 | return "NC".byteArray 186 | case .ce(let action): 187 | guard case .write(let isEnabled) = action else { return "CE".byteArray } 188 | return "CE".byteArray + [isEnabled.uint8] 189 | case .do(let action): 190 | guard case .write(let parameter) = action else { return "DO".byteArray } 191 | return "DO".byteArray + [parameter] 192 | case .dc(let action): 193 | guard case .write(let parameter) = action else { return "DC".byteArray } 194 | return "DC".byteArray + parameter.byteArray 195 | } 196 | } 197 | } 198 | 199 | public enum Addressing { 200 | /// Serial Number High 201 | /// - Note: High 32 bits of modems unique IEEE 64-bit Extended Address. 202 | case sh 203 | // Serial Number Low 204 | /// - Note: Low 32 bits of modems unique IEEE 64-bit Extended Address. 205 | case sl 206 | /// 16-bit Network Address 207 | /// - Note: 16 bit Network Address for the modem. 0xFFFE means the device has not joined a PAN. 208 | case my 209 | /// 16-bit Parent Address 210 | /// - Note: 16 bit Network Address of the modem's parent. Coordinators and Routers will always show 0xFFFE. An End Device 211 | /// will show its parent's address, or 0xFFFE if it has not yet joined a PAN. 212 | case mp 213 | /// Destination Address High 214 | /// - Note: 215 | /// - Valid range is __0x0 - 0xFFFFFFFF__. 216 | /// - Default is __0x0__. 217 | /// - Upper 32 bits of the 64 bit destination extended address. 0x000000000000FFFF is the broadcast address for the PAN. 0x0000000000000000 can be used to address the Pan Coordinator. 218 | case dh(Action) 219 | /// Destination Address Low 220 | /// - Note: 221 | /// - Valid range is __0x0 - 0xFFFFFFFF__. 222 | /// - Default is __0x0__. 223 | /// - Lower 32 bits of the 64 bit destination extended address. 0x000000000000FFFF is the broadcast address for the PAN. 0x0000000000000000 can be used to address the Pan Coordinator. 224 | case dl(Action) 225 | /// Node Identifier String 226 | /// - Note: 227 | /// - __0 - 20__ ASCII characters. 228 | /// - Default is __' '__. 229 | case ni(Action) 230 | /// Maximum Hops 231 | /// - Note: 232 | /// - Valid range is __0x0 - 0xFF__. 233 | /// - Default is __0x1E__. 234 | /// - This limit sets the maximum number of broadcast hops (BH) and determines the unicast timeout (unicast timeout = (50 * NH) + 100). A unicast timeout 235 | /// of 1.6 seconds (NH=30) is enough time for the data and acknowledgment to traverse about 8 hops. 236 | case nh(Action) 237 | /// Broadcast Radius 238 | /// - Note: 239 | /// - Valid range is __0x0 - 0x1E__. 240 | /// - Default is __0x0__. 241 | /// - Set to 0 for maximum radius. 242 | case bh(Action) 243 | /// Many-to-One Route Broadcast Time 244 | /// - Note: 245 | /// - Valid range is __0x0 - 0xFF__. 246 | /// - Default is __0xFF__. 247 | /// - Time between aggregation route broadcast times. An aggregation route broadcast creates a route on all devices in the network back to the device 248 | /// that sends the aggregation broadcast. Setting AR to 0xFF disables aggregation route broadcasting. Setting AR to 0 sends one broadcast. 249 | case ar(Action) 250 | /// Device Type Identifier 251 | /// - Note: 252 | /// - Valid range is __0x0 - 0xFFFFFFFF__. 253 | /// - Default is __0xA0000__. 254 | /// - This can be used to differentiate multiple XBee-based products. 255 | case dd(Action) 256 | /// Node Discovery Backoff 257 | /// - Note: 258 | /// - Valid range is __0x20 - 0xFF__. 259 | /// - Default is __0x3C__. 260 | /// - This sets the maximum delay for Node Discovery responses to be sent (ND, DN). 261 | case nt(Action) 262 | /// Node Discovery Options 263 | /// - Note: 264 | /// - Valid range is __0x0 - 0x3__. 265 | /// - Default is __0x0__. 266 | /// - Options include 0x01 - Append DD value to end of node discovery, 0x02 - Return devices own ND response first. 267 | case no(Action) 268 | /// Maximum Number of Transmission Bytes 269 | /// - Note: This number can change based on whether or not security is enabled (EE). Broadcast RF packets can support 8 more bytes than unicast packets. 270 | case np 271 | /// PAN Conflict Threshold 272 | /// - Note: 273 | /// - Valid range is __0x0 - 0x3F__. 274 | /// - Default is __0x3__. 275 | /// - Threshold for the number of PAN id conflict reports that must be received by the network manager within one minute to trigger a PAN id change. 276 | /// Starting with revision 4050, setting CR to 0 will instead set the threshold value to the default configuration value (3). 277 | case cr(Action) 278 | 279 | public var rawValue: [UInt8] { 280 | switch self { 281 | case .sh: 282 | return "SH".byteArray 283 | case .sl: 284 | return "SL".byteArray 285 | case .my: 286 | return "MY".byteArray 287 | case .mp: 288 | return "MP".byteArray 289 | case .dh(let action): 290 | guard case .write(let parameter) = action else { return "DH".byteArray } 291 | return "DH".byteArray + parameter.byteArray 292 | case .dl(let action): 293 | guard case .write(let parameter) = action else { return "DL".byteArray } 294 | return "DL".byteArray + parameter.byteArray 295 | case .ni(let action): 296 | guard case .write(var parameter) = action else { return "NI".byteArray } 297 | parameter = parameter.count > 20 ? String(parameter.prefix(20)) : parameter 298 | return "NI".byteArray + parameter.byteArray 299 | case .nh(let action): 300 | guard case .write(let parameter) = action else { return "NH".byteArray } 301 | return "NH".byteArray + [parameter] 302 | case .bh(let action): 303 | guard case .write(var parameter) = action else { return "BH".byteArray } 304 | parameter = parameter > 0x1E ? 0x1E : parameter 305 | return "BH".byteArray + [parameter] 306 | case .ar(let action): 307 | guard case .write(let parameter) = action else { return "AR".byteArray } 308 | return "AR".byteArray + [parameter] 309 | case .dd(let action): 310 | guard case .write(let parameter) = action else { return "DD".byteArray } 311 | return "DD".byteArray + parameter.byteArray 312 | case .nt(let action): 313 | guard case .write(var parameter) = action else { return "NT".byteArray } 314 | parameter = parameter < 0x20 ? 0x20 : parameter 315 | return "NT".byteArray + [parameter] 316 | case .no(let action): 317 | guard case .write(var parameter) = action else { return "NO".byteArray } 318 | parameter = parameter > 0x3 ? 0x3 : parameter 319 | return "NO".byteArray + [parameter] 320 | case .np: 321 | return "NP".byteArray 322 | case .cr(let action): 323 | guard case .write(var parameter) = action else { return "CR".byteArray } 324 | parameter = parameter > 0x3F ? 0x3F : parameter 325 | return "CR".byteArray + [parameter] 326 | } 327 | } 328 | } 329 | 330 | public enum ZigBeeAddressing { 331 | /// ZigBee Source Endpoint 332 | /// - Note: 333 | /// - Valid range is __0x00 - 0xFF__. 334 | /// - Default is __0xE8__. 335 | /// - This should only be changed if multiple endpoints must be supported. 336 | case se(Action) 337 | /// ZigBee Destination Endpoint 338 | /// - Note: 339 | /// - Valid range is __0x00 - 0xFF__. 340 | /// - Default is __0xE8__. 341 | /// - This should only be changed if multiple endpoints must be supported. 342 | case de(Action) 343 | /// ZigBee Cluster ID 344 | /// - Note: 345 | /// - Valid range is __0x00 - 0xFFFF__. 346 | /// - Default is __0x11__. 347 | /// - This should only be changed if multiple cluster IDs must be supported. 348 | case ci(Action) 349 | /// Transmit Options 350 | /// - Note: 351 | /// - Valid range is __0x00 - 0xFF__. 352 | /// - Default is __0x0__. 353 | /// - Application layer source transmit options for transparent mode: Bit0 - Disable retries and route repair. Bit5 - 354 | /// Enable APS Encryption (if EE=1) - note this decreases the maximum RF payload by 4 bytes below the value reported by NP. 355 | /// Bit6 - Use the extended timeout for this destination. 356 | case to(Action) 357 | 358 | public var rawValue: [UInt8] { 359 | switch self { 360 | case .se(let action): 361 | guard case .write(let parameter) = action else { return "SE".byteArray } 362 | return "SE".byteArray + [parameter] 363 | case .de(let action): 364 | guard case .write(let parameter) = action else { return "DE".byteArray } 365 | return "DE".byteArray + [parameter] 366 | case .ci(let action): 367 | guard case .write(let parameter) = action else { return "CI".byteArray } 368 | return "CI".byteArray + parameter.byteArray 369 | case .to(let action): 370 | guard case .write(let parameter) = action else { return "TO".byteArray } 371 | return "TO".byteArray + [parameter] 372 | } 373 | } 374 | } 375 | 376 | public enum RFInterfacing { 377 | /// TX Power Level 378 | /// - Note: 379 | /// - Valid range is __0x0 - 0x4__. 380 | /// - Default is __0x4__. 381 | /// - Approximate power levels w/o boost mode: 0= -5dBm, 1= -1dBm, 2= +1dBm, 3= +3dBm, 4= +5dBm. 382 | case pl(Action) 383 | /// Power Mode 384 | /// - Note: 385 | /// - Default is __true__. 386 | /// - If enabled, boost mode improves sensitivity by 2dBm and increases output power by 3dB, improving the link margin and range. 387 | case pm(Action) 388 | /// Power at PL4 389 | case pp 390 | 391 | public var rawValue: [UInt8] { 392 | switch self { 393 | case .pl(let action): 394 | guard case .write(var parameter) = action else { return "PL".byteArray } 395 | parameter = parameter > 0x4 ? 0x4 : parameter 396 | return "PL".byteArray + [parameter] 397 | case .pm(let action): 398 | guard case .write(let isEnabled) = action else { return "PM".byteArray } 399 | return "PM".byteArray + [isEnabled.uint8] 400 | case .pp: 401 | return "PP".byteArray 402 | } 403 | } 404 | } 405 | 406 | public enum Security { 407 | /// Encryption Enable 408 | /// - Note: 409 | /// - Default is __false__. 410 | case ee(Action) 411 | /// Encryption Options 412 | /// - Note: 413 | /// - Valid range is __0x0 - 0xFF__. 414 | /// - Default is __0x0__. 415 | /// - Bit0 - Acquire / Transmit network security key unencrypted during joining. Bit1 - Use Trust Center. ] 416 | /// Bit2 - Use hash link key. (for trust center only, Bit3 - Use Authentication) 417 | case eo(Action) 418 | /// Encryption Key 419 | /// - Note: 420 | /// - __1 - 32__ hexadecimal characters. 421 | /// - Default is __"__. 422 | /// - Sets key used for encryption and decryption (ZigBee trust center link key). This register can not be read. 423 | case ky(Action) 424 | /// Network Encryption Key 425 | /// - Note: 426 | /// - __1 - 32__ hexadecimal characters. 427 | /// - Default is __"__. 428 | /// - Sets network key used for network encryption and decryption. If set to 0 (default), the coordinator selects a random network encryption key (recommended). This register can not be read. 429 | case nk(Action) 430 | 431 | public var rawValue: [UInt8] { 432 | switch self { 433 | case .ee(let action): 434 | guard case .write(let isEnabled) = action else { return "EE".byteArray } 435 | return "EE".byteArray + [isEnabled.uint8] 436 | case .eo(let action): 437 | guard case .write(let parameter) = action else { return "EO".byteArray } 438 | return "EO".byteArray + [parameter] 439 | case .ky(let action): 440 | guard case .write(let parameter) = action else { return "KY".byteArray } 441 | return "KY".byteArray + parameter.byteArray 442 | case .nk(let action): 443 | guard case .write(let parameter) = action else { return "NK".byteArray } 444 | return "NK".byteArray + parameter.byteArray 445 | } 446 | } 447 | } 448 | 449 | public enum SerialInterfacing { 450 | /// Baud Rate 451 | /// - Note: 452 | /// - Valid range is __0x0 - 0x8__. 453 | /// - Default is __0x3__. 454 | /// - Serial interface baud rate for communication between modem serial port and host. Standard baud rates up to 115200 baud are supported. 455 | /// The use of non-standard baud rates is permitted but their performance is not guaranteed. 456 | case bd(Action) 457 | /// Parity 458 | /// - Note: 459 | /// - Valid range is __0x0 - 0x3__. 460 | /// - Default is __0x0__. 461 | /// - Configure parity for the UART. 462 | case nb(Action) 463 | /// Stop Bits 464 | /// - Note: 465 | /// - Valid range is __0x0 - 0x1__. 466 | /// - Default is __0x0__. 467 | /// - Configure the number of stop bits for the UART. This setting is only valid for no parity, even parity, or odd parity. 468 | case sb(Action) 469 | /// Packetization Timeout 470 | /// - Note: 471 | /// - Valid range is __0x0 - 0xFF__. 472 | /// - Default is __0x3__. 473 | /// - Number of character times of inter-character delay required before transmission begins. Set to zero to transmit characters as they arrive instead of buffering them into one RF packet. 474 | case ro(Action) 475 | /// Pin 16 DIO6/nRTS Configuration 476 | /// - Note: 477 | /// - Valid range is __0x0 - 0x5__. 478 | /// - Default is __0x0__. 479 | /// - Configure options for the DIO6 line of the module. Options include: nRTS flow control, and Digital Input and Output 480 | case d6(Action) 481 | /// Pin 12 DIO7/nCTS Configuration 482 | /// - Note: 483 | /// - Valid range is __0x0 - 0x7__. 484 | /// - Default is __0x1__. 485 | /// - Configure options for the DIO7 line of the module. Options include: nCTS flow control, Digital Input and Output, or RS-485 enable control. 486 | case d7(Action) 487 | /// API Enable 488 | /// - Note: 489 | /// - Valid range is __0x0 - 0x2__. 490 | /// - Default is __0x0__. 491 | /// - Enables API mode. 492 | case ap(Action) 493 | /// API Output Mode 494 | /// - Note: 495 | /// - Valid range is __0x0 - 0xB__. 496 | /// - Default is __0x0__. 497 | /// - Set the API output mode register value. 0 - Received Data formatted as native API frame format. 1 - Received RF data formatted as Explicit Rx-Indicator. 498 | /// 3 - Same as 1, plus unsupported ZDO request passthru. 7 - Same as 3, plus supported ZDO request passthru. 0x0B - Same as 3, plus Binding Request passthru 499 | case ao(Action) 500 | 501 | public var rawValue: [UInt8] { 502 | switch self { 503 | case .bd(let action): 504 | guard case .write(var parameter) = action else { return "BD".byteArray } 505 | parameter = parameter > 0x8 ? 0x8 : parameter 506 | return "BD".byteArray + [parameter] 507 | case .nb(let action): 508 | guard case .write(var parameter) = action else { return "NB".byteArray } 509 | parameter = parameter > 0x3 ? 0x3 : parameter 510 | return "NB".byteArray + [parameter] 511 | case .sb(let action): 512 | guard case .write(var parameter) = action else { return "SB".byteArray } 513 | parameter = parameter > 0x1 ? 0x1 : parameter 514 | return "SB".byteArray + [parameter] 515 | case .ro(let action): 516 | guard case .write(let parameter) = action else { return "RO".byteArray } 517 | return "RO".byteArray + [parameter] 518 | case .d6(let action): 519 | guard case .write(var parameter) = action else { return "D6".byteArray } 520 | parameter = parameter > 0x5 ? 0x5 : parameter 521 | return "D6".byteArray + [parameter] 522 | case .d7(let action): 523 | guard case .write(var parameter) = action else { return "D7".byteArray } 524 | parameter = parameter > 0x7 ? 0x7 : parameter 525 | return "D7".byteArray + [parameter] 526 | case .ap(let action): 527 | guard case .write(var parameter) = action else { return "AP".byteArray } 528 | parameter = parameter > 0x2 ? 0x2 : parameter 529 | return "AP".byteArray + [parameter] 530 | case .ao(let action): 531 | guard case .write(var parameter) = action else { return "AO".byteArray } 532 | parameter = parameter > 0xB ? 0xB : parameter 533 | return "AO".byteArray + [parameter] 534 | } 535 | } 536 | } 537 | 538 | public enum ATCommandOptions { 539 | /// AT Command Mode Timeout 540 | /// - Note: 541 | /// - Valid range is __0x2 - 0x28F__. 542 | /// - Default is __0x64__. 543 | /// - Command mode timeout parameter. If no valid commands have been received within this time period, modem returns to Idle Mode from AT Command Mode. 544 | case ct(Action) 545 | /// Guard Times 546 | /// - Note: 547 | /// - Valid range is __0x01 - 0xCE4__. 548 | /// - Default is __0x3E8__. 549 | /// - Set required period of silence before, after and between the Command Mode Characters of the Command Mode Sequence (GT + CC + GT). 550 | /// The period of silence is used to prevent inadvertent entrance into AT Command Mode. 551 | case gt(Action) 552 | /// Command Sequence Character 553 | /// - Note: 554 | /// - Valid range is __0x0 - 0xFF__. 555 | /// - Default is __0x2B__. 556 | /// - Character value to be used to break from data mode to command mode. The default value (0x2B) is the ASCII code for the plus ('+') character. 557 | case cc(Action) 558 | 559 | public var rawValue: [UInt8] { 560 | switch self { 561 | case .ct(let action): 562 | guard case .write(var parameter) = action else { return "CT".byteArray } 563 | parameter = parameter < 0x2 ? 0x2 : parameter 564 | parameter = parameter > 0x28F ? 0x28F : parameter 565 | return "CT".byteArray + parameter.byteArray 566 | case .gt(let action): 567 | guard case .write(var parameter) = action else { return "GT".byteArray } 568 | parameter = parameter < 0x01 ? 0x01 : parameter 569 | parameter = parameter > 0xCE4 ? 0xCE4 : parameter 570 | return "GT".byteArray + parameter.byteArray 571 | case .cc(let action): 572 | guard case .write(let parameter) = action else { return "CC".byteArray } 573 | return "CC".byteArray + [parameter] 574 | } 575 | } 576 | } 577 | 578 | public enum SleepModes { 579 | /// Cyclic Sleep Period 580 | /// - Note: 581 | /// - Valid range is __0x20 - 0xAF0__. 582 | /// - Default is __0x20__. 583 | /// - Set SP on all devices to match SP on End Device children. On routers and coordinators, SP determines the transmission and buffering timeouts when sending a message to a sleeping end device. 584 | case sp(Action) 585 | /// Number of Cyclic Sleep Periods 586 | /// - Note: 587 | /// - Valid range is __0x1 - 0xFFFF__. 588 | /// - Default is __0x1__. 589 | /// - Number of cyclic sleep periods used to calculate end device poll timeout. If an end device does not send a poll request to its parent coordinator 590 | /// or router within the poll timeout, the end device is removed from the child table. The poll timeout is calculated in milliseconds as (3 * SN * (SP * 10ms)), 591 | /// minimum of 5 seconds. i.e. if SN=15, SP=0x64, the timeout is 45 seconds. 592 | case sn(Action) 593 | /// Sleep Mode 594 | /// - Note: 595 | /// - Valid range is __0x0 - 0x5__. 596 | /// - Default is __0x0__. 597 | /// - Pin Hibernate is lowest power, Cyclic Sleep wakes on timer expiration, Cyclic Sleep Pin-Wake wakes on timer expiration or when Sleep_Rq (module pin 9) transitions 598 | /// from a high to a low state. If SM is set to 0, the XBee is a router, otherwise it is an end device. 599 | case sm(Action) 600 | /// Time Before Sleep 601 | /// - Note: 602 | /// - Valid range is __0x1 - 0xFFFE__. 603 | /// - Default is __0x1388__. 604 | /// - Time period of inactivity (no serial or RF data is sent or received) before activating Sleep Mode. The ST parameter is used only with Cyclic Sleep settings (SM=4-5). 605 | case st(Action) 606 | /// Sleep Options 607 | /// - Note: 608 | /// - Valid range is __0x0 - 0xFF__. 609 | /// - Default is __0x0__. 610 | /// - Bit1 - Wake for ST time on each cyclic wake after sleeping for SN sleep periods. Bit2 - Enable extended cyclic sleep (sleep for entire SN*SP time - possible data loss). All other option bits should be set to 0. 611 | case so(Action) 612 | /// Wake Host 613 | /// - Note: 614 | /// - Valid range is __0x0 - 0xFFFF__. 615 | /// - Default is __0x0__. 616 | /// - The end device wake host timer value. A non-zero value specifies the time the device should allow after waking from sleep before sending data out 617 | /// the UART (like an IO sample). If serial characters are received, the WH timer is stopped immediately. 618 | case wh(Action) 619 | /// Poll Rate 620 | /// - Note: 621 | /// - Valid range is __0x0 - 0x3E8__. 622 | /// - Default is __0x0__. 623 | /// - The end device poll rate. Setting this to 0 (default) enables polling at 100ms (default rate). Adaptive polling may allow the end device to poll more rapidly for a short time when receiving RF data. 624 | case po(Action) 625 | 626 | public var rawValue: [UInt8] { 627 | switch self { 628 | case .sp(let action): 629 | guard case .write(var parameter) = action else { return "SP".byteArray } 630 | parameter = parameter < 0x20 ? 0x20 : parameter 631 | parameter = parameter > 0xAF0 ? 0xAF0 : parameter 632 | return "SP".byteArray + parameter.byteArray 633 | case .sn(let action): 634 | guard case .write(var parameter) = action else { return "SN".byteArray } 635 | parameter = parameter < 0x1 ? 0x1 : parameter 636 | return "SN".byteArray + parameter.byteArray 637 | case .sm(let action): 638 | guard case .write(var parameter) = action else { return "SM".byteArray } 639 | parameter = parameter > 0x5 ? 0x5 : parameter 640 | return "SM".byteArray + [parameter] 641 | case .st(let action): 642 | guard case .write(var parameter) = action else { return "ST".byteArray } 643 | parameter = parameter < 0x1 ? 0x1 : parameter 644 | parameter = parameter > 0xFFFE ? 0xFFFE : parameter 645 | return "ST".byteArray + parameter.byteArray 646 | case .so(let action): 647 | guard case .write(let parameter) = action else { return "SO".byteArray } 648 | return "SO".byteArray + [parameter] 649 | case .wh(let action): 650 | guard case .write(let parameter) = action else { return "WH".byteArray } 651 | return "WH".byteArray + parameter.byteArray 652 | case .po(let action): 653 | guard case .write(var parameter) = action else { return "PO".byteArray } 654 | parameter = parameter > 0x3E8 ? 0x3E8 : parameter 655 | return "PO".byteArray + parameter.byteArray 656 | } 657 | } 658 | } 659 | 660 | public enum IOSettings { 661 | /// Pin 20 - DIO0/AD0/CB Configuration 662 | /// - Note: 663 | /// - Valid range is __0x0 - 0x5__. 664 | /// - Default is __0x1__. 665 | /// - Configure options for the AD0/DIO0 line of the module. Options include: Enabling commissioning button functionality, Analog to Digital converter,Digital Input and Output. 666 | case d0(Action) 667 | /// Pin 19 - DIO1/AD1/nSPI_ATTN Configuration 668 | /// - Note: 669 | /// - Valid range is __0x0 - 0x5__. 670 | /// - Default is __0x0__. 671 | /// - Configure options for the nSPI_ATTN/AD1/DIO1 line of the module. Options include: nSPI_ATTN,Analog to Digital converter,Digital Input and Output. 672 | case d1(Action) 673 | /// Pin 18 - DIO2/AD2/SPI_SCLK Configuration 674 | /// - Note: 675 | /// - Valid range is __0x0 - 0x5__. 676 | /// - Default is __0x0__. 677 | /// - Configure options for the AD2/DIO2/SPI_SCLK line of the module. Options include: SPI slave clock input, Analog to Digital converter,Digital Input and O 678 | case d2(Action) 679 | /// Pin 17 - DIO3/AD3/nSPI_SSEL Configuration 680 | /// - Note: 681 | /// - Valid range is __0x0 - 0x5__. 682 | /// - Default is __0x0__. 683 | /// - Configure options for the AD3/DIO3/nSPI_SSEL line of the module. Options include: nSPI Slave select, Analog to Digital converter, Digital Input and Output. 684 | case d3(Action) 685 | /// Pin 11 - DIO4/SPI_MOSI Configuration 686 | /// - Note: 687 | /// - Valid range is __0x0 - 0x5__. 688 | /// - Default is __0x0__. 689 | /// - Configure options for the DIO4/SPI_MOSI line of the module. Options include: SPI Slave MOSI, Digital Input and Output. 690 | case d4(Action) 691 | /// Pin 15 - DIO5/Associated Configuration 692 | /// - Note: 693 | /// - Valid range is __0x0 - 0x5__. 694 | /// - Default is __0x1__. 695 | /// - Configure options for the DIO5/Associated line of the module. Options include: Associated LED indicator (blinks when associated),Digital Input and Output. 696 | case d5(Action) 697 | /// Pin 9 - DIO8.nDTR/Sleep_Rq Configuration 698 | /// - Note: 699 | /// - Valid range is __0x0 - 0x5__. 700 | /// - Default is __0x1__. 701 | /// - Configure options for the DIO8 line of the module. Options include: SleepRq and Digital Input and Output. 702 | case d8(Action) 703 | /// Pin 13 - DIO9/nOn_Sleep Configuration 704 | /// - Note: 705 | /// - Valid range is __0x0 - 0x5__. 706 | /// - Default is __0x1__. 707 | /// - Configure options for the DIO9 line of the module. Options include: nOn-Sleep indicator and Digital Input and Output 708 | case d9(Action) 709 | /// Pin 6 - DIO10/RSSI PWM/PWM0 Configuration 710 | /// - Note: 711 | /// - Valid range is __0x0 - 0x5__. 712 | /// - Default is __0x1__. 713 | /// - Configure options for the DIO10 line of the module. Options include: RSSI PWM Output, Digital Input and Output. 714 | case p0(Action) 715 | /// Pin 7 - DIO11/PWM1 Configuration 716 | /// - Note: 717 | /// - Valid range is __0x0 - 0x5__. 718 | /// - Default is __0x0__. 719 | /// - Configure options for the DIO11 line of the module. Options include: Digital Input and O 720 | case p1(Action) 721 | /// Pin 4 - DIO12/SPI_MISO Configuration 722 | /// - Note: 723 | /// - Valid range is __0x0 - 0x5__. 724 | /// - Default is __0x0__. 725 | /// - Configure options for the SPI_MISO/DIO12 line of the module. Options include: SPI Slave MISO, Digital Input and Output. 726 | case p2(Action) 727 | /// Pin 2 - DIO13/DOUT Configuration 728 | /// - Note: 729 | /// - Valid range is __0x0 - 0x5__. 730 | /// - Default is __0x1__. 731 | /// - Configure options for the DIO13 line of the module. Options include: UART DOUT and Digital Input and Output. 732 | case p3(Action) 733 | /// Pin 3 - DIO14/DIN/nConfig Configuration 734 | /// - Note: 735 | /// - Valid range is __0x0 - 0x5__. 736 | /// - Default is __0x1__. 737 | /// - Configure options for the DIO14 line of the module. Options include: UART DIN and Digital Input and Output. 738 | case p4(Action) 739 | /// Pull-up Resistor Enable 740 | /// - Note: 741 | /// - Valid range is __0x0 - 0x7FFF__. 742 | /// - Default is __0x1FBF__. 743 | /// - Bitfield to configure internal pullup resistors status for I/O lines. 1=internal pullup enabled, 0=no internal pullup. Bitfield map: 744 | /// Bit0 - DIO4. Bit1 - DIO3/AD3. Bit2 - DIO2/AD2. Bit3 - DIO1/AD1. Bit4 - DIO0/AD0. Bit5 - DIO6/RTS. Bit6 - DIO8/nDTR/Sleep_Rq. Bit7 - DIO14/DIN/nConfig. 745 | //// Bit8 - DIO5/Associate. Bit9 - DIO9/nOn_Sleep. Bit10 - DIO12/CD. Bit11 - DIO10/PWM-RSSI. Bit12 - DIO11/PWM1. Bit13 - DIO7/CTS. Bit14 - DIO13/DOUT. 746 | case pr(Action) 747 | /// Pull-up/down Direction 748 | /// - Note: 749 | /// - Valid range is __0x0 - 0x7FFF__. 750 | /// - Default is __0x1FFF__. 751 | /// - Resistor direction (1=pullup, 0=pull-down) for corresponding I/O lines that are set in PR command. 752 | case pd(Action) 753 | /// Associate LED Blink Time 754 | /// - Note: 755 | /// - Valid range is __0x0A - 0xFF__. 756 | /// - Default is __0x0__. 757 | /// - This value determines the blink rate of the Associate/DIO5 pin if D5=1 and the module has started a network. Setting LT to 0 will use the default blink time (250ms). 758 | case lt(Action) 759 | /// RSSI PWM Timer 760 | /// - Note: 761 | /// - Valid range is __0x0 - 0xFF__. 762 | /// - Default is __0x28__. 763 | /// - Set duration of PWM (pulse width modulation) signal output on the RSSI pin (Pin6). The signal duty cycle is updated with each received packet or APS acknowledgment and is shut off when the timer expires. 764 | case rp(Action) 765 | 766 | public var rawValue: [UInt8] { 767 | switch self { 768 | case .d0(let action): 769 | guard case .write(var parameter) = action else { return "D0".byteArray } 770 | parameter = parameter > 0x5 ? 0x5 : parameter 771 | return "D0".byteArray + [parameter] 772 | case .d1(let action): 773 | guard case .write(var parameter) = action else { return "D1".byteArray } 774 | parameter = parameter > 0x5 ? 0x5 : parameter 775 | return "D1".byteArray + [parameter] 776 | case .d2(let action): 777 | guard case .write(var parameter) = action else { return "D2".byteArray } 778 | parameter = parameter > 0x5 ? 0x5 : parameter 779 | return "D2".byteArray + [parameter] 780 | case .d3(let action): 781 | guard case .write(var parameter) = action else { return "D3".byteArray } 782 | parameter = parameter > 0x5 ? 0x5 : parameter 783 | return "D3".byteArray + [parameter] 784 | case .d4(let action): 785 | guard case .write(var parameter) = action else { return "D4".byteArray } 786 | parameter = parameter > 0x5 ? 0x5 : parameter 787 | return "D4".byteArray + [parameter] 788 | case .d5(let action): 789 | guard case .write(var parameter) = action else { return "D5".byteArray } 790 | parameter = parameter > 0x5 ? 0x5 : parameter 791 | return "D5".byteArray + [parameter] 792 | case .d8(let action): 793 | guard case .write(var parameter) = action else { return "D8".byteArray } 794 | parameter = parameter > 0x5 ? 0x5 : parameter 795 | return "D8".byteArray + [parameter] 796 | case .d9(let action): 797 | guard case .write(var parameter) = action else { return "D9".byteArray } 798 | parameter = parameter > 0x5 ? 0x5 : parameter 799 | return "D9".byteArray + [parameter] 800 | case .p0(let action): 801 | guard case .write(var parameter) = action else { return "P0".byteArray } 802 | parameter = parameter > 0x5 ? 0x5 : parameter 803 | return "P0".byteArray + [parameter] 804 | case .p1(let action): 805 | guard case .write(var parameter) = action else { return "P1".byteArray } 806 | parameter = parameter > 0x5 ? 0x5 : parameter 807 | return "P1".byteArray + [parameter] 808 | case .p2(let action): 809 | guard case .write(var parameter) = action else { return "P2".byteArray } 810 | parameter = parameter > 0x5 ? 0x5 : parameter 811 | return "P2".byteArray + [parameter] 812 | case .p3(let action): 813 | guard case .write(var parameter) = action else { return "P3".byteArray } 814 | parameter = parameter > 0x5 ? 0x5 : parameter 815 | return "P3".byteArray + [parameter] 816 | case .p4(let action): 817 | guard case .write(var parameter) = action else { return "P4".byteArray } 818 | parameter = parameter > 0x5 ? 0x5 : parameter 819 | return "P4".byteArray + [parameter] 820 | case .pr(let action): 821 | guard case .write(var parameter) = action else { return "PR".byteArray } 822 | parameter = parameter > 0x7FFF ? 0x7FFF : parameter 823 | return "PR".byteArray + parameter.byteArray 824 | case .pd(let action): 825 | guard case .write(var parameter) = action else { return "PD".byteArray } 826 | parameter = parameter > 0x7FFF ? 0x7FFF : parameter 827 | return "PD".byteArray + parameter.byteArray 828 | case .lt(let action): 829 | guard case .write(var parameter) = action else { return "LT".byteArray } 830 | parameter = parameter < 0x0A ? 0x0A : parameter 831 | return "LT".byteArray + [parameter] 832 | case .rp(let action): 833 | guard case .write(let parameter) = action else { return "RP".byteArray } 834 | return "RP".byteArray + [parameter] 835 | } 836 | } 837 | } 838 | 839 | public enum IOSampling { 840 | /// IO Sampling Rate 841 | /// - Note: 842 | /// - Valid range is __0x32 - 0xFFFF__. 843 | /// - Default is __0x0__. 844 | /// - Set the IO sampling rate to enable periodic sampling. If set >0, all enabled digital IO and analog inputs will be sampled and transmitted every 845 | /// IR milliseconds. IO Samples are transmitted to the address specified by DH+DL. 846 | case ir(Action) 847 | /// Digital IO Change Detection 848 | /// - Note: 849 | /// - Valid range is __0x0 - 0xFFFF__. 850 | /// - Default is __0x0__. 851 | /// - Bitfield that configures which digital IO pins should be monitored for change detection. If a change is detected on an enabled digital IO pin, 852 | /// a digital IO sample is immediately transmitted to the address specified by DH+DL. 853 | case ic(Action) 854 | /// Supply Voltage High Threshold 855 | /// - Note: 856 | /// - Valid range is __0x0 - 0xFFFF__. 857 | /// - Default is __0x0__. 858 | /// - Configure the supply voltage high threshold. If the supply voltage measurement equals or drops below this threshold, the supply voltage will be appended to an IO sample transmission. 859 | case vPlus(Action) 860 | 861 | public var rawValue: [UInt8] { 862 | switch self { 863 | case .ir(let action): 864 | guard case .write(var parameter) = action else { return "IR".byteArray } 865 | parameter = parameter < 0x32 ? 0x32 : parameter 866 | return "IR".byteArray + parameter.byteArray 867 | case .ic(let action): 868 | guard case .write(let parameter) = action else { return "IC".byteArray } 869 | return "IC".byteArray + parameter.byteArray 870 | case .vPlus(let action): 871 | guard case .write(let parameter) = action else { return "V+".byteArray } 872 | return "V+".byteArray + parameter.byteArray 873 | } 874 | } 875 | } 876 | 877 | public enum DiagnosticCommands { 878 | /// Firmware Version 879 | case vr 880 | /// Hardware Version 881 | case hv 882 | /// Association Indication Status Code 883 | case ai 884 | /// RSSI of Last Packet 885 | /// - Note: RSSI of the last received packet or APS acknowledgment. 886 | case db 887 | /// Supply Voltage 888 | /// - Note: Supply voltage of the module in mV units. 889 | case percentageV 890 | 891 | public var rawValue: [UInt8] { 892 | switch self { 893 | case .vr: 894 | return "VR".byteArray 895 | case .hv: 896 | return "HV".byteArray 897 | case .ai: 898 | return "AI".byteArray 899 | case .db: 900 | return "DB".byteArray 901 | case .percentageV: 902 | return "%V".byteArray 903 | } 904 | } 905 | } 906 | } 907 | --------------------------------------------------------------------------------