Conflict between Bluetooth and Timer

Hello Everyone,
Thanks so much for taking a look at my code. I am just a newbie, and have been in the process of making my first app. Most of this is cobbled together from bits and pieces of other code that I found, and then modified. Weirdest problem. The Bluetooth runs fine and I can see the data on my App no issues. But the timer does not run. (does display). As soon as I stop Bluetooth from running, (I have an Arduino nano), the timer begins running again. There are no errors shown in Xcode so I am sort stuck on where to look.

Below is full Bluetooth Code:

import Foundation
import CoreBluetooth

enum ConnectionStatus: String {
    case connected
    case disconnected
    case scanning
    case connecting
    case error
}


let BarrelBaristaService: CBUUID = CBUUID(string: "4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
//Temperature F
let TemperatureCharacteristic: CBUUID = CBUUID(string: "5D54D470-8B08-4368-9E8F-03191A0314A5")
//Humidity %
let HumidityCharacteristic: CBUUID = CBUUID(string: "B2F5E988-C50F-4200-A1D9-5884F9417DEF")
//Weight %
let WeightCharacteristic: CBUUID = CBUUID(string: "BEB5483E-36E1-4688-B7F5-EA07361B26A8")

class BluetoothService: NSObject, ObservableObject {

    private var centralManager: CBCentralManager!

    var BarrelBarristaPeripheral: CBPeripheral?
    @Published var peripheralStatus: ConnectionStatus = .disconnected
    @Published var TempValue: Float = 0
    @Published var HumidValue: Float = 0
    @Published var WeightValue: Float = 0
    @Published var Connected: Bool = false

    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }

    func scanForPeripherals() {
        peripheralStatus = .scanning
        centralManager.scanForPeripherals(withServices: nil)
    }

}

extension BluetoothService: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            print("CB Powered On")
            scanForPeripherals()
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {

        if peripheral.name == "Barrel Barista" {
            print("Discovered \(peripheral.name ?? "no name")")
            BarrelBarristaPeripheral = peripheral
            centralManager.connect(BarrelBarristaPeripheral!)
            peripheralStatus = .connecting
        }
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        peripheralStatus = .connected
        Connected = true

        peripheral.delegate = self
        peripheral.discoverServices([BarrelBaristaService])
        centralManager.stopScan()
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        peripheralStatus = .disconnected
        Connected = false
    }

    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        peripheralStatus = .error
        print(error?.localizedDescription ?? "no error")
    }

}

extension BluetoothService: CBPeripheralDelegate {

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        for service in peripheral.services ?? [] {
            if service.uuid == BarrelBaristaService {
                print("found service for \(BarrelBaristaService)")
                peripheral.discoverCharacteristics([TemperatureCharacteristic], for: service)
                peripheral.discoverCharacteristics([HumidityCharacteristic], for: service)
                peripheral.discoverCharacteristics([WeightCharacteristic], for: service)
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        for characteristic in service.characteristics ?? [] {
            peripheral.setNotifyValue(true, for: characteristic)
            print("found characteristic, waiting on values.")
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        
        if characteristic.uuid == TemperatureCharacteristic {
            guard let data = characteristic.value else {
                print("No data received for \(characteristic.uuid.uuidString)")
                return
            }
            let TempData: Float = data.withUnsafeBytes { $0.pointee }
            TempValue = TempData
        }
        if characteristic.uuid == HumidityCharacteristic {
            guard let data = characteristic.value else {
                print("No data received for \(characteristic.uuid.uuidString)")
                return
            }
            let HumidData: Float = data.withUnsafeBytes { $0.pointee }
            HumidValue = HumidData
         }
        if characteristic.uuid == WeightCharacteristic {
            guard let data = characteristic.value else {
                print("No data received for \(characteristic.uuid.uuidString)")
                return
            }
            let WeightData: Float = data.withUnsafeBytes { $0.pointee }
            WeightValue = WeightData
         }
        
    }

}

Below is Starting App code:

import SwiftUI
@main
struct Barrel_BaristaApp: App {
    @ObservedObject var bluetoothService = BluetoothService()
    var body: some Scene {
      WindowGroup {
      SplashScreenView()
       .environmentObject(bluetoothService)
      }
   }
}

Below is where the timer appears in the View:

import SwiftUI
import UIKit
import Foundation



struct ContentView: View {
    @EnvironmentObject var bluetoothService: BluetoothService
    @Binding var whiskyname: String
    @Binding var barrelabv: String
    @Binding var imageSelected: UIImage
    @Binding var weightlownum: Float32
    @Binding var weighthinum: Float32
    @Binding var screenDisable: Bool
    @State var pourabv: String = ""
    @State var TempValue: String = ""
    @State var link = UIImage()
    @State var timeinterval = Date()
    @State var percentFilled: Float = 0
    @State var timeFormat: String = "Days"
    @State var timeremaining: String = ""
    let formatPicker: [String] = ["Seconds","Minutes","Hours","Days", "Weeks", "Months"]
    let calendar = Calendar.current
    
    let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
    let futuredate: Date = Calendar.current.date(byAdding: .second, value:0, to: Date()) ?? Date ()
    
    func updateTimeRemaining() {
        let remaining = Calendar.current.dateComponents([.year, .month, .weekOfYear, .hour, .minute, .second], from: futuredate, to: Date())
        let year = remaining.year ?? 0
        let month = remaining.month ?? 0
        let week = remaining.weekOfYear ?? 0
        let day = remaining.day ?? 0
        let hour = remaining.hour ?? 0
        let minute = remaining.minute ?? 0
        let second = remaining.second ?? 0
        timeremaining = "Whisky has been aging for \(year) Years  \(month) Months \(week) Weeks \(day) Days \(hour) Hours \(minute) Minutes \(second) Seconds"
    }
    
    
    var body: some View {
        NavigationStack{
            NavigationLink {
                SetupView()
            } label: {
                
            }
            .onReceive(timer, perform: { _ in
                updateTimeRemaining()
            })
            
            VStack{
                Text(self.whiskyname)
                    .frame(width: 300, height:1)
                    .font(.system(size: 24))
            }
            VStack{
                Image(uiImage: imageSelected)
                    .resizable()
                    .font(.system(size: 40))
                    .aspectRatio(contentMode: .fill)
                    .frame(width:245, height:245)
                    .font(.system(size: 24))
                    .background(Color.blue.opacity(0.4))
                    .cornerRadius(15)
                    .padding(.vertical, 10)
            }
            VStack(alignment: .leading){
                HStack{
                    if bluetoothService.Connected == true{
                        let link = Image(systemName: "link.circle.fill").foregroundColor(.blue)
                        link
                        Text("Bluetooth Connected")
                            .font(.footnote)
                    } else {
                        let link = Image(systemName: "link.circle.fill").foregroundColor(.red)
                        link
                        Text("Bluetooth Disconnected")
                            .font(.footnote)
                    }
                }
                HStack{
                    TextField("", text: $pourabv)
                        .padding(20)
                        .font(.system(size: 30))
                        .frame(width: 80, height:35)
                        .font(.system(size: 24))
                        .background(Color.blue.opacity(0.4))
                        .cornerRadius(15)
                    
                    Text("      % Pour ABV")
                        .font(.system(size: 30))
                        .frame(width: 200, height:35)
                        .font(.system(size: 24))
                        .cornerRadius(15)
                }
                HStack{
                    Text("\(bluetoothService.TempValue, specifier: "%.1f")°")
                        .font(.system(size: 30))
                        .frame(width: 110, height:35)
                        .font(.system(size: 24))
                        .background(Color.blue.opacity(0.4))
                        .cornerRadius(15)
                    Text("Temperature F")
                        .font(.system(size: 30))
                        .frame(width: 200, height:35)
                        .font(.system(size: 24))
                        .cornerRadius(15)
                }
                HStack{
                    Text("\(bluetoothService.HumidValue, specifier: "%.1f") %")
                        .font(.system(size: 30))
                        .frame(width: 110, height:35)
                        .font(.system(size: 24))
                        .background(Color.blue.opacity(0.4))
                        .cornerRadius(15)
                    Text("Humidity")
                        .font(.system(size: 30))
                        .frame(width: 200, height:35)
                        .font(.system(size: 24))
                        .cornerRadius(15)
                }
                HStack{
                    let percent = (100 / (weighthinum - weightlownum)) * (bluetoothService.WeightValue - weightlownum)
                    
                    Text("\(percent, specifier: "%.1f") %")
                        .font(.system(size: 30))
                        .frame(width: 110, height:35)
                        .font(.system(size: 24))
                        .background(Color.blue.opacity(0.4))
                        .cornerRadius(15)
                    Text("Barrel Filled")
                        .font(.system(size: 30))
                        .frame(width: 200, height:35)
                        .font(.system(size: 24))
                        .cornerRadius(15)
                }
                HStack{
                    let myString = (barrelabv)
                    let floatbarrelabv = (myString as NSString).floatValue
                    let myString2 = (pourabv)
                    let floatpourabv = (myString2 as NSString).floatValue
                    
                    let glasses = (((((1.8 * floatbarrelabv * (100 / (weighthinum - weightlownum)) * (bluetoothService.WeightValue - weightlownum))) / floatpourabv) * 3785.41) / 40) / 100
                    Image("whisky glass")
                        .resizable()
                        .frame(width: 50, height:50)
                    Text("\(glasses, specifier: "%.0f")")
                        .font(.system(size: 30))
                        .padding(20)
                        .frame(width: 105, height:35)
                        .font(.system(size: 24))
                        .background(Color.blue.opacity(0.4))
                        .cornerRadius(15)
                    Text("Glasses remaining")
                        .font(.system(size: 25))
                        .frame(width: 200, height:35)
                        .font(.system(size: 24))
                        .cornerRadius(15)
                }
                HStack{
                    let myString = (barrelabv)
                    let floatbarrelabv = (myString as NSString).floatValue
                    let myString2 = (pourabv)
                    let floatpourabv = (myString2 as NSString).floatValue
                    
                    let bottles = (((((1.8 * floatbarrelabv * (100 / (weighthinum - weightlownum)) * (bluetoothService.WeightValue - weightlownum))) / floatpourabv) * 3785.41) / 750) / 100
                    Image("bottle")
                        .resizable()
                        .frame(width: 50, height:50)
                    
                    Text("\(bottles, specifier: "%.1f")")
                        .font(.system(size: 30))
                        .padding(20)
                        .frame(width: 105, height:35)
                        .font(.system(size: 24))
                        .background(Color.blue.opacity(0.4))
                        .cornerRadius(15)
                    Text("Bottles remaining")
                        .font(.system(size: 25))
                        .frame(width: 200, height:40)
                        .font(.system(size: 24))
                        .cornerRadius(15)
                }
                
                
                
                Text(timeremaining)
                
                
            } //Main VStack ends here
            
            Spacer()
            
        } //Navigation Stack
        
        
    } // body view
    
    
} // Content View


struct ContentView_Previews: PreviewProvider {
    @State static var whiskyname: String = ""
    @State static var barrelabv: String = ""
    @State static var weighthinum: Float32 = 0
    @State static var weightlownum: Float32 = 0
    @State static var imageSelected = UIImage()
    @State static var screenDisable: Bool = false
    @State var link = UIImage()
    @State static var bottles: Float32 = 0
    static var previews: some View{
        
        ContentView(whiskyname: $whiskyname, barrelabv: $barrelabv, imageSelected: $imageSelected, weightlownum: $weightlownum, weighthinum: $weighthinum, screenDisable: $screenDisable)
            .environmentObject(BluetoothService())
    }
    
}

Thanks so much for taking a look at this. I am just starting out, and this is stopping me from progressing. There are no errors shown in Xcode, just does not work properly. Thanks again...

Dan

Try changing .common to .default.

Thanks so much for the help. So I made the change, and not quite fixed... but an interesting behavior. The display stays fixed at 1 second. However, if the screen locks, when I open the phone again you can see it count for a second (was away for a few minutes, and showed 3 min etc...) and then went back to 1 second and stayed there. So close... Does this new information help with a possible solution?

I'm not too familiar with that a way to run a timer. Two suggestions, the first might help the second will help:

  1. With your existing code make that let timer a state variable:
@State private var timer = Timer.publish(...)
  1. Alternatively run a timer from your model class (and get rid of the .onReceive(timer, from a view):
class BluetoothService: NSObject, ObservableObject {
    @Published var timeRemaining = ""
    private var timer: Timer!
    ...
    private func updateTimeRemaining() {
        timeRemaining = ...
    }
    init() {
        super.init()
        ...
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.updateTimeRemaining()
        }
    }
}
struct ContentView: View {
....
    var body: some View {
        ....
                Text(bluetoothService.timeRemaining)
        ....
    }
}

On a separate note beware that as you are scheduling a timer with one second interval there'll be times when the timer goes off just a little too early before the second changes to the next digit so your display will "stutter" at times and show the same seconds digit for 2 seconds in a row and then skip a digit. Might be not a big deal.

Thanks so very much!!!! Solution #2 was exactly what was needed. In case someone else runs into this problem in the future. This is what I did.

Brand new file "Timer"

import Foundation
import SwiftUI
import UIKit

class TimerService: NSObject, ObservableObject {
    
    @Published var timeremaining: String = ""
    
    private var timer: Timer!
    
    let formatPicker: [String] = ["Seconds","Minutes","Hours","Days", "Weeks", "Months"]
    let calendar = Calendar.current
    let futuredate: Date = Calendar.current.date(byAdding: .second, value:0, to: Date()) ?? Date ()
    private func updateTimeRemaining() {
        let remaining = Calendar.current.dateComponents([.year, .month, .weekOfYear, .hour, .minute, .second], from: futuredate, to: Date())
        let year = remaining.year ?? 0
        let month = remaining.month ?? 0
        let week = remaining.weekOfYear ?? 0
        let day = remaining.day ?? 0
        let hour = remaining.hour ?? 0
        let minute = remaining.minute ?? 0
        let second = remaining.second ?? 0
        timeremaining = "Whisky has been aging for \(year) Years  \(month) Months \(week) Weeks \(day) Days \(hour) Hours \(minute) Minutes \(second) Seconds"
    }
    override init() {
        super.init()
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.updateTimeRemaining()
        }
        
    }
}

Changes to top level app. I elected to make this an environmental Object, so I could use it anywhere...

import SwiftData


@main
struct Barrel_BaristaApp: App {
    @ObservedObject var bluetoothService = BluetoothService()
    @ObservedObject var timerService = TimerService()
    var body: some Scene {
      WindowGroup {
      SplashScreenView()
       .environmentObject(bluetoothService)
       .environmentObject(timerService)
      }
   }
}

Added to View here.,,,,

     Text(timerService.timeremaining)

Also remember to add the environmental object to your preview or it will not work...

struct ContentView_Previews: PreviewProvider {
    @State static var whiskyname: String = ""
    @State static var barrelabv: String = ""
    @State static var weighthinum: Float32 = 0
    @State static var weightlownum: Float32 = 0
    @State static var imageSelected = UIImage()
    @State static var screenDisable: Bool = false
    @State var link = UIImage()
    @State static var bottles: Float32 = 0
    static var previews: some View{
        
        ContentView(whiskyname: $whiskyname, barrelabv: $barrelabv, imageSelected: $imageSelected, weightlownum: $weightlownum, weighthinum: $weighthinum, screenDisable: $screenDisable)
            .environmentObject(BluetoothService())
            .environmentObject(TimerService())

So apparently there was a conflict between the Bluetooth and the Timer. Now that timer has been moved off of the view. That conflict no longer exists, and the timer runs normally when Bluetooth is active. I see see that "stutter" where the seconds do not always update every second. That is fine in this application. Once everything is verified working, the time will only be for years, months, weeks, and days. So a second or two off is perfectly fine. Thanks again..

Dan