#include "device.hpp" #include namespace { std::optional> asIntList(const DaliValue* value) { if (!value) return std::nullopt; const auto* arr = value->asArray(); if (!arr) return std::nullopt; std::vector out; out.reserve(arr->size()); for (const auto& v : *arr) { out.push_back(v.asInt().value_or(0)); } return out; } DaliValue toIntArray(const std::optional>& values) { if (!values.has_value()) return DaliValue(); DaliValue::Array out; out.reserve(values->size()); for (const int v : *values) { out.emplace_back(v); } return DaliValue(std::move(out)); } } // namespace DaliLongAddress DaliLongAddress::fromJson(const DaliValue::Object* json) { if (!json) return DaliLongAddress{}; DaliLongAddress addr; addr.h = getObjectInt(*json, "H").value_or(0); addr.m = getObjectInt(*json, "M").value_or(0); addr.l = getObjectInt(*json, "L").value_or(0); return addr; } DaliValue::Object DaliLongAddress::toJson() const { DaliValue::Object out; out["H"] = h; out["M"] = m; out["L"] = l; return out; } DaliDeviceCapabilities DaliDeviceCapabilities::fromJson(const DaliValue::Object* json) { DaliDeviceCapabilities c; if (!json) return c; c.supportsDt1 = getObjectBool(*json, "dt1"); c.supportsDt8 = getObjectBool(*json, "dt8"); return c; } DaliValue::Object DaliDeviceCapabilities::toJson() const { DaliValue::Object out; if (supportsDt1.has_value()) out["dt1"] = supportsDt1.value(); if (supportsDt8.has_value()) out["dt8"] = supportsDt8.value(); return out; } void DaliDeviceCapabilities::merge(const DaliDeviceCapabilities& other) { if (!supportsDt1.has_value()) supportsDt1 = other.supportsDt1; if (!supportsDt8.has_value()) supportsDt8 = other.supportsDt8; } DaliStatusFlags DaliStatusFlags::fromJson(const DaliValue::Object* json) { DaliStatusFlags s; if (!json) return s; s.controlGearPresent = getObjectBool(*json, "controlGearPresent"); s.lampFailure = getObjectBool(*json, "lampFailure"); s.lampPowerOn = getObjectBool(*json, "lampPowerOn"); s.limitError = getObjectBool(*json, "limitError"); s.fadingCompleted = getObjectBool(*json, "fadingCompleted"); s.resetState = getObjectBool(*json, "resetState"); s.missingShortAddress = getObjectBool(*json, "missingShortAddress"); s.psFault = getObjectBool(*json, "psFault"); return s; } DaliValue::Object DaliStatusFlags::toJson() const { DaliValue::Object out; if (controlGearPresent.has_value()) out["controlGearPresent"] = controlGearPresent.value(); if (lampFailure.has_value()) out["lampFailure"] = lampFailure.value(); if (lampPowerOn.has_value()) out["lampPowerOn"] = lampPowerOn.value(); if (limitError.has_value()) out["limitError"] = limitError.value(); if (fadingCompleted.has_value()) out["fadingCompleted"] = fadingCompleted.value(); if (resetState.has_value()) out["resetState"] = resetState.value(); if (missingShortAddress.has_value()) out["missingShortAddress"] = missingShortAddress.value(); if (psFault.has_value()) out["psFault"] = psFault.value(); return out; } bool DaliStatusFlags::hasData() const { return controlGearPresent.has_value() || lampFailure.has_value() || lampPowerOn.has_value() || limitError.has_value() || fadingCompleted.has_value() || resetState.has_value() || missingShortAddress.has_value() || psFault.has_value(); } void DaliStatusFlags::merge(const DaliStatusFlags& other) { if (!controlGearPresent.has_value()) controlGearPresent = other.controlGearPresent; if (!lampFailure.has_value()) lampFailure = other.lampFailure; if (!lampPowerOn.has_value()) lampPowerOn = other.lampPowerOn; if (!limitError.has_value()) limitError = other.limitError; if (!fadingCompleted.has_value()) fadingCompleted = other.fadingCompleted; if (!resetState.has_value()) resetState = other.resetState; if (!missingShortAddress.has_value()) missingShortAddress = other.missingShortAddress; if (!psFault.has_value()) psFault = other.psFault; } DaliDt8State DaliDt8State::fromJson(const DaliValue::Object* json) { DaliDt8State s; if (!json) return s; s.colorType = getObjectInt(*json, "colorType"); s.activeMode = getObjectString(*json, "activeMode"); if (const auto* xyVal = getObjectValue(*json, "xy")) { if (const auto* xy = xyVal->asObject()) { s.xyX = getObjectInt(*xy, "x"); s.xyY = getObjectInt(*xy, "y"); } } if (const auto* gamutVal = getObjectValue(*json, "gamut")) { if (const auto* gamut = gamutVal->asObject()) { s.xyMinX = getObjectInt(*gamut, "xMin"); s.xyMaxX = getObjectInt(*gamut, "xMax"); s.xyMinY = getObjectInt(*gamut, "yMin"); s.xyMaxY = getObjectInt(*gamut, "yMax"); } } s.mirek = getObjectInt(*json, "mirek"); s.mirekMin = getObjectInt(*json, "mirekMin"); s.mirekMax = getObjectInt(*json, "mirekMax"); s.rgbwaf = asIntList(getObjectValue(*json, "rgbwaf")); s.primaryN = asIntList(getObjectValue(*json, "primaryN")); return s; } DaliValue::Object DaliDt8State::toJson() const { DaliValue::Object out; if (colorType.has_value()) out["colorType"] = colorType.value(); if (activeMode.has_value()) out["activeMode"] = activeMode.value(); if (xyX.has_value() || xyY.has_value()) { DaliValue::Object xy; if (xyX.has_value()) xy["x"] = xyX.value(); if (xyY.has_value()) xy["y"] = xyY.value(); out["xy"] = std::move(xy); } if (xyMinX.has_value() || xyMaxX.has_value() || xyMinY.has_value() || xyMaxY.has_value()) { DaliValue::Object gamut; if (xyMinX.has_value()) gamut["xMin"] = xyMinX.value(); if (xyMaxX.has_value()) gamut["xMax"] = xyMaxX.value(); if (xyMinY.has_value()) gamut["yMin"] = xyMinY.value(); if (xyMaxY.has_value()) gamut["yMax"] = xyMaxY.value(); out["gamut"] = std::move(gamut); } if (mirek.has_value()) out["mirek"] = mirek.value(); if (mirekMin.has_value()) out["mirekMin"] = mirekMin.value(); if (mirekMax.has_value()) out["mirekMax"] = mirekMax.value(); if (rgbwaf.has_value()) out["rgbwaf"] = toIntArray(rgbwaf); if (primaryN.has_value()) out["primaryN"] = toIntArray(primaryN); return out; } DaliDt1State DaliDt1State::fromJson(const DaliValue::Object* json) { DaliDt1State s; if (!json) return s; s.emergencyLevel = getObjectInt(*json, "emergencyLevel"); s.emergencyMinLevel = getObjectInt(*json, "emergencyMinLevel"); s.emergencyMaxLevel = getObjectInt(*json, "emergencyMaxLevel"); s.prolongTimeMinutes = getObjectInt(*json, "prolongTimeMinutes"); s.ratedDurationMinutes = getObjectInt(*json, "ratedDurationMinutes"); s.testDelayTime = getObjectInt(*json, "testDelayTime"); s.failureStatus = getObjectInt(*json, "failureStatus"); s.emergencyStatus = getObjectInt(*json, "emergencyStatus"); s.emergencyMode = getObjectInt(*json, "emergencyMode"); s.feature = getObjectInt(*json, "feature"); s.version = getObjectInt(*json, "version"); return s; } DaliValue::Object DaliDt1State::toJson() const { DaliValue::Object out; if (emergencyLevel.has_value()) out["emergencyLevel"] = emergencyLevel.value(); if (emergencyMinLevel.has_value()) out["emergencyMinLevel"] = emergencyMinLevel.value(); if (emergencyMaxLevel.has_value()) out["emergencyMaxLevel"] = emergencyMaxLevel.value(); if (prolongTimeMinutes.has_value()) out["prolongTimeMinutes"] = prolongTimeMinutes.value(); if (ratedDurationMinutes.has_value()) out["ratedDurationMinutes"] = ratedDurationMinutes.value(); if (testDelayTime.has_value()) out["testDelayTime"] = testDelayTime.value(); if (failureStatus.has_value()) out["failureStatus"] = failureStatus.value(); if (emergencyStatus.has_value()) out["emergencyStatus"] = emergencyStatus.value(); if (emergencyMode.has_value()) out["emergencyMode"] = emergencyMode.value(); if (feature.has_value()) out["feature"] = feature.value(); if (version.has_value()) out["version"] = version.value(); return out; } DaliDevice DaliDevice::fromJson(const DaliValue::Object& json) { DaliDevice d; d.name = getObjectString(json, "name").value_or(""); d.id = getObjectString(json, "id").value_or(""); if (d.id.empty()) { d.id = d.name.empty() ? "device-unknown" : d.name; } if (d.name.empty()) { d.name = d.id; } d.shortAddress = getObjectInt(json, "shortAddress"); if (const auto* lv = getObjectValue(json, "longAddress")) { if (const auto* lo = lv->asObject()) { d.longAddress = DaliLongAddress::fromJson(lo); } } d.isolated = getObjectBool(json, "isolated").value_or(false); d.brightness = getObjectInt(json, "brightness"); d.groupBits = getObjectInt(json, "groupBits"); d.scenes = asIntList(getObjectValue(json, "scenes")); d.fadeTime = getObjectInt(json, "fadeTime"); d.fadeRate = getObjectInt(json, "fadeRate"); d.powerOnLevel = getObjectInt(json, "powerOnLevel"); d.systemFailureLevel = getObjectInt(json, "systemFailureLevel"); d.minLevel = getObjectInt(json, "minLevel"); d.maxLevel = getObjectInt(json, "maxLevel"); d.operatingMode = getObjectInt(json, "operatingMode"); d.physicalMinLevel = getObjectInt(json, "physicalMinLevel"); d.deviceType = getObjectInt(json, "deviceType"); d.extType = asIntList(getObjectValue(json, "extType")).value_or(std::vector{}); d.version = getObjectInt(json, "version"); if (const auto* cv = getObjectValue(json, "capabilities")) { d.capabilities = DaliDeviceCapabilities::fromJson(cv->asObject()); } if (const auto* dt8v = getObjectValue(json, "dt8")) { if (const auto* obj = dt8v->asObject()) { d.dt8 = DaliDt8State::fromJson(obj); } } if (const auto* dt1v = getObjectValue(json, "dt1")) { if (const auto* obj = dt1v->asObject()) { d.dt1 = DaliDt1State::fromJson(obj); } } if (const auto* sfv = getObjectValue(json, "statusFlags")) { d.statusFlags = DaliStatusFlags::fromJson(sfv->asObject()); } d.lastSyncedUtc = getObjectString(json, "lastSyncedUtc"); if (const auto* mv = getObjectValue(json, "meta")) { if (const auto* obj = mv->asObject()) { d.metadata = *obj; } } return d; } DaliValue::Object DaliDevice::toJson() const { DaliValue::Object out; out["id"] = id; out["name"] = name; if (shortAddress.has_value()) out["shortAddress"] = shortAddress.value(); if (longAddress.has_value()) out["longAddress"] = longAddress->toJson(); out["isolated"] = isolated; if (brightness.has_value()) out["brightness"] = brightness.value(); if (groupBits.has_value()) out["groupBits"] = groupBits.value(); if (scenes.has_value()) out["scenes"] = toIntArray(scenes); if (fadeTime.has_value()) out["fadeTime"] = fadeTime.value(); if (fadeRate.has_value()) out["fadeRate"] = fadeRate.value(); if (powerOnLevel.has_value()) out["powerOnLevel"] = powerOnLevel.value(); if (systemFailureLevel.has_value()) out["systemFailureLevel"] = systemFailureLevel.value(); if (minLevel.has_value()) out["minLevel"] = minLevel.value(); if (maxLevel.has_value()) out["maxLevel"] = maxLevel.value(); if (operatingMode.has_value()) out["operatingMode"] = operatingMode.value(); if (physicalMinLevel.has_value()) out["physicalMinLevel"] = physicalMinLevel.value(); if (deviceType.has_value()) out["deviceType"] = deviceType.value(); if (!extType.empty()) { DaliValue::Array arr; arr.reserve(extType.size()); for (const int t : extType) arr.emplace_back(t); out["extType"] = std::move(arr); } if (version.has_value()) out["version"] = version.value(); const auto caps = capabilities.toJson(); if (!caps.empty()) out["capabilities"] = caps; if (dt8.has_value()) out["dt8"] = dt8->toJson(); if (dt1.has_value()) out["dt1"] = dt1->toJson(); if (statusFlags.hasData()) out["statusFlags"] = statusFlags.toJson(); if (lastSyncedUtc.has_value()) out["lastSyncedUtc"] = lastSyncedUtc.value(); if (!metadata.empty()) out["meta"] = metadata; return out; } std::string DaliDevice::displayName() const { return name.empty() ? id : name; } void DaliDevice::merge(const DaliDevice& other) { if (other.shortAddress.has_value()) shortAddress = other.shortAddress; if (!longAddress.has_value()) longAddress = other.longAddress; isolated = other.isolated; if (!brightness.has_value()) brightness = other.brightness; if (!groupBits.has_value()) groupBits = other.groupBits; if (!scenes.has_value() && other.scenes.has_value()) scenes = other.scenes; if (!fadeTime.has_value()) fadeTime = other.fadeTime; if (!fadeRate.has_value()) fadeRate = other.fadeRate; if (!powerOnLevel.has_value()) powerOnLevel = other.powerOnLevel; if (!systemFailureLevel.has_value()) systemFailureLevel = other.systemFailureLevel; if (!minLevel.has_value()) minLevel = other.minLevel; if (!maxLevel.has_value()) maxLevel = other.maxLevel; if (!operatingMode.has_value()) operatingMode = other.operatingMode; if (!physicalMinLevel.has_value()) physicalMinLevel = other.physicalMinLevel; if (!deviceType.has_value()) deviceType = other.deviceType; if (extType.empty() && !other.extType.empty()) extType = other.extType; if (!version.has_value()) version = other.version; capabilities.merge(other.capabilities); if (!dt8.has_value() && other.dt8.has_value()) dt8 = other.dt8; if (!dt1.has_value() && other.dt1.has_value()) dt1 = other.dt1; statusFlags.merge(other.statusFlags); if (other.lastSyncedUtc.has_value()) lastSyncedUtc = other.lastSyncedUtc; for (const auto& kv : other.metadata) { metadata[kv.first] = kv.second; } }