1

I am using GCDAsyncUdpSocket for communication between my app and some smart-home hardware, and I have a problem with stopping a certain function. Logic goes something like this:

  1. Send a command
  2. If you didn't receive feedback from the hardware, it'll try to send it a few more times
  3. When app receives feedback, notification DidReceiveDataForRepeatSendingHandler is posted (along with device information in userInfo)

For example, let's say I have a curtain that can react on 3 commands: Open, Close and Stop... and that curtain is currently closed. I press Open (and don't receive feedback), and during the process I change my mind, so I press Stop. Now the app will send both commands simultaneously.

So without further ado, here's the code:

class RepeatSendingHandler: NSObject {

var byteArray: [UInt8]!
var gateway: Gateway!
var repeatCounter:Int = 1

var device:Device!

var appDel:AppDelegate!
var error:NSError? = nil

var sameDeviceKey: [NSManagedObjectID: NSNumber] = [:]
var didGetResponse:Bool = false
var didGetResponseTimer:Foundation.Timer!

//
// ================== Sending command for changing value of device ====================
//
init(byteArray:[UInt8], gateway: Gateway, device:Device, oldValue:Int) {
    super.init()
    appDel = UIApplication.shared.delegate as! AppDelegate

    self.byteArray = byteArray
    self.gateway = gateway
    self.device = device

    NotificationCenter.default.addObserver(self, selector: #selector(RepeatSendingHandler.didGetResponseNotification(_:)), name: NSNotification.Name(rawValue: NotificationKey.DidReceiveDataForRepeatSendingHandler), object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(sameDevice(_:)), name: NSNotification.Name(rawValue: NotificationKey.SameDeviceDifferentCommand), object: nil)


    sendCommand()

}

func updateRunnableList(deviceID: NSManagedObjectID) {
    RunnableList.sharedInstance.removeDeviceFromRunnableList(device: deviceID)
}

//   Did get response from gateway
func didGetResponseNotification (_ notification:Notification) {
    if let info = (notification as NSNotification).userInfo! as? [String:Device] {
        if let deviceInfo = info["deviceDidReceiveSignalFromGateway"] {
            if device.objectID == deviceInfo.objectID {
                didGetResponse = true
                didGetResponseTimer = nil
                NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationKey.DidReceiveDataForRepeatSendingHandler), object: nil)
            }
        }
    }
}

func sameDevice(_ notification: Notification) {
    print("NOTIFICATION RECEIVED for device with ID: ", self.device.objectID, "\n")
    if let info = notification.userInfo as? [NSManagedObjectID: NSNumber] {
        sameDeviceKey = info
    }
}

func sendCommand () {
    if sameDeviceKey != [device.objectID: device.currentValue] { print("keys have DIFFERENT values") } else { print("keys have SAME values") }

    if sameDeviceKey != [device.objectID: device.currentValue] {

        if !didGetResponse {
            if repeatCounter < 4 {

                print("Sending command. Repeat counter: ", repeatCounter)

                SendingHandler.sendCommand(byteArray: byteArray, gateway: gateway)
                didGetResponseTimer = Foundation.Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(RepeatSendingHandler.sendCommand), userInfo: nil, repeats: false)
                repeatCounter += 1

            } else {
                didGetResponseTimer = nil
                updateRunnableList(deviceID: device.objectID)
                CoreDataController.shahredInstance.saveChanges()
                NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationKey.DidReceiveDataForRepeatSendingHandler), object: nil)
                NotificationCenter.default.post(name: Notification.Name(rawValue: NotificationKey.RefreshDevice), object: self)
            }
        }else{
            didGetResponseTimer = nil
            updateRunnableList(deviceID: device.objectID)
            CoreDataController.shahredInstance.saveChanges()
            NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationKey.DidReceiveDataForRepeatSendingHandler), object: nil)
            NotificationCenter.default.post(name: Notification.Name(rawValue: NotificationKey.RefreshDevice), object: self)
        }
    } else {
        print("Command canceled")
        didGetResponseTimer = nil
        return
    }

 }   

On the ViewController where I keep my devices, I call this like:

    func openCurtain(_ gestureRecognizer:UITapGestureRecognizer){
    let tag = gestureRecognizer.view!.tag
    let address = [UInt8(Int(devices[tag].gateway.addressOne)),UInt8(Int(devices[tag].gateway.addressTwo)),UInt8(Int(devices[tag].address))]

    if devices[tag].controlType == ControlType.Curtain {
            let setDeviceValue:UInt8 = 0xFF
            let deviceCurrentValue = Int(devices[tag].currentValue)
            devices[tag].currentValue = 0xFF // We need to set this to 255 because we will always display Channel1 and 2 in devices. Not 3 or 4. And this channel needs to be ON for image to be displayed properly
            let deviceGroupId = devices[tag].curtainGroupID.intValue
            CoreDataController.shahredInstance.saveChanges()
            DispatchQueue.main.async(execute: {
                RunnableList.sharedInstance.checkForSameDevice(device: self.devices[tag].objectID, newCommand: NSNumber(value: setDeviceValue))
                _ = RepeatSendingHandler(byteArray: OutgoingHandler.setCurtainStatus(address, value: setDeviceValue, groupId:  UInt8(deviceGroupId)), gateway: self.devices[tag].gateway, device: self.devices[tag], oldValue: deviceCurrentValue)
            })
        }
}

What I did was I made a separate class where I have a dictionary that has Device's ManagedObjectID as a key, and the command we are sending is it's value. So whenever we are sending a command for a device that's already on the list, I post a notification SameDeviceDifferentCommand with userInfo containing device's ManagedObjectID and the old command. I use it on RepeatSendingHandler to populate sameDeviceKey dictionary. That's how I tried to distinguish which function should be stopped.

public class RunnableList {

open static let sharedInstance = RunnableList()

var runnableList: [NSManagedObjectID: NSNumber] = [:]

func checkForSameDevice(device: NSManagedObjectID, newCommand: NSNumber) {

    if runnableList[device] != nil && runnableList[device] != newCommand {
            let oldDataToSend = [device: runnableList[device]!]
            NotificationCenter.default.post(name: Notification.Name(rawValue: NotificationKey.SameDeviceDifferentCommand), object: self, userInfo: oldDataToSend)
            print("Notification sent for device with ID: ", device, "\n")
    }

    runnableList[device] = newCommand
    print("Device with ID: ", device, "received a new command", newCommand, "\n")
}

func removeDeviceFromRunnableList(device: NSManagedObjectID) {
    runnableList.removeValue(forKey: device)
    print("Removed from list device with ID: ", device)
}

}

However, sometimes it does it's job as it should, and sometimes it doesn't. Using a bunch of prints I tried to see in which order everything happens, and it seems that sometimes even though sameDeviceKey gets it's value from the notification - it looks like it uses old (nil) value until repeatCounter maxes out. I do not understand why.

Could anyone explain what is happening, and/or advise a better solution than the one I provided?

(There is a bit of additional code which I removed as it's irrelevant to logic/question). Please bear in mind that I am a junior and that I'm relatively new to this.

halfer
  • 19,824
  • 17
  • 99
  • 186
vla
  • 11
  • 2

0 Answers0