Skip to content

Commit

Permalink
Add MQTT client to exchange events with external services like the Sh…
Browse files Browse the repository at this point in the history
…ortcuts-app
  • Loading branch information
TheMisfit68 committed Feb 17, 2024
1 parent b445697 commit 04fc7f2
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 120 deletions.
226 changes: 124 additions & 102 deletions Accessory Delegates/LeafAccessoryDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,109 +11,131 @@ import HAP
import JVSwift
import LeafDriver
import OSLog
import JVNetworking

class LeafAccessoryDelegate:LeafDriver, AccessoryDelegate, AccessorySource{

var name: String{
return String(localized:"Electric Car")
}

typealias AccessorySubclass = Accessory.ElectricCar

var characteristicChanged: Bool = false

let lowChargeLimit = 15

var getBatteryStatusTimer:Timer!

public override init(leafProtocol: LeafProtocol) {

super.init(leafProtocol: leafProtocol)

getBatteryStatusTimer = Timer.scheduledTimer(withTimeInterval: 1800, repeats: true) { timer in super.batteryChecker.getNewBatteryStatus()}
getBatteryStatusTimer.tolerance = getBatteryStatusTimer.timeInterval/10.0 // Give the processor some slack with a 10% tolerance on the timeInterval

}

var startCharging:Bool = false{
didSet{
if startCharging{
self.charger.startCharging()
}
}
}

var setAirco:Bool = false{
didSet{
if setAirco{
self.acController.setAirCo(to: .on)
}else{
self.acController.setAirCo(to: .off)

}
}
}

func handleCharacteristicChange<T>(accessory: AccessorySubclass, service: Service, characteristic: GenericCharacteristic<T>, to value: T?) where T : CharacteristicValueType {
let logger = Logger(subsystem: "be.oneclick.HAPiNest", category: "LeafAccessoryDelegate")

switch service {
case accessory.chargerService:

switch characteristic.type{
case CharacteristicType.powerState:

startCharging = characteristic.value as! Bool

default:
logger.warning( "Unhandled characteristic change for accessory \(accessory.info.name.value ?? "")")
}

// case accessory.aircoService:
//
// switch characteristic.type{
// case CharacteristicType.powerState:
//
// setAirco = characteristic.value as! Bool
//
// default:
// logger.warning( "Unhandled characteristic change for accessory \(accessory.info.name.value ?? "")")
// }

default:
logger.warning( "Unhandled characteristic change for accessory \(accessory.info.name.value ?? "")")
}



}

var hardwareFeedbackChanged:Bool = false


func pollCycle() {

if let percentageRemaining = batteryChecker.percentageRemaining, let _ = batteryChecker.rangeRemaining{

accessory.primaryService.batteryLevel?.value = UInt8(percentageRemaining)
if (percentageRemaining >= lowChargeLimit){
accessory.primaryService.statusLowBattery.value = .batteryNormal
}else{
accessory.primaryService.statusLowBattery.value = .batteryLow
}


}

if let chargingState = batteryChecker.chargingState{
accessory.primaryService.chargingState?.value = .charging
}

}

private func sendSMS(phoneNumber:String, content:String){

}

let logger = Logger(subsystem: "be.oneclick.HAPiNest", category: "LeafAccessoryDelegate")

var name: String{
return String(localized:"Electric Car")
}

typealias AccessorySubclass = Accessory.ElectricCar

var characteristicChanged: Bool = false

let lowChargeLimit = 15


public override init(leafProtocol: LeafProtocol) {

super.init(leafProtocol: leafProtocol)

}

var startCharging:Bool = false{
didSet{
if startCharging{
self.charger.startCharging()
}
}
}

var setAirco:Bool = false{
didSet{
if setAirco{
self.acController.setAirCo(to: .on)
}else{
self.acController.setAirCo(to: .off)

}
}
}

var mqttMessage:LeafMQTTMessage? = nil{
didSet{
guard mqttMessage != nil else {return}
if mqttMessage != oldValue{
MQTTClient.shared.publish(topic: "HomeKit/ExternalEvents/FromServer/NissanLeaf", type: mqttMessage!, retained: true)
}
}
}

func handleCharacteristicChange<T>(accessory: AccessorySubclass, service: Service, characteristic: GenericCharacteristic<T>, to value: T?) where T : CharacteristicValueType {

switch service {
case accessory.chargerService:

switch characteristic.type{
case CharacteristicType.powerState:

startCharging = characteristic.value as! Bool

default:
logger.warning( "Unhandled characteristic change for accessory \(accessory.info.name.value ?? "")")
}

case accessory.aircoService:

switch characteristic.type{
case CharacteristicType.powerState:

setAirco = characteristic.value as! Bool

default:
logger.warning( "Unhandled characteristic change for accessory \(accessory.info.name.value ?? "")")
}

default:
logger.warning( "Unhandled characteristic change for accessory \(accessory.info.name.value ?? "")")
}



}


var hardwareFeedbackChanged:Bool = false


func pollCycle() {

if let percentageRemaining = batteryChecker.percentageRemaining,
let rangeRemaining = batteryChecker.rangeRemaining,
let isConnected = batteryChecker.connectionStatus,
let isCharging = batteryChecker.chargingStatus
{

// Send an MQTT-payload
let timeStamp = batteryChecker.updateTimeStamp!
self.mqttMessage = LeafMQTTMessage(timeStamp: timeStamp.localDateTimeString(), percentage: percentageRemaining, range: rangeRemaining, isConnected: isConnected, isCharging: isCharging)

// Chang the state of the accessory
accessory.primaryService.batteryLevel?.value = UInt8(percentageRemaining)
if (percentageRemaining >= lowChargeLimit){
accessory.primaryService.statusLowBattery.value = .batteryNormal
}else{
accessory.primaryService.statusLowBattery.value = .batteryLow
}
if isConnected{
accessory.primaryService.chargingState?.value = isCharging ? .charging : .notCharging
}else{
accessory.primaryService.chargingState?.value = .notChargeable
}

}


}

}

public struct LeafMQTTMessage:Codable, Equatable{

let timeStamp: String
let percentage: Int
let range: Int
let isConnected: Bool
let isCharging: Bool

}
11 changes: 7 additions & 4 deletions HAPiNest/HAPiNestApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct HAPiNestApp: App {

let homekitServer:HomeKitServer = HomeKitServer.shared

let mqttCLient = MQTTClient()
let mqttCLient = MQTTClient.shared

let plc:SoftPLC = SoftPLC(hardwareConfig:MainConfiguration.PLC.HardwareConfig, ioList: MainConfiguration.PLC.IOList, simulator:ModbusSimulator())
let cyclicPoller:CyclicPoller = CyclicPoller(timeInterval: 1.0)
Expand All @@ -47,7 +47,6 @@ struct HAPiNestApp: App {
accessories: MainConfiguration.Accessories.map{accessory, delegate in return accessory},
configfileName: configFile
)
mqttCLient.connect()

plc.plcObjects = MainConfiguration.PLC.PLCobjects
#if DEBUG
Expand All @@ -73,8 +72,12 @@ struct HAPiNestApp: App {
)
.padding()
.background(Color.Neumorphic.main)
.onAppear(perform: {
})
.onAppear(perform:
{ mqttCLient.connect() }
)
.onDisappear(perform:
{ mqttCLient.disconnect() }
)
}
.onChange(of: scenePhase) {
let logger = Logger(subsystem: "be.oneclick.HAPiNest", category:.lifeCycle)
Expand Down
17 changes: 17 additions & 0 deletions HAPiNest/MQTTClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// MQTTClient.swift
// HAPiNest
//
// Created by Jan Verrept on 29/01/2024.
// Copyright © 2024 Jan Verrept. All rights reserved.
//

import Foundation
import JVSwiftCore
import JVNetworking

extension MQTTClient:Singleton{

public static var shared: MQTTClient = MQTTClient(autoSubscribeTo: ["HomeKit/ExternalEvents/ToServer"], autoPublishTo: ["HomeKit/ExternalEvents/FromServer"])

}
10 changes: 9 additions & 1 deletion HAPiNest/PreferencesWindow/PreferencesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
//

import SwiftUI
import LeafDriver
import WeatherKit
import JVWeather
import JVNetworking

// Struct specifier needed because compiler can't keep module LeafDriver apart from the Class with the same name
import struct LeafDriver.LeafSettingsView


struct PreferencesView: View {

Expand All @@ -19,6 +23,10 @@ struct PreferencesView: View {
.tabItem {
Label("General", systemImage: "gearshape")
}
JVNetworking.MQTTClientSettingsView()
.tabItem {
Label("MQTT", systemImage: "info.bubble")
}
TizenSettingsView()
.tabItem {
Label("Samsung Tizen", systemImage: "tv")
Expand Down
29 changes: 16 additions & 13 deletions MainConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,22 @@ struct MainConfiguration{
]) : TizenAccessoryDelegate(tvName:String(localized:"TV Upstairs", table:"AccessoryNames"), macAddress: "7C:64:56:80:4E:90", ipAddress: "192.168.0.116", port: 8002, deviceName: "HAPiNestServer"),

// MARK: - Other
// Accessory.ElectricCar(info: Service.Info(name: String(localized:"Electric Car", table:"AccessoryNames"), serialNumber: "30003", manufacturer: "Nissan")) : LeafAccessoryDelegate(leafProtocol: LeafProtocolV2())
//


// (Accessory.init(info: Service.Info(name:String(localized:"Zonnepanelen", table:"AccessoryNames"), serialNumber: "30001", manufacturer: "SMA"),
// type: .other,
// services: [
// // TODO: - Insert a Service.EnergyMeter and Service.PowerMeter,
// // once it gets supported by Apples 'Home'-App
// .SwitchBase(characteristics:[.name("Opbrengst opvragen")] ),
// ] ),
// YASDIDriver.InstallDrivers().first!
// )
Accessory.ElectricCar(info: Service.Info(name: String(localized:"Electric Car", table:"AccessoryNames"), serialNumber: "30003", manufacturer: "Nissan")) : LeafAccessoryDelegate(leafProtocol: LeafProtocolV2())



// (Accessory.init(info: Service.Info(name:String(localized:"Zonnepanelen", table:"AccessoryNames"), serialNumber: "30001", manufacturer: "SMA"),
// type: .other,
// services: [
// // TODO: - Insert a Service.EnergyMeter and Service.PowerMeter,
// // once it gets supported by Apples 'Home'-App
// .SwitchBase(characteristics:[.name("Opbrengst opvragen")] ),
// ] ),
// YASDIDriver.InstallDrivers().first!
// )



]

}
Expand Down

0 comments on commit 04fc7f2

Please sign in to comment.