setupViews()
setupChatCallBack()
- //设置默认平台
- self.title = MSPlatform.doubao.title
+
}
func setupViews(){
- self.title = "大模型对话"
+ //设置默认平台
+ self.title = MSPlatform.doubao.title
self.view.backgroundColor = .white
self.navigationItem.rightBarButtonItems = [UIBarButtonItem(title: "切换平台", style: .done, target: self, action: #selector(changePlatform))]
}
func setupChatCallBack(){
+ IBAiManager.shared.useMode = { [weak self] mode in
+ guard let self = self else {return}
+ self.title = mode.title
+ }
IBAiManager.shared.aiResponse = {[weak self] response in
print(response.description)
guard let self = self else {return}
if let model = list.last,model.reponseId == response.reponseId {
list.removeLast()
list.append(response)
+// IBSpeaker.shared.appendText(response.totalContent)
}
else{
list.append(response)
tableView.scrollToRow(at: IndexPath(row: list.count - 1, section: 0), at: .none, animated: true)
}
}
+ if response.type == 4{
+ IBSpeaker.shared.appendText(response.totalContent)
+ }
}
}
--- /dev/null
+//
+// IBSpeaker.swift
+// IOTBluetooth
+//
+// Created by liangshi on 2025/2/13.
+//
+
+import AVFoundation
+
+class IBSpeaker: NSObject, AVSpeechSynthesizerDelegate {
+ static let shared = IBSpeaker()
+ // MARK: - Properties
+ private let synthesizer = AVSpeechSynthesizer()
+ private var textBuffer: String = "" // 用于存储待播放的文字
+ private var isPlaying: Bool = false // 是否正在播放
+
+ // MARK: - Initializer
+ override init() {
+ super.init()
+ synthesizer.delegate = self
+ }
+
+ // MARK: - Public Methods
+
+ /// 添加文字并开始播放
+ func appendText(_ text: String) {
+ textBuffer += text
+
+ // 如果没有正在播放,则开始播放
+ if !isPlaying {
+ playNextChunk()
+ }
+ }
+
+ /// 停止播放并清空缓冲区
+ func stop() {
+ synthesizer.stopSpeaking(at: .immediate)
+ textBuffer = ""
+ isPlaying = false
+ }
+
+ // MARK: - Private Methods
+
+ /// 播放缓冲区中的下一段文字
+ private func playNextChunk() {
+ guard !textBuffer.isEmpty else {
+ isPlaying = false
+ return
+ }
+
+ // 每次播放一段文字(可以根据需要调整)
+ let chunkSize = textBuffer.count // 每次播放的字符数
+ let endIndex = textBuffer.index(textBuffer.startIndex, offsetBy: min(chunkSize, textBuffer.count))
+ let chunk = String(textBuffer[..<endIndex])
+
+ // 移除已播放的文字
+ textBuffer.removeSubrange(..<endIndex)
+
+ // 创建语音并播放
+ let utterance = AVSpeechUtterance(string: chunk)
+ utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN") // 设置语言为中文
+ utterance.rate = 0.5 // 设置语速
+ utterance.pitchMultiplier = 1.0 // 设置音调
+
+ synthesizer.speak(utterance)
+ isPlaying = true
+ }
+
+ // MARK: - AVSpeechSynthesizerDelegate
+
+ func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
+ print("语音播放完成")
+ // 当前段落播放完成后,继续播放下一段
+ playNextChunk()
+ }
+
+ func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
+ print("语音播放取消")
+ isPlaying = false
+ }
+
+ func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
+ print("正在播放: \(characterRange)")
+ }
+}
import UIKit
import IOTBluetooth
-
+import AVFAudio
class ViewController: BaseViewController {
@IBOutlet weak var listView: UITableView!
3F2AF82F2D57125D00635545 /* ChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F2AF7F92D57118D00635545 /* ChatViewController.swift */; };
3F2AF8352D573B6200635545 /* ChatCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F2AF8342D573B5D00635545 /* ChatCell.swift */; };
3F6A07CC2D2D2C3F00F4122A /* IBPassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6A07CB2D2D2C3F00F4122A /* IBPassword.swift */; };
+ 3F8167202D5DC65B002936D5 /* IBSpeaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F81671F2D5DC64B002936D5 /* IBSpeaker.swift */; };
3F9355B72D38FF2100C54DAB /* IBPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9355B62D38FF1900C54DAB /* IBPush.swift */; };
3FCFBFDE2D548D70003FC090 /* pumpkin.lzma in Resources */ = {isa = PBXBuildFile; fileRef = 3FCFBFDD2D548D70003FC090 /* pumpkin.lzma */; };
3FFAE0552D3A042900325873 /* IBContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFAE0542D3A03EF00325873 /* IBContact.swift */; };
3F2AF7F92D57118D00635545 /* ChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewController.swift; sourceTree = "<group>"; };
3F2AF8342D573B5D00635545 /* ChatCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatCell.swift; sourceTree = "<group>"; };
3F6A07CB2D2D2C3F00F4122A /* IBPassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBPassword.swift; sourceTree = "<group>"; };
+ 3F81671F2D5DC64B002936D5 /* IBSpeaker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBSpeaker.swift; sourceTree = "<group>"; };
3F9355B62D38FF1900C54DAB /* IBPush.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBPush.swift; sourceTree = "<group>"; };
3FCFBFDD2D548D70003FC090 /* pumpkin.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = pumpkin.lzma; sourceTree = "<group>"; };
3FFAE0542D3A03EF00325873 /* IBContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBContact.swift; sourceTree = "<group>"; };
8A1DFF512B9EB4F800B735FF /* AppDelegate.swift */,
8A1DFF552B9EB4F800B735FF /* ViewController.swift */,
8A34B9262BA2AB66000F66BD /* ConnectViewController.swift */,
+ 3F81671F2D5DC64B002936D5 /* IBSpeaker.swift */,
8A76B6082BA84C2B0016F2AB /* AbilityViewController.swift */,
3F2AF7F92D57118D00635545 /* ChatViewController.swift */,
3F2AF8342D573B5D00635545 /* ChatCell.swift */,
8A76B6092BA84C2B0016F2AB /* AbilityViewController.swift in Sources */,
3F2AF8352D573B6200635545 /* ChatCell.swift in Sources */,
3F2AF82F2D57125D00635545 /* ChatViewController.swift in Sources */,
+ 3F8167202D5DC65B002936D5 /* IBSpeaker.swift in Sources */,
8A34B9272BA2AB66000F66BD /* ConnectViewController.swift in Sources */,
18EF697A2C47A86D00AA0A36 /* IBTools.swift in Sources */,
8A1DFF562B9EB4F800B735FF /* ViewController.swift in Sources */,
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
- uuid = "614B2AEC-7047-4DF3-9E22-65096DA81F63"
+ uuid = "E8730F99-9BCA-41EE-90B8-54A71862AC30"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
- filePath = "IOTBluetooth/IBAiManager.swift"
+ filePath = "Example/IBSpeaker.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
- startingLineNumber = "128"
- endingLineNumber = "128"
- landmarkName = "receiveAiData(sbcData:)"
+ startingLineNumber = "45"
+ endingLineNumber = "45"
+ landmarkName = "playNextChunk()"
landmarkType = "7">
- <Locations>
- <Location
- uuid = "614B2AEC-7047-4DF3-9E22-65096DA81F63 - 3e429f5031b96ab5"
- shouldBeEnabled = "Yes"
- ignoreCount = "0"
- continueAfterRunningActions = "No"
- symbolName = "closure #1 (Swift.Optional<Swift.String>, Swift.Optional<MixSdk.MSError>) -> () in IOTBluetooth.IBAiManager.receiveAiData(sbcData: Foundation.Data) -> ()"
- moduleName = "Example.debug.dylib"
- usesParentBreakpointCondition = "Yes"
- urlString = "file:///Users/yaozuopan/Documents/code/IOTBluetooth-iOS/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAiManager.swift"
- startingColumnNumber = "9223372036854775807"
- endingColumnNumber = "9223372036854775807"
- startingLineNumber = "128"
- endingLineNumber = "128">
- </Location>
- <Location
- uuid = "614B2AEC-7047-4DF3-9E22-65096DA81F63 - 3e429f5031b96ab5"
- shouldBeEnabled = "Yes"
- ignoreCount = "0"
- continueAfterRunningActions = "No"
- symbolName = "closure #1 (Swift.Optional<Swift.String>, Swift.Optional<MixSdk.MSError>) -> () in IOTBluetooth.IBAiManager.receiveAiData(sbcData: Foundation.Data) -> ()"
- moduleName = "Example.debug.dylib"
- usesParentBreakpointCondition = "Yes"
- urlString = "file:///Users/yaozuopan/Documents/code/IOTBluetooth-iOS/IOTBluetooth/IOTBluetooth/IOTBluetooth/IBAiManager.swift"
- startingColumnNumber = "9223372036854775807"
- endingColumnNumber = "9223372036854775807"
- startingLineNumber = "128"
- endingLineNumber = "128">
- </Location>
- </Locations>
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
public var productKey:String = ""
/// chatGptKey
public var chatGptKey:String = ""
+ /// 使用大模型
+ public var useMode:((_ result:MSPlatform) -> Void)?
+ /// 使用大模型
+ public var aiResponse:((_ result:IBAiResponse) -> Void)?
- public var aiResponse:((_ response:IBAiResponse) -> Void)?
+ var chatModeMap:[Int:MSPlatform] = [0:.yiyan,
+ 1:.chatGPT,
+ 2:.kimi,
+ 3:.doubao,
+ 4:.aliyun,
+ 5:.xunfei,
+ ]
var responseId = ""
var reply = ""
+ var isLogin:Bool{
+ return MixSdk.shared.auth.authorized
+ }
+
init(){
MixSdk.shared.isDebug = true
}
func aiValid(_ data:Data){
+ if !isLogin{
+ Logger.log("ai未登录!!!")
+ login()
+ }
+
let result = IBGeneralParser.dataPacket(data)
let code = result.int
- let desc = ["文心一言","ChatGPT","KIMI","豆包","通义千问","讯飞星火"]
- Logger.log("询问模型是否可用:",code >= desc.count ? "未知": desc[code])
+ let platform = chatModeMap[code] ?? .doubao
+ useMode?(platform)
let model = IBAiValid()
- model.isValid = true
+ model.isValid = isLogin
if code == 1 {
model.key = chatGptKey
}
.response { result in
}
-
}
func aiRequest(_ data:Data){
let result = IBGeneralParser.dataPacket(data)
let code = result.int
- let desc = ["文心一言","ChatGPT","KIMI","豆包","通义千问","讯飞星火"]
- Logger.log("使用模型:",code >= desc.count ? "未知": desc[code])
IBSender()
.ability(IBAbility.aiReady)
.parameters([.value(1)])
}
- var platform = MSPlatform.doubao
+ let platform = chatModeMap[code] ?? .doubao
var key = ""
- if code == 0 {
- platform = .yiyan
- }
- else if code == 1 {
- platform = .chatGPT
+ if code == 1 {
key = chatGptKey
if chatGptKey.isEmpty {
Logger.log("请先设置chatGpt Key!")
// let gptModel = "gpt-4-turbo"
// MixSdk.shared.chatGpt.changeModel(model:gptModel )
}
- else if code == 2 {
- platform = .kimi
- }
- else if code == 3 {
- platform = .doubao
- }
- else if code == 4 {
- platform = .aliyun
- }
- else if code == 5 {
- platform = .xunfei
- }
+ Logger.log("使用模型:",platform.title)
MixSdk.shared.setPlatform(platform,key: key)
+ useMode?(platform)
}
func fectchAiAudio(){
IBSender()
}
}
-
-
extension IBAiManager{
+ func login(){
+ let mac = IBBluetooth.shared.currentConnected.macAddress.split(separator: ":").joined().uppercased()
+ login(deviceId:mac ) { token, error in
+
+ }
+ }
/// 登录
/// - Parameters:
/// - deviceId: 设备ID
// Created by incar on 2024/7/15.
//
-import Foundation
import CoreBluetooth
-
+import Foundation
@objc protocol IBReceiveManagerDelegate {
- func receiveManagerDidReceiveFullData(characteristic: CBCharacteristic,_ data:Data)
+ func receiveManagerDidReceiveFullData(
+ characteristic: CBCharacteristic, _ data: Data)
}
-class IBReceiveManager{
- var delegate:IBReceiveManagerDelegate?
+class IBReceiveManager {
+ var delegate: IBReceiveManagerDelegate?
static let shared = IBReceiveManager()
-
+
var headData = Data()
var lastData = Data()
var totalData = Data()
-
-
- func receice(characteristic: CBCharacteristic){
-
-
+
+ func receice(characteristic: CBCharacteristic) {
+
guard let data = characteristic.value else { return }
- if(data.count == 2 && data.int == 0){
-// Logger.log("收到0000指令异常")
+ if data.count == 2 && data.int == 0 {
+ // Logger.log("收到0000指令异常")
return
}
-
- if data.count >= 18,data[0] == 0x72{
+
+ if data.count >= 18, data[0] == 0x72 {
let mark = characteristic.isNotifying ? "Notify Read" : "Read"
let uuid = characteristic.uuid.uuidString
let pkgTotal = data[3...6].int
let dataTotal = data[7...10].int
let pkgIndex = data[11...14].int
let pkgLen = data[15...16].int
-
- Logger.log(mark, uuid, "->", "pkgTotal = \(pkgTotal),dataTotal = \(dataTotal),pkgIndex = \(pkgIndex),pkgLen = \(pkgLen)",data.joined(" "))
-
-// if data[1] == 0x01,data[2] == 0x34{
-// //ai语音
- if pkgIndex == 1{
- headData = data.prefix(17)
- lastData = data.suffix(1)
- }
- IBBluetooth.shared.deinitTimer()
- IBBluetooth.shared.startTimer()
- totalData = totalData + data.suffix(from: 17).prefix(pkgLen)
- if pkgTotal == pkgIndex{
- Logger.log("total = \(dataTotal),receive = \(totalData.count)")
- let fullData = headData + totalData + lastData
- didGetFullData(characteristic: characteristic, data: fullData)
- }
-// }
-// else{
-// didGetFullData(characteristic: characteristic, data: data)
-// }
- }
- else{
- Logger.log( data.joined(" "))
+
+ Logger.log(
+ mark, uuid, "->",
+ "pkgTotal = \(pkgTotal),dataTotal = \(dataTotal),pkgIndex = \(pkgIndex),pkgLen = \(pkgLen)",
+ data.joined(" "))
+ if pkgIndex == 1 {
+ headData = data.prefix(17)
+ lastData = data.suffix(1)
+ }
+ totalData = totalData + data.suffix(from: 17).prefix(pkgLen)
+ if pkgTotal == pkgIndex {
+ Logger.log("total = \(dataTotal),receive = \(totalData.count)")
+ let fullData = headData + totalData + lastData
+ didGetFullData(characteristic: characteristic, data: fullData)
+ }
+ } else {
+ Logger.log(data.joined(" "))
didGetFullData(characteristic: characteristic, data: data)
}
}
-
- func didGetFullData(characteristic: CBCharacteristic,data:Data){
-// Logger.log( data.joined(" "))
-
- delegate?.receiveManagerDidReceiveFullData(characteristic: characteristic, data)
+
+ func didGetFullData(characteristic: CBCharacteristic, data: Data) {
+ // Logger.log( data.joined(" "))
+ delegate?.receiveManagerDidReceiveFullData(
+ characteristic: characteristic, data)
totalData = Data()
-
- if characteristic.uuid.uuidString == readCharacteristicUUID {
+
+ if characteristic.uuid.uuidString == readCharacteristicUUID {
let info = IBWatchInfo(data)
IBBluetooth.shared.currentConnected.battery = info.battery
IBBluetooth.shared.currentConnected.macAddress = info.macAddress
IBBluetooth.shared.currentConnected.customerID = info.customerID
sendDeviceInfo()
}
-
+
if IBNotify.handle(data) {
-// Logger.log("通知消息")
+ Logger.log("通知消息")
return
}
}
-
- func sendDeviceInfo(){
- let mac = UserDefaults.standard.object(forKey: "IOTBletooth.mac") as? [Int] ?? []
+
+ func sendDeviceInfo() {
+ let mac =
+ UserDefaults.standard.object(forKey: "IOTBletooth.mac") as? [Int]
+ ?? []
let model = IBPhoneInfo()
model.mac = mac
IBSender()
.parameters([.sendPhone(model)])
.response { result in
Logger.log(result)
- if result.code != .success{
+ if result.code != .success {
//通知连接失败
IBManager.completeHandler?(false)
IBManager.completeHandler = nil
return
}
- guard let value = result.value as? Int else {return}
+ guard let value = result.value as? Int else { return }
if value == 2 {
//通知连接成功
IBManager.completeHandler?(true)
IBManager.completeHandler = nil
- }
- else if value == 1{
+ } else if value == 1 {
//等待手表同意连接
Logger.log("等待手表同意连接")
- }
- else if value == 3{
+ } else if value == 3 {
//手表已与其他手机绑定
IBManager.completeHandler?(false)
IBManager.completeHandler = nil
}
}
}
-
public class IBSender {
public init() {
Self.all.append(self)
+ startTimer()
}
var ability: IBAbility!
var parameters: [IBParameter] = []
var completion: IBCompletionHandler!
+ var timer: Timer?
+ func startTimer() {
+ deinitTimer()
+
+ self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false, block: { [unowned self] _ in
+ deinitTimer()
+ self.completion?(.response.code(.timeout))
+ self.remove()
+ })
+ }
+
+ func deinitTimer() {
+ guard let _ = self.timer else { return }
+ self.timer?.invalidate()
+ self.timer = nil
+ Logger.log("销毁定时器")
+ }
+
var dataPacket: [[UInt8]] {
var totalData: [UInt8] = []
if isValue(ability.checker) {
}
deinit {
-// Logger.log("deinit IBSender")
+ Logger.log("deinit IBSender")
}
}
/// 从缓存实例集合中移除该实例
func remove() {
+ deinitTimer()
Self.all.removeAll { $0 === self }
}
+
+ static func answer(_ data: Data) -> IBSender? {
+ for item in Self.all {
+ if item.isAnswer(data) {
+ return item
+ }
+ }
+ return nil
+ }
}
/// 当前连接设备
var currentConnected: IBPeripheral!
- /// 最后消息发送实例
- private weak var lastSender: IBSender?
+// /// 最后消息发送实例
+// private weak var lastSender: IBSender?
/// 文件发送实例
internal weak var fileSender: IBFileSender?
// Logger.log("手机未连上蓝牙设备!")
// return
// }
-
- if let _ = lastSender {
- sender.completion(.response.code(.failure).message("有命令正在执行中..."))
- sender.remove()
- Logger.log("有命令正在执行中...")
- if Self.timer == nil{
- startTimer()
- }
- return
- }
-
- startTimer()
- lastSender = sender
-
for dataPacket in sender.dataPacket {
// Thread.sleep(forTimeInterval:0.02)
// IBNotify.post(.connectedDeviceDidChange)
Logger.log("已连接", currentConnected.name)
IBNotify.post(.connectStateDidChange, object: 1)
-
- IBAiManager.shared.login(deviceId: "28110687C456") { token, error in
-
- }
-// IBAiManager.shared.login(deviceId: currentConnected.macAddress) { token, error in
-//
-// }
+
+ IBAiManager.shared.login()
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
Logger.log("断开连接")
deinitTimer()
- if let sender = lastSender{
- lastSender = nil
- sender.completion?(.response.code(.failure))
- sender.remove()
- }
-
self.fileSender?.lastData?.fileWriteHander?(.response.code(.failure).message("蓝牙设备断开连接"))
IBManager.completeHandler?(false)
else{
Logger.log(characteristic.uuid.uuidString, "write success")
}
- if let sender = lastSender,sender.ability.type == .ai{
- lastSender = nil
- sender.completion?(.response.code(.success))
- sender.remove()
- return
- }
+// if let sender = lastSender,sender.ability.type == .ai{
+// lastSender = nil
+// sender.completion?(.response.code(.success))
+// sender.remove()
+// return
+// }
if let hander = self.fileSender?.lastData?.fileWriteHander{
hander(error == nil ? .response.code(.success):.response.code(.failure))
}
extension IBBluetooth:IBReceiveManagerDelegate {
func receiveManagerDidReceiveFullData(characteristic: CBCharacteristic,_ data: Data) {
- if let sender = lastSender,sender.isAnswer(data) == true {
- deinitTimer()
- lastSender = nil
+ if let sender = IBSender.answer(data) {
sender.completion?(sender.parse(data))
sender.remove()
return
extension IBBluetooth {
static var timer: Timer?
- func startTimer() {
+ private func startTimer() {
deinitTimer()
Self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false, block: { [unowned self] _ in
deinitTimer()
- if let sender = lastSender{
- lastSender = nil
- sender.completion?(.response.code(.timeout))
- sender.remove()
- }
-
self.fileSender?.lastData?.fileWriteHander?(.response.code(.timeout))
})
// Logger.log("创建定时器")
}
- func deinitTimer() {
+ private func deinitTimer() {
guard let _ = Self.timer else { return }
Self.timer?.invalidate()
Self.timer = nil