Skip to content

Commit

Permalink
added UDPReceiver class being used to query one inverter and waiting …
Browse files Browse the repository at this point in the history
…for the result
  • Loading branch information
jollyjinx committed Jul 5, 2023
1 parent 1410f63 commit b3f3fda
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 21 deletions.
6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@ let package = Package(
.target(
name: "sma2mqttLibrary",
dependencies: [
"CNative",
.product(name: "BinaryCoder", package: "BinaryCoder"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "MQTTNIO", package: "mqtt-nio"),
.product(name: "JLog", package: "JLog"),

],
resources: [
.copy("Obis/Resources/obisdefinition.json"),
Expand All @@ -47,6 +49,10 @@ let package = Package(
.copy("SMAPacket/Resources/SMANetPacketDefinitions.json"),
]
),
.target(
name: "CNative",
dependencies: []
),
.testTarget(
name: "sma2mqttTests",
dependencies: [
Expand Down
7 changes: 7 additions & 0 deletions Sources/CNative/include/select.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <sys/select.h>

__BEGIN_DECLS

extern void SWIFT_FD_SET(int d, fd_set *set);

__END_DECLS
5 changes: 5 additions & 0 deletions Sources/CNative/select.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "select.h"

void SWIFT_FD_SET(int d, fd_set *set) {
FD_SET(d, &(*set));
}
7 changes: 2 additions & 5 deletions Sources/sma2mqtt/sma2mqtt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,10 @@ extension JLog.Level: ExpressibleByArgument {}
"ac-side/grid-measurements/power:1",
"ac-side/measured-values/daily-yield:30",

"immediate/feedin:1",
"immediate/usage:1",

"battery/state-of-charge:20",
"battery/battery/temperature:30",
"battery/battery/battery-charge/battery-charge:20",
// "*:0", // all once
"battery/battery-charge/battery-charge:20",
// "*:600", // all once
]
func run() async throws
{
Expand Down
4 changes: 3 additions & 1 deletion Sources/sma2mqttLibrary/DataObjects/PublishedValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ public struct PublishedValue: Encodable

public func encode(to encoder: Encoder) throws
{
enum CodingKeys: String, CodingKey { case unit, value, scale, id, prio, write, event }
enum CodingKeys: String, CodingKey { case unit, value, scale, id, prio, write, event,date }
var container = encoder.container(keyedBy: CodingKeys.self)

let objectDefinition = tagTranslator.smaObjectDefinitions[objectID]
let compacted = values.compactMap { $0 }
try container.encode(objectID, forKey: .id)
// try container.encode(Date(), forKey: .date)

switch compacted.first
{
Expand Down
52 changes: 40 additions & 12 deletions Sources/sma2mqttLibrary/SMADevice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public actor SMADevice
struct QueryObject: Hashable
{
let objectid: String
let path:String
let interval: Int
}

Expand All @@ -38,6 +39,8 @@ public actor SMADevice

var objectsToQueryContinously = [String: QueryObject]()
var objectsToQueryNext = [QueryElement]()
var lastRequestSentDate = Date.distantPast
let minimumRequestInterval = 1.0 / 20.0 // 1 / maximumRequestsPerSecond

let requestAllObjects: Bool

Expand All @@ -47,6 +50,7 @@ public actor SMADevice
let httpClient: HTTPClient
private var sessionid: String?

let udpReceiver: UDPReceiver
let udpEmitter: UDPEmitter?
var udpSystemId: UInt16 = 0xFFFF
var udpSerial: UInt32 = 0xFFFF_FFFF
Expand All @@ -60,7 +64,7 @@ public actor SMADevice
private var refreshTask: Task<Void, Error>?
private var tagTranslator = SMATagTranslator.shared

public init(address: String, userright: UserRight = .user, password: String = "00000", publisher: SMAPublisher? = nil, refreshInterval: Int = 30, interestingPaths: [String: Int] = [:], requestAllObjects: Bool = false, udpEmitter: UDPEmitter? = nil) async throws
public init(address: String, userright: UserRight = .user, password: String = "00000", publisher: SMAPublisher? = nil, refreshInterval: Int = 30, interestingPaths: [String: Int] = [:], requestAllObjects: Bool = false, bindAddress:String = "0.0.0.0", udpEmitter: UDPEmitter? = nil) async throws
{
self.address = address
self.userright = userright
Expand All @@ -72,6 +76,10 @@ public actor SMADevice
self.interestingPaths = interestingPaths
self.requestAllObjects = requestAllObjects
self.udpEmitter = udpEmitter

self.udpReceiver = try UDPReceiver(bindAddress: bindAddress, listenPort: 0)


name = address
hasDeviceName = false

Expand Down Expand Up @@ -114,7 +122,7 @@ public extension SMADevice
return nil
}

JLog.trace("received udp packet:\(data.hexDump)")
JLog.debug("\(address):received udp packet:\(data.hexDump)")

let smaPacket: SMAPacket

Expand Down Expand Up @@ -199,7 +207,7 @@ public extension SMADevice
{
if obisvalue.mqtt != .invisible
{
try? await publisher?.publish(to: name + "/" + obisvalue.topic, payload: obisvalue.json, qos: .atLeastOnce, retain: obisvalue.mqtt == .retained)
try? await publisher?.publish(to: name + "/" + obisvalue.topic, payload: obisvalue.json, qos: .atMostOnce, retain: obisvalue.mqtt == .retained)
}
}

Expand Down Expand Up @@ -236,12 +244,16 @@ public extension SMADevice
{
let objectToQuery = try getNextRequest()

let timeToWait = objectToQuery.nextReadDate.timeIntervalSinceNow
let nextReadDate = max( objectToQuery.nextReadDate , lastRequestSentDate + minimumRequestInterval )

let timeToWait = nextReadDate.timeIntervalSinceNow

if timeToWait > 0
{
try await Task.sleep(for: .seconds(timeToWait))
}
lastRequestSentDate = Date()

try await udpQueryObject(objectID: objectToQuery.objectid)
}

Expand All @@ -267,7 +279,9 @@ public extension SMADevice
}

JLog.trace("\(address): sending udp packet:\(packetToSend)")
await udpEmitter?.sendPacket(data: [UInt8](packetToSend.hexStringToData()), address: address, port: 9522)
let packet = try await udpReceiver.sendReceivePacket(data: [UInt8](packetToSend.hexStringToData()), address: address, port: 9522, receiveTimeout: 1.0)

let _ = await receivedUDPData(packet.data)
}
}

Expand Down Expand Up @@ -335,12 +349,13 @@ extension SMADevice
name = deviceName
}

try await getInformationDictionary(atPath: "/dyn/getDashValues.json")
try await getInformationDictionary(atPath: "/dyn/getAllOnlValues.json")

for objectid in tagTranslator.smaObjectDefinitions.keys
{
addObjectToQueryContinouslyIfNeeded(objectid: objectid)
}
// try await getInformationDictionary(atPath: "/dyn/getDashValues.json")
// try await getInformationDictionary(atPath: "/dyn/getAllOnlValues.json")

try? await logout()
}
Expand Down Expand Up @@ -436,21 +451,34 @@ extension SMADevice
return nil
}

func objectIdIsInteresting(_ objectid: String) -> Int?
func objectIdIsInteresting(_ objectid: String) -> (path:String,interval:Int?)
{
let path = "/" + (tagTranslator.objectsAndPaths[objectid]?.path ?? "unkown-id-\(objectid)")

return pathIsInteresting(path)
let interval = pathIsInteresting(path)

JLog.debug("\(address):\(objectid) \(path) interval:\( interval ?? -1 )")

return (path:path,interval:interval)
}

@discardableResult
func addObjectToQueryContinouslyIfNeeded(objectid: String) -> Bool
{
JLog.trace("\(address):working on objectId:\(objectid)")

if let interval = objectIdIsInteresting(objectid)
let (path,interval) = objectIdIsInteresting(objectid)

if let interval
{
let queryObject = objectsToQueryContinously[objectid] ?? QueryObject(objectid: objectid, interval: interval)
if let inuse = objectsToQueryContinously.values.first(where:{ $0.path == path })
{
JLog.notice("\(address): Won't query objectid:\(objectid) - object with same path:\(inuse.objectid) path:\(inuse.path)")
return false
}
JLog.debug("\(address): adding to objectsToQueryContinously objectid:\(objectid) path:\(path) interval:\(interval)")

let queryObject = objectsToQueryContinously[objectid] ?? QueryObject(objectid: objectid, path:path, interval: interval)

if interval <= queryObject.interval
{
Expand Down Expand Up @@ -498,7 +526,7 @@ extension SMADevice
if hasDeviceName,
addObjectToQueryContinouslyIfNeeded(objectid: objectId.key)
{
try await publisher?.publish(to: mqttPath, payload: singleValue.json, qos: .atMostOnce, retain: true)
try await publisher?.publish(to: mqttPath, payload: singleValue.json, qos: .atMostOnce, retain: false)
}
}
catch
Expand Down
2 changes: 1 addition & 1 deletion Sources/sma2mqttLibrary/SMALighthouse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public actor SMALighthouse

JLog.debug("Got new SMA Device with remoteAddress:\(remoteAddress)")

let task = Task { try await SMADevice(address: remoteAddress, userright: .user, password: password, publisher: mqttPublisher, interestingPaths: interestingPaths, udpEmitter: mcastReceiver) }
let task = Task { try await SMADevice(address: remoteAddress, userright: .user, password: password, publisher: mqttPublisher, interestingPaths: interestingPaths, bindAddress: bindAddress, udpEmitter: mcastReceiver) }
smaDeviceCache[remoteAddress] = .inProgress(task)

do
Expand Down
6 changes: 4 additions & 2 deletions Sources/sma2mqttLibrary/SMAPacket/SMAPacketGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ extension SMAPacketGenerator
return try generateCommandPacket(packetcounter: packetcounter, command: command, dstSystemId: dstSystemId, dstSerial: dstSerial)
}

private static let localhostname = Host.current().names.filter({ $0 != "localhost" }).sorted(by: { $0.count < $1.count }).first ?? "localhost"

static func generateCommandPacket(packetcounter: Int, command: String, dstSystemId: UInt16 = 0xFFFF, dstSerial: UInt32 = 0xFFFF_FFFF) throws -> String
{
let jobid = String(format: "%02x", 1)
Expand All @@ -30,7 +32,7 @@ extension SMAPacketGenerator
let dstSysidString = String(format: "%02x%02x", dstSystemId & 0xFF, (dstSystemId & 0xFF00) >> 8)
let dstSerialString = String(format: "%02x%02x%02x%02x", dstSerial & 0xFF, (dstSerial >> 8) & 0xFF, (dstSerial >> 16) & 0xFF, (dstSerial >> 24) & 0xFF)

let ownid = String(format: "%04x", generateRandomNumber())
let ownidString = String(format: "%04x", Self.localhostname.hashValue & 0xFFFF )
let header = """
534d 4100
0004 02a0 0000 0001
Expand All @@ -41,7 +43,7 @@ extension SMAPacketGenerator
A0
\(dstSysidString) \(dstSerialString) 00
01
1234 \(ownid) 4321 00
1234 \(ownidString) 4321 00
\(jobid)
\(result)
\(remainingpackets)
Expand Down
1 change: 1 addition & 0 deletions Sources/sma2mqttLibrary/Tools/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public extension UInt32 { var ipv4String: String { "\(self >> 24).\(self >> 16 &

#if os(Linux)
public let NSEC_PER_SEC: Int64 = 1_000_000_000
public let USEC_PER_SEC: Int64 = 1_000_000
#endif

public extension Data
Expand Down
1 change: 1 addition & 0 deletions Sources/sma2mqttLibrary/Tools/MQTTPublisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public actor MQTTPublisher: SMAPublisher
{
_ = self.mqttClient.connect()
}
JLog.debug("publish:\(topic) payload:\(payload)")
_ = self.mqttClient.publish(to: topic, payload: byteBuffer, qos: qos, retain: retain)
}
}
Expand Down
Loading

0 comments on commit b3f3fda

Please sign in to comment.