Swift 4 - MQTT receive constantly messages - swift

I want to update a label in my swift app constantly with a distance-value I get from my raspberry. The configuration of the raspberry works fine and it publish me the value as a string every second, so I can receive it with MQTT.
Now I run out of ideas how to subscribe to this value. Do I need some kind of loop? Everything I tried the debugger found nil inside my subscription.
Is there a possibility to let my app know, when a message received?
My code (snippet)
override func viewDidLoad() {
super.viewDidLoad()
configureMQTT()
mqttClient.subscribe(distanceTopic, qos: .qos1)
func configureMQTT() {
let clientID = "IPhoneTest"
let host = "192.168.2.105"
let port = UInt16(1883)
mqttClient = CocoaMQTT(clientID: clientID, host: host, port: port)
mqttClient.username = ""
mqttClient.password = ""
mqttClient.keepAlive = 60
mqttClient.delegate = self
}
func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16 ) {
if (message.topic == distanceTopic){
print(message.string!)
I know it is nil, because it only subscribes one time after starting the app. I can't set the subscription to an action, so do I need a loop or sth like this?
Thank you for your help!

Related

Swift-NIO + WebSocket-Kit: Proper Setup/Cleanup in a Mac App

Context
I'm developing a Mac app. In this app, I want to run a websocket server. To do this, I'm using Swift NIO and Websocket-Kit. My full setup is below.
Question
All of the documentation for Websocket-Kit and SwiftNIO is geared towards a creating a single server-side process that starts up when you launch it from the command line and then runs infinitely.
In my app, I must be able to start the websocket server and then shut it down and restart it on demand, without re-launching my application. The code below does that, but I would like confirmation of two things:
In the test() function, I send some text to all connected clients. I am unsure if this is thread-safe and correct. Can I store the WebSocket instances as I'm doing here and message them from the main thread of my application?
Am I shutting down the websocket server correctly? The result of the call to serverBootstrap(group:)[...].bind(host:port:).wait() creates a Channel and then waits infinitely. When I call shutdownGracefully() on the associated EventLoopGroup, is that server cleaned up correctly? (I can confirm that port 5759 is free again after this shutdown, so I'm guessing everything is cleaned up?)
Thanks for the input; it's tough to find examples of using SwiftNIO and Websocket-Kit inside an application.
Code
import Foundation
import NIO
import NIOHTTP1
import NIOWebSocket
import WebSocketKit
#objc class WebsocketServer: NSObject
{
private var queue: DispatchQueue?
private var eventLoopGroup: MultiThreadedEventLoopGroup?
private var websocketClients: [WebSocket] = []
#objc func startServer()
{
queue = DispatchQueue.init(label: "socketServer")
queue?.async
{
let upgradePipelineHandler: (Channel, HTTPRequestHead) -> EventLoopFuture<Void> = { channel, req in
WebSocket.server(on: channel) { ws in
ws.send("You have connected to WebSocket")
DispatchQueue.main.async {
self.websocketClients.append(ws)
print("websocketClients after connection: \(self.websocketClients)")
}
ws.onText { ws, string in
print("received")
ws.send(string.trimmingCharacters(in: .whitespacesAndNewlines).reversed())
}
ws.onBinary { ws, buffer in
print(buffer)
}
ws.onClose.whenSuccess { value in
print("onClose")
DispatchQueue.main.async
{
self.websocketClients.removeAll { (socketToTest) -> Bool in
return socketToTest === ws
}
print("websocketClients after close: \(self.websocketClients)")
}
}
}
}
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
let port: Int = 5759
let promise = self.eventLoopGroup!.next().makePromise(of: String.self)
let server = try? ServerBootstrap(group: self.eventLoopGroup!)
// Specify backlog and enable SO_REUSEADDR for the server itself
.serverChannelOption(ChannelOptions.backlog, value: 256)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
let webSocket = NIOWebSocketServerUpgrader(
shouldUpgrade: { channel, req in
return channel.eventLoop.makeSucceededFuture([:])
},
upgradePipelineHandler: upgradePipelineHandler
)
return channel.pipeline.configureHTTPServerPipeline(
withServerUpgrade: (
upgraders: [webSocket],
completionHandler: { ctx in
// complete
})
)
}.bind(host: "0.0.0.0", port: port).wait()
_ = try! promise.futureResult.wait()
}
}
///
/// Send a message to connected clients, then shut down the server.
///
#objc func test()
{
self.websocketClients.forEach { (ws) in
ws.eventLoop.execute {
ws.send("This is a message being sent to all websockets.")
}
}
stopServer()
}
#objc func stopServer()
{
self.websocketClients.forEach { (ws) in
try? ws.eventLoop.submit { () -> Void in
print("closing websocket: \(ws)")
_ = ws.close()
}.wait() // Block until complete so we don't shut down the eventLoop before all clients get closed.
}
eventLoopGroup?.shutdownGracefully(queue: .main, { (error: Error?) in
print("Eventloop shutdown now complete.")
self.eventLoopGroup = nil
self.queue = nil
})
}
}
In the test() function, I send some text to all connected clients. I am unsure if this is thread-safe and correct. Can I store the WebSocket instances as I'm doing here and message them from the main thread of my application?
Exactly as you're doing here, yes, that should be safe. ws.eventLoop.execute will execute that block on the event loop thread belonging to that WebSocket connection. This will be safe.
When I call shutdownGracefully() on the associated EventLoopGroup, is that server cleaned up correctly? (I can confirm that port 5759 is free again after this shutdown, so I'm guessing everything is cleaned up?)
Yes. shutdownGracefully forces all connections and listening sockets closed.

Integrate Apollo subscriptions on iOS with Action Cable being used on the backend for websockets

I'm trying to make Apollo subscriptions on iOS work with a backend that is using Action Cable to implement websockets. I learned that the iOS app needs to send command, channel and channel id to the backend to make subscriptions work (see here). I have tried to use the function write func write(_ str: String, force forced: Bool = false, id: Int? = nil) in WebSocketTransport.swift on WebSocketTransport object when initializing instance of Apollo. Below you can see how I'm doing that.
let userDefault = UserDefaults.standard
var authPayloads = Dictionary<String, String> ()
var authToken = ""
if let token = userDefault.object(forKey: "token") {
authToken = "\(token)"
authPayloads.updateValue(authToken, forKey: "authorization")
}
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = authPayloads
let map: GraphQLMap = authPayloads
let wsEndpointURL = URL(string: "ws://localhost:8080/subscriptions/\(authToken)")!
let endpointURL = URL(string: "http://localhost:8080/api")!
websocket = WebSocketTransport(request: URLRequest(url: wsEndpointURL), connectingPayload: map)
var channelId = Int(arc4random_uniform(100000))
websocket?.write(stringify(json: ["command":"subscribe", "identifier": stringify(json: ["channel":"channelName", "channelId": channelId])]))
let splitNetworkTransport = SplitNetworkTransport(
httpNetworkTransport: HTTPNetworkTransport(
url: endpointURL,
configuration: configuration
),
webSocketNetworkTransport: websocket!
)
return ApolloClient(networkTransport: splitNetworkTransport)
}()
However, the backend isn't seeing what I'm writing to the WebSocket Transport object in their logs and I'm not able to subscribe to that specific channel. Any idea how I can make use Apollo subscriptions on iOS if the backend is using Action Cable, and make the two work together?
The only solution for now is to use Swift-ActionCableClient to recieve streams, and use the mutations/queries from ApolloClient.
Unfortenatly! Apollo iOS doesn't know how to communicate with ActionCable channels, this issue is reported on Apollo-iOS Github Issue #634 & Your issue Github Issue #454

GoogleCast iOS sender, v4, not sending messages

On version 2, the sender app was able to send messages.
func deviceManager(_ deviceManager: GCKDeviceManager!,
didConnectToCastApplication
applicationMetadata: GCKApplicationMetadata!,
sessionID: String!,
launchedApplication: Bool) {
deviceManager.add(self.textChannel)
}
However, the API says that we are now using GCKSessionManager instead of GCKDeviceManager.
The API says I must have a GCKSession add the textChannel, which I did here:
Once the session starts, I add the textChannel (because sessionManager.currentCastSession was nil before the session started).
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKSession) {
if session.device == connectionQueue {
connectionQueue = nil
}
self.sessionManager!.currentCastSession!.add(textChannel)
print("")
}
Meanwhile, I send the text message in another function:
let result = self.textChannel.sendTextMessage("\(self.textField.text)", error: &error)
But the result is always false, and the error is always "Channel is not connected or is not registered with a session".
In addition, when I do:
print("isConnected1 \(self.textChannel.isConnected)")
the result is false.
Do you know what other steps I am missing for it to be connected?
Just learned that it was an issue of my namespace. It connects now.
Problem was the namespace wasn't matching the namespace from my receiver code.
fileprivate lazy var textChannel:TextChannel = {
return TextChannel(namespace: NAMESPACE)
}()

How do I get the broadcast address of my wifi network using Swift?

currently I am using GCDAsyncUdpSocket to send a udp broadcast. Currently, the code sends it to a hardcoded 255.255.255.255 address. Is that the correct broadcast address on all networks? Will it fail on certain networks? What is the correct swift code?
socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.main)
socket!.send(ipRequestData!, toHost: "255.255.255.255", port: discoveryPort, withTimeout: 10000, tag: 0)
This question has an answer but it is in Objective-C
Calculating the Broadcast Address in Objective-C
Your broadcast address depends on your host address and the subnet mask. You can find the formula to calculate the broadcast address on Wikipedia:
broadcastAddress = ipAddress | ~subnetMask
You can calculate it with the following function:
func calculateBroadcastAddress(ipAddress: String, subnetMask: String) -> String {
let ipAdressArray = ipAddress.split(separator: ".")
let subnetMaskArray = subnetMask.split(separator: ".")
guard ipAdressArray.count == 4 && subnetMaskArray.count == 4 else {
return "255.255.255.255"
}
var broadcastAddressArray = [String]()
for i in 0..<4 {
let ipAddressByte = UInt8(ipAdressArray[i]) ?? 0
let subnetMaskbyte = UInt8(subnetMaskArray[i]) ?? 0
let broadcastAddressByte = ipAddressByte | ~subnetMaskbyte
broadcastAddressArray.append(String(broadcastAddressByte))
}
return broadcastAddressArray.joined(separator: ".")
}
Example:
let broadcastAddress = calculateBroadcastAddress(ipAddress: "172.16.0.0", subnetMask: "255.240.0.0")
Result:
"172.31.255.255"
In general, you should never choose the main queue for network operations because it will lead to lags in the user interface (which is drawn in the main queue). Better use
let udpSocket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.global())
Do not forget to explicitly enable broadcasts for this socket
do {
try udpSocket?.enableBroadcast(true)
} catch let error {
print(error)
}
Also consider that Apple enforces an iOS app to work with IPv6, which does not support broadcasts but multicasts. So maybe you should switch to multicasts at least if your device is inside a pure IPv6 network.

Reading Sensor data indefinitely using TCP in Swift

I am new to Swift and i am trying to read the data from a sensor that connects to my router using Wifi. According to the sensor documentation: "In local communication mode the sensor node works as TCP/IP-server and writes output data in ASCII-format in the defined IP-address port 8080 at 1Hz rate.” So i want to read that data that the sensor is continuously emitting every second and use it to plot a chart on the iPhone. This is how the sample data looks like, the first value on each row(7255,7256.., 7259) is the time that changes every second.
7255,64,6,46,390,555,1,11137,0,0
7256,64,6,46,390,785,1,1108,0,0
7257,64,6,46,390,602,1,1135,0,0
7258,64,6,46,390,847,2,1135,0,0
7259,64,6,47,403,870,2,1669,0,0
I wrote a sample application and I am able to connect to the sensor and read the data but it doesn’t work continuously or consistently. I will receive one record then it will wait for few second and then all of a sudden i will receive the data for 5-8 seconds. Also after working for 25-30 seconds it will wait for a long time and then nothing will be read anymore. I would like to read the data continuously like the free app "WIFI TCP Test Tool" available for free on App Store. This app reads the data perfectly every second from the sensor so i know there is nothing wrong with the sensor or my router. Here is my sample code, would appreciate any help.
import UIKit
import Foundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func readSensorData() {
let addr = "192.168.0.100"
let port = 8080
var buffer = [UInt8](count: 255, repeatedValue: 0)
var inp : NSInputStream?
var out : NSOutputStream?
NSStream.getStreamsToHostWithName(addr, port: port, inputStream: &inp, outputStream: &out)
if inp != nil && out != nil {
let inputStream : NSInputStream = inp!
inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
inputStream.open()
if inputStream.streamError == nil {
while true {
let readChars: Int = inputStream.read(&buffer, maxLength: buffer.count)
if (readChars > 0) {
let readString: String = NSString(data: NSData(bytes:buffer, length:readChars), encoding: NSUTF8StringEncoding)! as String
print(readString)
} else {
print ("sensor closed connection")
inputStream.close()
break
}
}
} else {
print ("could not create socket")
}
} else {
print("could not initialize stream")
}
}
#IBAction func startSensorReadTapped(sender: AnyObject) {
readSensorData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}