Too many open files using NWListener on macOS - swift

I'm trying to create an app that listens for incoming data on a UDP port using NWListener. This works fine until 248 messages are received - at which point the app crashes with the error message nw_listener_inbox_accept_udp socket() failed [24: Too many open files].
This (I think) relates to the file descriptor limit and so I tried resetting the NWListener within a safe count of 100, but the problem still persists. This seems clumsy and I'm not sure it's actually possible within a receive.
How can I truly reset it such that it releases any open files?
Full code below, which is instantiated within a SwiftUI content view using:
.onAppear() {
udpListener.start(port: self.udpPort)
}
Full class:
import Foundation
import Network
class UdpListener: NSObject, ObservableObject {
private var listener: NWListener?
private var port: NWEndpoint.Port?
#Published var incoming: String = ""
#Published var messageCount: Int = 0
func start(port: NWEndpoint.Port) {
self.port = port
do {
let params = NWParameters.udp
params.allowLocalEndpointReuse = true
self.listener = try NWListener(using: params, on: port)
self.listener?.stateUpdateHandler = {(newState) in
switch newState {
case .ready:
print("ready")
default:
break
}
}
self.listener?.newConnectionHandler = {(newConnection) in
newConnection.stateUpdateHandler = {newState in
switch newState {
case .ready:
self.receive(on: newConnection)
default:
break
}
}
newConnection.start(queue: DispatchQueue(label: "new client"))
}
} catch {
print("unable to create listener")
}
self.listener?.start(queue: .main)
}
func receive(on connection: NWConnection) {
connection.receiveMessage { (data, context, isComplete, error) in
if let error = error {
print(error)
return
}
guard let data = data, !data.isEmpty else {
print("unable to receive data")
return
}
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss.SSSS"
DispatchQueue.main.async {
self.messageCount = self.messageCount + 1
self.incoming = formatter.string(from: date) + " " + String(decoding: data, as: UTF8.self) + "\n" + self.incoming
}
if self.messageCount == 100 {
print("Resetting")
self.listener?.stateUpdateHandler = nil
self.listener?.newConnectionHandler = nil
self.listener?.cancel()
self.listener = nil
self.start(port: self.port!)
}
}
}
}

Related

Swift: receive UDP packet using Network framework

I'm studying swift 5.6 Network framework. For this I have a Java-based server, that waits a udp packet of size 64 at localhost port 10000 and sends it back to localhost port 20000. Here is my implementation for Swift :
import Foundation
import Network
class UdpConnection {
private var connection: NWConnection?
private var isConnectionReady = false
init?(host: String, port: UInt16) {
self.connection = NWConnection(
host: NWEndpoint.Host(host),
port: NWEndpoint.Port(integerLiteral: port),
using: .udp
)
let connectionEstablishWaiter = DispatchSemaphore(value: 0)
self.connection?.stateUpdateHandler = { [weak self] (newState) in
switch (newState) {
case .ready:
self?.isConnectionReady = true
default :
self?.isConnectionReady = false
}
connectionEstablishWaiter.signal()
}
self.connection?.start(queue: .global())
switch connectionEstablishWaiter.wait(timeout: .now() + 1) {
case .timedOut:
return nil
default:
()
}
}
func sendUDP(content: Data) {
let sema = DispatchSemaphore(value: 0)
self.connection?.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
if (NWError == nil) {
print("Data was sent to UDP")
} else {
print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
}
sema.signal()
})))
sema.wait()
}
func receiveUDP() {
self.connection?.receiveMessage { (data, context, isComplete, error) in
if (isComplete) {
if let data = data {
print("Receive is complete : \(data.count)")
} else {
print("Data == nil")
}
}
}
}
}
And here is my test app :
import Foundation
if let udpRequestConnection = UdpConnection(host: "127.0.0.1", port: 10_000) {
print("connection established OK")
if let udpResponseConnection = UdpConnection(host: "127.0.0.1", port: 20_000) {
let data = Data(count: 64)
udpResponseConnection.receiveUDP()
udpRequestConnection.sendUDP(content: data)
print("sent")
}
} else {
print("connection establishing FAILURE")
}
I see no packet received and moreover I see a strange picture in Wireshark :
What am I doing wrong? Why is there an ICMP packet ? What am I missing to get this UDP ?
Ok, it seems that I got the misunderstanding -> NWConnection ctor is responsible for setting outbound connection params. So to listen to UDP stream I need the following implementation :
import Foundation
import Network
import Combine
class UDPListener: ObservableObject {
var listener: NWListener?
var connection: NWConnection?
var queue = DispatchQueue.global(qos: .userInitiated)
/// New data will be place in this variable to be received by observers
#Published private(set) public var messageReceived: Data?
/// When there is an active listening NWConnection this will be `true`
#Published private(set) public var isReady: Bool = false
/// Default value `true`, this will become false if the UDPListener ceases listening for any reason
#Published public var listening: Bool = true
/// A convenience init using Int instead of NWEndpoint.Port
convenience init(on port: Int) {
self.init(on: NWEndpoint.Port(integerLiteral: NWEndpoint.Port.IntegerLiteralType(port)))
}
/// Use this init or the one that takes an Int to start the listener
init(on port: NWEndpoint.Port) {
let params = NWParameters.udp
params.allowFastOpen = true
self.listener = try? NWListener(using: params, on: port)
self.listener?.stateUpdateHandler = { update in
switch update {
case .ready:
self.isReady = true
case .failed, .cancelled:
// Announce we are no longer able to listen
self.listening = false
self.isReady = false
default:
()
}
}
self.listener?.newConnectionHandler = { connection in
self.createConnection(connection: connection)
}
self.listener?.start(queue: self.queue)
}
private func createConnection(connection: NWConnection) {
self.connection = connection
self.connection?.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
self.receive()
case .cancelled, .failed:
// Cancel the listener, something went wrong
self.listener?.cancel()
// Announce we are no longer able to listen
self.listening = false
default:
()
}
}
self.connection?.start(queue: .global())
}
func receive() {
self.connection?.receiveMessage { data, context, isComplete, error in
if error != nil {
return
}
guard isComplete, let data = data else {
return
}
self.messageReceived = data
print("Rx data size = \(data.count)")
if self.listening {
self.receive()
}
}
}
func cancel() {
self.listening = false
self.connection?.cancel()
}
}
With this I am able to get the UDP response I expect.

Has recent update affected genData in AudioKit?

*** Update 2 ***
Ok, done some more digging and managed to get things working with MIKMIDI by starting at position 1 rather than position 0; the same fix hasn't worked with AudioKit.
Further, I've created a new, ugly AF, app that replicates behaviour across both frameworks and outputs, among other things, the track data, and there is definitely a difference between them. I include the code below for perusal. You'll need both MIKMIDI and AudioKit available as frameworks, and a soundfont. Both appear to be working identically but the generated data is different. Again, it could be that I'm making a fundamental error for which I apologise if that's the case, but if anyone can point out the issue I'd be grateful. Many thanks.
import SwiftUI
import MIKMIDI
import AudioKit
let MIKsequence = MIKMIDISequence()
let MIKsequencer = MIKMIDISequencer()
var AKoutputSampler = MIDISampler(name: "output")
var AKsequencer = AppleSequencer()
var AKmixer = Mixer()
var AKengine = AudioEngine()
func AKsetup() {
print("AK Setup Start---------")
AKmixer.addInput(AKoutputSampler)
AKengine.output = AKmixer
do {
try AKengine.start()
} catch {
print("error starting the engine: \(error)")
}
print("AK Setup End ----------")
}
func AKinitialise(){
print("AK Initilise Start --------")
AKsequencer = AppleSequencer()
for t in 0..<AKsequencer.tracks.count {
AKsequencer.deleteTrack(trackIndex: t)
}
let AKtrackManager = AKsequencer.newTrack("piano")
for note in 1..<6{
AKtrackManager?.add(noteNumber: MIDINoteNumber(note+60), velocity: 100, position: Duration(beats: Double(note * 16)/16), duration: Duration(beats: 0.25),channel: 1)
}
let length = AKtrackManager?.length
print("Length = \(length)")
let mnd : [MIDINoteData] = (AKtrackManager?.getMIDINoteData())!
for d in mnd {
print("Note \(d.noteNumber), position \(d.position.seconds)")
}
AKsequencer.setLength(Duration(beats: Double(length!)))
AKsequencer.disableLooping()
AKsequencer.setTempo(120)
AKsequencer.addTimeSignatureEvent(timeSignature: TimeSignature(topValue: 4, bottomValue: .four))
AKtrackManager?.setMIDIOutput(AKoutputSampler.midiIn)
let hexValues = AKsequencer.genData()!.map { String(format: "%02X", $0) }
print(hexValues.joined(separator: " "))
AKsequencer.debug()
print("AK Initialise End ---------")
}
func loadSF2(name: String, ext: String, preset: Int, sampler: MIDISampler) {
print("Load SF2 Start")
guard let url = Bundle.main.url(forResource: name, withExtension: ext) else {
print("LoadSF2: Could not get SoundFont URL")
return
}
do {
try sampler.loadMelodicSoundFont(url: url, preset: preset)
} catch {
print("can not load SoundFont \(name) with error: \(error)")
}
print("Load SF2 End")
}
func AKplay() {
AKengine.stop()
loadSF2(name: "Chaos Bank", ext: "sf2", preset: 1, sampler: AKoutputSampler)
do {
try AKengine.start()
} catch {
print("error starting the engine: \(error)")
}
AKsequencer.play()
}
func AKstop(){
AKsequencer.stop()
AKsequencer.rewind()
}
func MIKinitialise(){
print("MIK Initialise Start")
do {
let tempo = 120.0
let signature = MIKMIDITimeSignature(numerator: 4, denominator: 4)
MIKsequence.setOverallTempo(tempo)
MIKsequence.setOverallTimeSignature(signature)
for t in MIKsequence.tracks {
MIKsequence.removeTrack(t)
}
let _ = try MIKsequence.addTrack()
let track = MIKsequence.tracks[0]
let trackSynth = MIKsequencer.builtinSynthesizer(for: track)
if let soundfont = Bundle.main.url(forResource: "Chaos Bank", withExtension: "sf2") {
do {
try trackSynth?.loadSoundfontFromFile(at: soundfont)
} catch {
print("can not load SoundFont with error: \(error)")
}
let instrumentId = MIKMIDISynthesizerInstrument(id: 10, name: "Eric")
try trackSynth!.selectInstrument(instrumentId!, error: ())
print("Available Instruments \(trackSynth!.availableInstruments)")
}
var notes = [MIKMIDINoteEvent]()
for n in 1..<6 {
let note = MIKMIDINoteEvent(timeStamp:Double(n),note:UInt8(60 + n),velocity:100,duration:0.25,channel:1)
notes.append(note)
}
track.addEvents(notes)
let length = track.length
MIKsequence.length = length
MIKsequencer.sequence = MIKsequence
print("Duration in seconds \(MIKsequencer.sequence.durationInSeconds)")
print("Tempo Track \(MIKsequence.tempoTrack.length), \(MIKsequence.tempoTrack.notes.count)")
for t in MIKsequence.tracks {
print("Track Number \(t.trackNumber)")
for notes in t.notes {
print("Note \(notes.note), \(notes.duration), \(notes.timeStamp)")
}
}
let hexValues = MIKsequencer.sequence.dataValue!.map { String(format: "%02X", $0) }
print(hexValues.joined(separator: " "))
} catch let error {
print(error.localizedDescription)
}
print("MIK Initialise End")
}
func startMIKPlayback(){
MIKsequencer.startPlayback()
}
func stopMIKPlayback(){
MIKsequencer.stop()
}
func getDocumentsDirectory() -> URL {
// find all possible documents directories for this user
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
// just send back the first one, which ought to be the only one
print(paths[0])
return paths[0]
}
func saveMIKFile()->String{
let date = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYYMMddHHmmss"
let filename = dateFormatter.string(from: date) + " MIK Song.mid"
try! MIKsequence.write(to: getDocumentsDirectory().appendingPathComponent(filename))
return getDocumentsDirectory().absoluteString
}
func saveAudioKitFile()->String{
let date = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYYMMddHHmmss"
let filename = dateFormatter.string(from: date) + "AK Song.mid"
try! AKsequencer.genData()!.write(to: getDocumentsDirectory().appendingPathComponent(filename))
return getDocumentsDirectory().absoluteString
}
struct ContentView: View {
init(){
AKsetup()
MIKinitialise()
}
var body: some View {
HStack{
VStack {
Text("MIKMIDI Test 01")
Button("Play", action: startMIKPlayback)
Button("Stop", action: stopMIKPlayback)
Button("Save") {
let _ = print(saveMIKFile())
}
}
.padding()
VStack {
Text("AudioKit Test 01")
let _ = AKinitialise()
Button("Play", action: AKplay)
Button("Stop", action: AKstop)
Button("Save") {
let _ = print(saveAudioKitFile())
}
}
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
*** End Update 2 ***
*** Update ***
Still having the same problem, and now I've tried with both AudioKit and MIKMIDI. I've run the generated file through a midi analyzer online and it says "Undefined Variable: Last". I've reached out to both MIKMIDI and Midi-Analyzer authors to see if they can assist but if anyone can throw light on this issue, I'd be grateful.
*** End Update ***
I'm working on an app that saves a midi sequence to file, using AudioKit and genData(). However, it seems that a recent update - either OS or Audiokit - has affected the way things save.
The startnote now seems to be offset on tracks by a varying amount, and the rest of the track then follows that offset. Oftentimes the end notes of the track may be missing. This problem was not occurring until recently.
Showing output of the sequence shows the data in the correct positions but it's coming out like this (the notes should be starting at position 0 in the track):
Pattern Offset shown in Garageband
I've also had difficulty in importing the same midi file into other packages; again, this wasn't a problem until recently.
I'm happy to be told I'm doing something amiss but, as I've said, it seems to have been working up until recently.
Any help would be really appreciated.
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
func saveFile()->String{
try! sequencer.genData()!.write(to: getDocumentsDirectory().appendingPathComponent("QuickTestmidi.mid"))
return getDocumentsDirectory().absoluteString
}
func setSequence_01(){
var trackLength = 0.0
sequencer.tracks[0].setMIDIOutput(outputSampler[0].midiIn)
for track in sequencer.tracks {
track.clear()
}
for i in 0..<16 {
sequencer.tracks[0].add(noteNumber: MIDINoteNumber(96 + i), velocity: MIDIVelocity(100), position: Duration(beats: Double(2 * i)), duration: Duration(beats: 1),channel: 1)
trackLength = Double(sequencer.tracks[0].length)
sequencer.setLength(Duration(beats:trackLength))
sequencer.enableLooping()
sequencer.setTempo(120)
sequencer.addTimeSignatureEvent(timeSignature: TimeSignature(topValue: 4, bottomValue: .four))
}
}

IOS Swift WebRtc insertDtmf issue

I am building an app that works with janus gateway via websocket and webrtc. everything works fine, I can send and receive voice calls successfully but insertDtmf metod doesnt send my dtmf to other peer.
Same account and same codes in android works fine.
Here is where I prepare webrtc
private func prepareWebRtc( callbacks:PluginHandleWebRTCCallbacksDelegate) {
if (pc != nil) {
if (callbacks.getJsep() == nil) {
createSdpInternal(callbacks: callbacks, isOffer: isOffer)
} else {
let jsep = callbacks.getJsep()!
let sdpString:String = jsep["sdp"] as! String
let type:RTCSdpType = RTCSessionDescription.type(for: jsep["type"] as! String)
let sdp:RTCSessionDescription = RTCSessionDescription.init(type: type, sdp: sdpString)
pc.setRemoteDescription(sdp) { (err) in}
}
} else {
trickle = callbacks.getTrickle() != nil ? callbacks.getTrickle()! : false
streamsDone(webRTCCallbacks: callbacks)
}
}
private func streamsDone(webRTCCallbacks:PluginHandleWebRTCCallbacksDelegate) {
let rtcConfig = RTCConfiguration.init()
rtcConfig.iceServers = server.iceServers
rtcConfig.bundlePolicy = RTCBundlePolicy.maxBundle
rtcConfig.rtcpMuxPolicy = RTCRtcpMuxPolicy.require
rtcConfig.continualGatheringPolicy = RTCContinualGatheringPolicy.gatherContinually
rtcConfig.sdpSemantics = .planB
let source :RTCAudioSource = sessionFactory.audioSource(with: audioConstraints)
let audioTrack:RTCAudioTrack? = sessionFactory.audioTrack(with: source, trackId: AUDIO_TRACK_ID)
let stream:RTCMediaStream? = sessionFactory.mediaStream(withStreamId: LOCAL_MEDIA_ID)
if (audioTrack != nil){
stream!.addAudioTrack(audioTrack!)
myStream = stream
}
if (stream != nil){
onLocalStream(stream: stream!)
}
// pc.addTrack(audioTrack, mediaStreamLabels);
pc = sessionFactory.peerConnection(with: rtcConfig, constraints: audioConstraints, delegate: nil)
if (myStream != nil){
pc.add(myStream)
}
if let obj:[String:Any] = webRTCCallbacks.getJsep(){
let sdp:String = obj["sdp"] as! String
let type:RTCSdpType = RTCSessionDescription.type(for: obj["type"] as! String)
let sessionDescription:RTCSessionDescription = RTCSessionDescription(type: type, sdp: sdp)
print(" STREAMS DONE JSEP NULL DEĞİL")
// pc.setRemoteDescription(WebRtcObserver(webRTCCallbacks), sessionDescription);
pc.setRemoteDescription(sessionDescription) { (err) in
}
}else{
createSdpInternal(callbacks: webRTCCallbacks, isOffer: isOffer)
print(" STREAMS DONE JSEP NULL ");
}
/* } catch (Exception ex) {
webRTCCallbacks.onCallbackError(ex.getMessage());
}*/
}
and here where I try to send dtmf
public func insertDTMF(_ tone:String){
if(pc != nil){
if let dtmfSender = pc.senders.first?.dtmfSender{
dtmfSender.insertDtmf(tone, duration: 200, interToneGap: 70)
}
//Here the timers are in ms
}
}
In my case, this is how I have handled insert DTMF functionality.
a - First filter out audio RTCRtpSender track:
var audioSender: RTCRtpSender?
for rtpSender in pc.senders {
if rtpSender.track?.kind == "audio" {
audioSender = rtpSender
}
}
b - And then use the same filtered audioSender object to insert the tone using OperationQueue
if let audioSender = audioSender {
let queue = OperationQueue()
queue.addOperation({
audioSender.dtmfSender?.insertDtmf(dtmfTone, duration: TimeInterval(0.1),interToneGap: TimeInterval(0.5))
})
}
Note: you can modify duration and interToneGap as per your requirement.
Hope this solution works for you as well.
The original answer can be found here: https://stackoverflow.com/a/60148372/4515269

SwiftUI app freezing when using multiple product identifiers in StoreKit

I'm currently learning Swift and following some tutorials but I'm stuck on a StoreKit issue.
The code works when I provide a single productIdentifier, but when I provide more than 1 in the Set, the entire app hangs on loading. This is in the iOS Simulator, and on a device. I've got 2 identifiers in the set, and both of these work individually, but not at the same time. My code looks the same as the original tutorial (video) so I don't know where I'm going long.
Entire Store.swift file below. Problem appears to be in the fetchProducts function, but I'm not sure. Can anyone point me in the right direction?
import StoreKit
typealias FetchCompletionHandler = (([SKProduct]) -> Void)
typealias PurchaseCompletionHandler = ((SKPaymentTransaction?) -> Void)
class Store: NSObject, ObservableObject {
#Published var allRecipes = [Recipe]() {
didSet {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
for index in self.allRecipes.indices {
self.allRecipes[index].isLocked = !self.completedPurchases.contains(self.allRecipes[index].id)
}
}
}
}
private let allProductIdentifiers = Set(["com.myname.ReceipeStore.test", "com.myname.ReceipeStore.test2"])
private var completedPurchases = [String]()
private var productsRequest: SKProductsRequest?
private var fetchedProducts = [SKProduct]()
private var fetchCompletionHandler: FetchCompletionHandler?
private var purchaseCompletionHandler: PurchaseCompletionHandler?
override init() {
super.init()
startObservingPaymentQueue()
fetchProducts { products in
self.allRecipes = products.map { Recipe(product: $0) }
}
}
private func startObservingPaymentQueue() {
SKPaymentQueue.default().add(self)
}
private func fetchProducts(_ completion: #escaping FetchCompletionHandler) {
guard self.productsRequest == nil else { return }
fetchCompletionHandler = completion
productsRequest = SKProductsRequest(productIdentifiers: allProductIdentifiers)
productsRequest!.delegate = self
productsRequest!.start()
}
private func buy(_ product: SKProduct, competion: #escaping PurchaseCompletionHandler) {
purchaseCompletionHandler = competion
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
}
extension Store {
func product(for identififier: String) -> SKProduct? {
return fetchedProducts.first(where: { $0.productIdentifier == identififier })
}
func purchaseProduct(_ product: SKProduct) {
buy(product) { _ in }
}
}
extension Store: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
var shouldFinishTransactions = false
switch transaction.transactionState {
case .purchased, .restored:
completedPurchases.append(transaction.payment.productIdentifier)
shouldFinishTransactions = true
case .failed:
shouldFinishTransactions = true
case .deferred, .purchasing:
break
#unknown default:
break
}
if shouldFinishTransactions {
SKPaymentQueue.default().finishTransaction(transaction)
DispatchQueue.main.async {
self.purchaseCompletionHandler?(transaction)
self.purchaseCompletionHandler = nil
}
}
}
}
}
// loading products from the store
extension Store: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let loadedProducts = response.products
let invalidProducts = response.invalidProductIdentifiers
guard !loadedProducts.isEmpty else {
print("Could not load the products!")
if !invalidProducts.isEmpty {
print("Invalid products found: \(invalidProducts)")
}
productsRequest = nil
return
}
// cache the feteched products
fetchedProducts = loadedProducts
// notify anyone waiting on the product load (swift UI view)
DispatchQueue.main.async {
self.fetchCompletionHandler?(loadedProducts)
self.fetchCompletionHandler = nil
self.productsRequest = nil
}
}
}```
It looks like you're running all of your requests on the main DispatchQueue, this will block other main queue work until completed. You should consider handling some of these tasks with a custom concurrent queue. This bit of sample code should get the ball rolling.
func requestProducts(_ productIdentifiers: Set<ProductIdentifier>, handler: #escaping ProductRequestHandler) {
// Set request handler
productRequest?.cancel()
productRequestHandler = handler
// Request
productRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productRequest?.delegate = self
productRequest?.start()
}
func requestPrices() {
// Retry interval, 5 seconds, set this to your liking
let retryTimeOut = 5.0
var local1: String? = nil
var local2: String? = nil
let bundleIdentifier = Bundle.main.bundleIdentifier!
let queue = DispatchQueue(label: bundleIdentifier + ".IAPQueue", attributes: .concurrent)
// Request price
queue.async {
var trying = true
while(trying) {
let semaphore = DispatchSemaphore(value: 0)
requestProducts(Set(arrayLiteral: SettingsViewController.pID_1000Credits, SettingsViewController.pID_2000Credits)) { (response, error) in
local1 = response?.products[0].localizedPrice
local2 = response?.products[1].localizedPrice
semaphore.signal()
}
// We will keep checking on this thread until completed
_ = semaphore.wait(timeout: .now() + retryTimeOut)
if(local2 != nil) { trying = false }
}
// Update with main thread once request is completed
DispatchQueue.main.async {
self.price1 = local1 ?? "$0.99"
self.price2 = local2 ?? "$1.99"
}
}
}
extension SKProduct {
// Helper function, not needed for this example
public var localizedPrice: String? {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = self.priceLocale
return formatter.string(from: self.price)
}

ORSSerialPort.send doesn't send anything

I am new (very new!!) to swift and straggling to make my UI to send a string over the serial port. I've managed to open the port and read/parse the incoming traffic but when it comes to send a string, nothing is sent.
What I need to do is typing in the sendTextField and when press the SendButton to send the string to serial port. Also, when I print the data which is what I want to send over serial port, it prints the number of bytes I try to send (i.e. 5 bytes). Shouldn't this be the string "Hello" that I try to send to serial port?
I am using Xcode Version 11.2 (11B52) and Swift 5.
Any help will be really appreciated. Thank you in advance!
This is how I call the "send" function:
#IBAction func SendButton(_ sender: Any) {
let TxData = sendTextField.stringValue
SFSerialIn.SendSerialData(TxData)
}
My main program is below:
import ORSSerial
import IOKit
import IOKit.serial
let SFSerialRegexp =
"(?<SFmode>[A-Z]+),\\s*" + "(?<prox>[0-1]),\\s*"
class SFSerialIn: NSObject, ORSSerialPortDelegate {
let path = "/dev/cu.usbserial-AI0484S9"
let baudRate: NSNumber = 115200
var serialPort: ORSSerialPort?
var delegate: SFSerialDelegate?
var stringBuffer = ""
var regex: NSRegularExpression!
var receivedBufferStart = false
override init() {
regex = try! NSRegularExpression(pattern: SFSerialRegexp)
}
deinit {
disconnect()
}
func SendSerialData(_ TxData: String){
let data = Data(TxData.utf8)
serialPort?.send(data)
print(TxData)
print(data)
}
func connect() {
if let serialPort = ORSSerialPort(path: path) {
serialPort.baudRate = baudRate
serialPort.delegate = self
serialPort.open()
} else {
print("Failed to open serial port")
}
}
func disconnect() {
serialPort?.close()
print("closing port...")
}
func serialPort(_ serialPort: ORSSerialPort, didReceive data: Data) {
guard let string = String(data: data, encoding: .utf8)
else {
return
}
stringBuffer += string
parseBuffer()
}
func parseBuffer() {
let lines = stringBuffer.split { $0.isNewline }
guard lines.count > 1 else {
return
}
let nextLines = lines[1...].joined()
if !receivedBufferStart {
stringBuffer = nextLines
receivedBufferStart = true
return
}
let line = String(lines[0])
if let matchResult = regex.firstMatch(in: line, range: NSRange(..<line.endIndex, in: line)) {
let sensorFrame = SFFrame(matchResult: matchResult, string: line)
delegate?.receive(sensorFrame: sensorFrame)
stringBuffer = nextLines
return
}
print("Failed to parse line :(")
stringBuffer = nextLines
}
func serialPort(_ serialPort: ORSSerialPort, didEncounterError error: Error) {
print("Serial port encountered error", error)
}
func serialPortWasOpened(_ serialPort: ORSSerialPort) {
print("Serial port opened")
}
func serialPortWasClosed(_ serialPort: ORSSerialPort) {
print("Serial port closed")
}
func serialPortWasRemovedFromSystem(_ serialPort: ORSSerialPort) {
print("Serial port was removed from system")
}
}
protocol SFSerialDelegate {
func receive(sensorFrame: SFFrame)
}
extension StringProtocol {
var data: Data { .init(utf8) }
}
It doesn't look to me like you're ever storing the opened serial port in your serialPort instance property. So, when you do serialPort?.send(data), serialPort is nil, and the ? (optional chaining) operator means that send() isn't called.
Try storing the serial port in your property after opening it:
func connect() {
if let serialPort = ORSSerialPort(path: path) {
serialPort.baudRate = baudRate
serialPort.delegate = self
serialPort.open()
self.serialPort = serialPort
} else {
print("Failed to open serial port")
}
}