jsdw_ios/QuickLocation/Manager/MQTT/MQTTService.swift

258 lines
8.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// MQTTService.swift
// QuickLocation
//
// Created by on 2026/6/12.
//
import Foundation
import CocoaMQTT
import UIKit
import _LocationEssentials
// MARK: - MQTT
///
enum MqttType: String, Codable {
case track = "track" // )
case disconnect = "disconnect"
case message = "message"
case join = "join" //
case leave = "leave" //
case dismiss = "dismiss" //
case kick = "kick" //
case signIn = "signin" //
case sos = "sos" //
case needTrack = "needtrack" //
case emote = "emote" //
}
///
struct Points: Codable {
let lat: Double
let lon: Double
let addr: String
let time: Int64 //
let speed: Double
let bearing: Double
let altitude: Double
let accuracy: Double
}
///
struct MqttLocation: Codable {
let battery: String
let points: [Points]
}
/// MQTT
struct MqttIncomingMessage: Decodable {
let type: String?
let data: MqttIncomingData?
let extra: String?
}
struct MqttIncomingData: Decodable {
let points: [Points]?
let battery: String?
}
// MARK: - MQTTService
/// MQTT 5.0
final class MQTTService: NSObject {
static let shared = MQTTService()
private var mqtt: CocoaMQTT5?
private var isConnected = false
// MARK: -
var onConnected: (() -> Void)?
var onDisconnected: (() -> Void)?
/// topicCallback
var onMessageReceived: ((CocoaMQTT5Message, UInt16, MqttPublishProperties) -> Void)?
/// topic
private var topicCallbacks: [String: (CocoaMQTT5Message) -> Void] = [:]
// MARK: -
private var host: String { "emqx.batiao8.com" }
private var port: UInt16 { 1883 }
/// clientID
private(set) var clientID: String = ""
private var userName = "batiao"
private var password = "Batiao12B"
private var topic = "smartdrive/"
override private init() {}
// MARK: -
func connect() {
guard !isConnected else { return }
if clientID.isEmpty {
clientID = "smartdrive_\(AppContextManager.shared.userId)"
}
let mqtt = CocoaMQTT5(clientID: clientID, host: host, port: port)
mqtt.username = userName
mqtt.password = password
mqtt.keepAlive = 60
mqtt.autoReconnect = true
mqtt.autoReconnectTimeInterval = 5
mqtt.delegate = self
mqtt.logLevel = .warning
self.mqtt = mqtt
_ = mqtt.connect()
}
// MARK: -
func disconnect() {
mqtt?.disconnect()
isConnected = false
}
// MARK: -
/// clientID
func updateClientID(_ newID: String) {
clientID = newID
disconnect()
connect()
}
// MARK: -
/// - Parameters:
/// - topic:
/// - qos:
/// - callback: topic
func subscribe(topic: String, qos: CocoaMQTTQoS = .qos1, callback: ((CocoaMQTT5Message) -> Void)? = nil) {
let subscription = MqttSubscription(topic: topic, qos: qos)
mqtt?.subscribe([subscription])
if let cb = callback {
topicCallbacks[topic] = cb
}
}
// MARK: -
func unsubscribe(topic: String) {
mqtt?.unsubscribe(topic)
topicCallbacks.removeValue(forKey: topic)
}
/// topic: smartdrive/<memberId>
func subscribeGroupMembers(_ memberIds: [String]) {
for id in memberIds {
subscribe(topic: "\(topic)\(id)")
}
}
///
func unsubscribeGroupMembers(_ memberIds: [String]) {
for id in memberIds {
mqtt?.unsubscribe("\(topic)\(id)")
}
}
// MARK: -
@discardableResult
func publish(topic: String, message: String, qos: CocoaMQTTQoS = .qos1) -> Int {
let properties = MqttPublishProperties()
return mqtt?.publish(topic, withString: message, qos: qos, DUP: false, retained: false, properties: properties) ?? -1
}
@discardableResult
func publish(topic: String, data: Data, qos: CocoaMQTTQoS = .qos1) -> Int {
let properties = MqttPublishProperties()
let message = CocoaMQTT5Message(topic: topic, payload: [UInt8](data))
return mqtt?.publish(message, DUP: false, retained: false, properties: properties) ?? -1
}
// MARK: -
/// Android
func reportLocation(lat: Double, lon: Double, addr: String,
speed: CLLocationSpeed, bearing: CLLocationDirection,
altitude: CLLocationDistance, accuracy: CLLocationAccuracy) {
let battery = UIDevice.current.batteryLevel > 0
? Int(UIDevice.current.batteryLevel * 100)
: 0
let point = Points(
lat: lat, lon: lon, addr: addr,
time: Int64(Date().timeIntervalSince1970 * 1000),
speed: speed, bearing: bearing,
altitude: altitude, accuracy: accuracy
)
let location = MqttLocation(battery: battery.string, points: [point])
guard let jsonData = try? JSONEncoder().encode(location),
let dataDict = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
else { return }
// data JSON
let payload: [String: Any] = [
"type": MqttType.track.rawValue,
"extra": "",
"data": dataDict
]
publish(topic: "\(topic)\(AppContextManager.shared.userId)", message: payload.toJsonString())
}
}
// MARK: - CocoaMQTT5Delegate
extension MQTTService: CocoaMQTT5Delegate {
func mqtt5(_ mqtt5: CocoaMQTT5, didConnectAck ack: CocoaMQTTCONNACKReasonCode, connAckData: MqttDecodeConnAck?) {
isConnected = true
print("MQTT5 connected: \(ack)")
onConnected?()
}
func mqtt5(_ mqtt5: CocoaMQTT5, didPublishMessage message: CocoaMQTT5Message, id: UInt16) {
print("MQTT5 published: \(message.topic)")
}
func mqtt5(_ mqtt5: CocoaMQTT5, didPublishAck id: UInt16, pubAckData: MqttDecodePubAck?) {
print("MQTT5 publish ack: \(id)")
}
func mqtt5(_ mqtt5: CocoaMQTT5, didPublishRec id: UInt16, pubRecData: MqttDecodePubRec?) {}
func mqtt5(_ mqtt5: CocoaMQTT5, didReceiveMessage message: CocoaMQTT5Message, id: UInt16, publishData: MqttDecodePublish?) {
// topic
if let cb = topicCallbacks[message.topic] {
cb(message)
return
}
//
if let payload = message.string {
print("MQTT5 received on \(message.topic): \(payload)")
}
onMessageReceived?(message, id, MqttPublishProperties())
}
func mqtt5(_ mqtt5: CocoaMQTT5, didSubscribeTopics success: NSDictionary, failed: [String], subAckData: MqttDecodeSubAck?) {
print("MQTT5 subscribe success: \(success), failed: \(failed)")
}
func mqtt5(_ mqtt5: CocoaMQTT5, didUnsubscribeTopics topics: [String], unsubAckData: MqttDecodeUnsubAck?) {
print("MQTT5 unsubscribe: \(topics)")
}
func mqtt5(_ mqtt5: CocoaMQTT5, didReceiveDisconnectReasonCode reasonCode: CocoaMQTTDISCONNECTReasonCode) {}
func mqtt5(_ mqtt5: CocoaMQTT5, didReceiveAuthReasonCode reasonCode: CocoaMQTTAUTHReasonCode) {}
func mqtt5DidPing(_ mqtt5: CocoaMQTT5) {}
func mqtt5DidReceivePong(_ mqtt5: CocoaMQTT5) {}
func mqtt5DidDisconnect(_ mqtt5: CocoaMQTT5, withError err: Error?) {
isConnected = false
print("MQTT5 disconnected: \(err?.localizedDescription ?? "")")
onDisconnected?()
}
}