forked from fritob/Camper-Monitor
CamperMonitor (Haupt-Repo) und VanAligneiOS (aus dem gemergten solar-integration-Branch) liefen unter zwei verschiedenen internen Namen, obwohl die App nach aussen längst einheitlich "VanControl Pro" heisst. Jetzt durchgängig VanControl: - Ordner: CamperMonitor/, CamperMonitorWatch/, CamperMonitorComplication/, VanAligneiOSWidget/ → VanControl/, VanControlWatch/, VanControlComplication/, VanControlWidget/ - Xcode-Projekt: CamperMonitor.xcodeproj → VanControl.xcodeproj, alle Targets/Schemes/Produktnamen entsprechend umbenannt - Bundle-Identifier auf Wunsch mitgeändert: de.s0.fototeddy.VanControl* (App noch nicht veröffentlicht); dabei auch die WKCompanionAppBundleIdentifier-Werte korrigiert, die noch das alte de.fritob-Präfix statt des tatsächlichen de.s0.fototeddy-Präfixes trugen - Swift-Dateien/Typen: CamperMonitorApp → VanControlApp, VanAligneiOSWidget* → VanControlWidget* - Config/*-Info.plist umbenannt, README.md/Tools/README.md/run-tests.sh auf die neuen Pfade angepasst Bewusst unverändert: firmware/vanalign und alle Bezüge auf "VanAlign" als Namen der Neigungsmesser-Hardware (eigenständiges Produkt, kein App-Name) sowie der komplette Android/-Ordner. Build (App, Watch, Debug) und Protokoll-Testlauf grün. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
62 lines
2.3 KiB
Swift
62 lines
2.3 KiB
Swift
import Foundation
|
||
|
||
/// Liest Felder beliebiger Bitbreite aus einem Byte-Array.
|
||
///
|
||
/// Victron packt die Felder seiner Werbedaten little-endian und bitweise ohne
|
||
/// Byte-Ausrichtung: das erste Feld beginnt am niederwertigsten Bit von Byte 0,
|
||
/// jedes weitere schliesst direkt an.
|
||
struct BitReader {
|
||
private let bytes: [UInt8]
|
||
private var bitOffset = 0
|
||
|
||
init(_ bytes: [UInt8]) { self.bytes = bytes }
|
||
|
||
var bitsRemaining: Int { bytes.count * 8 - bitOffset }
|
||
|
||
/// Liest `width` Bits als vorzeichenlose Zahl. Gibt nil zurück, wenn die
|
||
/// Daten zu kurz sind.
|
||
mutating func read(_ width: Int) -> UInt32? {
|
||
guard width > 0, width <= 32, bitsRemaining >= width else { return nil }
|
||
var result: UInt32 = 0
|
||
for i in 0..<width {
|
||
let absolute = bitOffset + i
|
||
let byte = bytes[absolute / 8]
|
||
let bit = (byte >> UInt8(absolute % 8)) & 1
|
||
result |= UInt32(bit) << UInt32(i)
|
||
}
|
||
bitOffset += width
|
||
return result
|
||
}
|
||
|
||
/// Wie `read`, liefert aber nil wenn alle Bits gesetzt sind – so markiert
|
||
/// Victron "Wert nicht verfügbar".
|
||
mutating func readOptional(_ width: Int) -> UInt32? {
|
||
guard let raw = read(width) else { return nil }
|
||
let notAvailable: UInt32 = width >= 32 ? .max : (1 << UInt32(width)) - 1
|
||
return raw == notAvailable ? nil : raw
|
||
}
|
||
|
||
/// Zweierkomplement-Feld. Der NA-Wert 0x7F..F wird zu nil.
|
||
mutating func readOptionalSigned(_ width: Int) -> Int32? {
|
||
guard width > 1, let raw = read(width) else { return nil }
|
||
let notAvailable: UInt32 = (1 << UInt32(width - 1)) - 1
|
||
if raw == notAvailable { return nil }
|
||
let signBit: UInt32 = 1 << UInt32(width - 1)
|
||
if raw & signBit != 0 {
|
||
let mask: UInt32 = width >= 32 ? 0 : ~((1 << UInt32(width)) - 1)
|
||
return Int32(bitPattern: raw | mask)
|
||
}
|
||
return Int32(bitPattern: raw)
|
||
}
|
||
|
||
/// Skaliertes, optionales Feld ohne Vorzeichen.
|
||
mutating func scaled(_ width: Int, _ factor: Double) -> Double? {
|
||
readOptional(width).map { Double($0) * factor }
|
||
}
|
||
|
||
/// Skaliertes, optionales Feld mit Vorzeichen.
|
||
mutating func scaledSigned(_ width: Int, _ factor: Double) -> Double? {
|
||
readOptionalSigned(width).map { Double($0) * factor }
|
||
}
|
||
}
|