Enhance DALI bridge: add BridgeOperation and BridgeDaliTarget support, refactor address resolution and provisioning methods

This commit is contained in:
Tony
2026-05-02 21:33:57 +08:00
parent 16907857c0
commit 307c480aa0
8 changed files with 285 additions and 102 deletions
+2
View File
@@ -18,6 +18,8 @@ struct BacnetObjectBinding {
BridgeObjectType objectType = BridgeObjectType::unknown;
int objectInstance = -1;
std::string property;
BridgeOperation operation = BridgeOperation::unknown;
BridgeDaliTarget target;
};
class DaliBacnetBridge {
+1 -1
View File
@@ -55,7 +55,7 @@ class DaliBridgeEngine {
DaliBridgeResult executeResolved(const DaliBridgeRequest& request,
const BridgeModel* model,
BridgeOperation operation);
std::optional<int> resolveShortAddress(const DaliBridgeRequest& request,
std::optional<int> resolveTargetAddress(const DaliBridgeRequest& request,
const BridgeModel* model) const;
std::optional<int> resolveRawAddress(const DaliBridgeRequest& request,
const BridgeModel* model) const;
+13
View File
@@ -25,6 +25,12 @@ enum class BridgeObjectType {
multiStateValue = 9,
};
enum class BridgeDaliTargetKind {
shortAddress = 0,
group = 1,
broadcast = 2,
};
enum class BridgeOperation {
unknown = 0,
send = 1,
@@ -46,6 +52,7 @@ enum class BridgeOperation {
getEmergencyFailureStatus = 17,
startEmergencyFunctionTest = 18,
stopEmergencyTest = 19,
startEmergencyDurationTest = 20,
};
enum class BridgeValueEncoding {
@@ -80,12 +87,15 @@ struct BridgeExternalPoint {
};
struct BridgeDaliTarget {
BridgeDaliTargetKind kind = BridgeDaliTargetKind::shortAddress;
std::optional<int> shortAddress;
std::optional<int> groupAddress;
std::optional<int> rawAddress;
std::optional<int> rawCommand;
static BridgeDaliTarget fromJson(const DaliValue::Object* json);
DaliValue::Object toJson() const;
std::optional<int> logicalAddress() const;
};
struct BridgeModel {
@@ -110,6 +120,9 @@ BridgeProtocolKind bridgeProtocolKindFromString(const std::string& value);
const char* bridgeObjectTypeToString(BridgeObjectType type);
BridgeObjectType bridgeObjectTypeFromString(const std::string& value);
const char* bridgeDaliTargetKindToString(BridgeDaliTargetKind kind);
BridgeDaliTargetKind bridgeDaliTargetKindFromString(const std::string& value);
const char* bridgeOperationToString(BridgeOperation operation);
BridgeOperation bridgeOperationFromString(const std::string& value);
+3
View File
@@ -34,6 +34,9 @@ class BridgeProvisioningStore {
esp_err_t save(const BridgeRuntimeConfig& config) const;
esp_err_t load(BridgeRuntimeConfig* config) const;
esp_err_t clear() const;
esp_err_t saveObject(const char* key, const DaliValue::Object& object) const;
esp_err_t loadObject(const char* key, DaliValue::Object* object) const;
esp_err_t clearKey(const char* key) const;
private:
std::string nvsNamespace_;
+4 -2
View File
@@ -44,7 +44,8 @@ std::optional<BacnetObjectBinding> DaliBacnetBridge::findObject(BridgeObjectType
if (!model.external.property.empty() && model.external.property != property) {
continue;
}
return BacnetObjectBinding{model.id, objectType, objectInstance, property};
return BacnetObjectBinding{model.id, objectType, objectInstance, property,
model.operation, model.dali};
}
return std::nullopt;
}
@@ -57,7 +58,8 @@ std::vector<BacnetObjectBinding> DaliBacnetBridge::describeObjects() const {
}
bindings.push_back(BacnetObjectBinding{model.id, model.external.objectType,
model.external.objectInstance.value(),
model.external.property});
model.external.property, model.operation,
model.dali});
}
return bindings;
}
+41 -32
View File
@@ -135,7 +135,7 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
return result;
}
case BridgeOperation::setBrightness: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
const auto value = resolveIntValue(request, model);
if (!address.has_value() || !value.has_value()) {
result.error = "missing address/value";
@@ -146,7 +146,7 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
break;
}
case BridgeOperation::setBrightnessPercent: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
const auto value = resolveDoubleValue(request);
if (!address.has_value() || !value.has_value()) {
result.error = "missing address/value";
@@ -157,43 +157,43 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
break;
}
case BridgeOperation::on: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
result.ok = base_.on(address.value());
break;
}
case BridgeOperation::off: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
result.ok = base_.off(address.value());
break;
}
case BridgeOperation::recallMaxLevel: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
result.ok = base_.recallMaxLevel(address.value());
break;
}
case BridgeOperation::recallMinLevel: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
result.ok = base_.recallMinLevel(address.value());
break;
}
case BridgeOperation::setColorTemperature: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
const auto value = resolveIntValue(request, model);
if (!address.has_value() || !value.has_value()) {
result.error = "missing address/value";
@@ -204,9 +204,9 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
break;
}
case BridgeOperation::getBrightness: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
const auto value = base_.getBright(address.value());
@@ -219,9 +219,9 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
break;
}
case BridgeOperation::getStatus: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
const auto value = base_.getStatus(address.value());
@@ -235,9 +235,9 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
break;
}
case BridgeOperation::getColorTemperature: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
const auto value = dt8_.getColorTemperature(address.value());
@@ -250,9 +250,9 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
break;
}
case BridgeOperation::getColorStatus: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
const auto value = dt8_.getColorStatus(address.value());
@@ -273,9 +273,9 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
break;
}
case BridgeOperation::getEmergencyLevel: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
const auto value = dt1_.getEmergencyLevel(address.value());
@@ -288,9 +288,9 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
break;
}
case BridgeOperation::getEmergencyStatus: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
const auto status = dt1_.getEmergencyStatusDecoded(address.value());
@@ -311,9 +311,9 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
break;
}
case BridgeOperation::getEmergencyFailureStatus: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
const auto status = dt1_.getDT1TestStatusDetailed(address.value());
@@ -337,18 +337,27 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
break;
}
case BridgeOperation::startEmergencyFunctionTest: {
const auto address = resolveShortAddress(request, model);
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
result.ok = dt1_.startFunctionTestCmd(address.value());
break;
}
case BridgeOperation::stopEmergencyTest: {
const auto address = resolveShortAddress(request, model);
case BridgeOperation::startEmergencyDurationTest: {
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing short address";
result.error = "missing target address";
return result;
}
result.ok = dt1_.startDurationTestCmd(address.value());
break;
}
case BridgeOperation::stopEmergencyTest: {
const auto address = resolveTargetAddress(request, model);
if (!address.has_value()) {
result.error = "missing target address";
return result;
}
result.ok = dt1_.stopTest(address.value());
@@ -366,13 +375,13 @@ DaliBridgeResult DaliBridgeEngine::executeResolved(const DaliBridgeRequest& requ
return result;
}
std::optional<int> DaliBridgeEngine::resolveShortAddress(const DaliBridgeRequest& request,
std::optional<int> DaliBridgeEngine::resolveTargetAddress(const DaliBridgeRequest& request,
const BridgeModel* model) const {
if (request.shortAddress.has_value()) {
return request.shortAddress;
}
if (model != nullptr) {
return model->dali.shortAddress;
return model->dali.logicalAddress();
}
return std::nullopt;
}
+92 -2
View File
@@ -7,6 +7,9 @@
namespace {
constexpr int kDaliGroupBaseAddress = 64;
constexpr int kDaliBroadcastAddress = 127;
std::string normalize(const std::string& value) {
std::string out = value;
std::transform(out.begin(), out.end(), out.begin(), [](unsigned char ch) {
@@ -88,7 +91,37 @@ BridgeDaliTarget BridgeDaliTarget::fromJson(const DaliValue::Object* json) {
if (json == nullptr) {
return target;
}
target.shortAddress = getObjectInt(*json, "shortAddress");
const auto explicit_kind = bridgeDaliTargetKindFromString(
getObjectString(*json, "kind")
.value_or(getObjectString(*json, "targetKind").value_or("")));
const auto shared_address = getObjectInt(*json, "address");
const auto short_address = getObjectInt(*json, "shortAddress");
const auto group_address = getObjectInt(*json, "groupAddress").has_value()
? getObjectInt(*json, "groupAddress")
: getObjectInt(*json, "group");
const bool broadcast = getObjectBool(*json, "broadcast").value_or(false);
target.kind = explicit_kind;
if (explicit_kind == BridgeDaliTargetKind::shortAddress) {
target.shortAddress = shared_address;
} else if (explicit_kind == BridgeDaliTargetKind::group) {
target.groupAddress = shared_address;
}
if (short_address.has_value()) {
target.shortAddress = short_address;
if (explicit_kind != BridgeDaliTargetKind::broadcast) {
target.kind = BridgeDaliTargetKind::shortAddress;
}
}
if (group_address.has_value()) {
target.groupAddress = group_address;
if (explicit_kind != BridgeDaliTargetKind::broadcast) {
target.kind = BridgeDaliTargetKind::group;
}
}
if (broadcast) {
target.kind = BridgeDaliTargetKind::broadcast;
}
target.rawAddress = getObjectInt(*json, "rawAddress");
target.rawCommand = getObjectInt(*json, "rawCommand");
return target;
@@ -96,12 +129,44 @@ BridgeDaliTarget BridgeDaliTarget::fromJson(const DaliValue::Object* json) {
DaliValue::Object BridgeDaliTarget::toJson() const {
DaliValue::Object out;
if (shortAddress.has_value()) out["shortAddress"] = shortAddress.value();
out["kind"] = bridgeDaliTargetKindToString(kind);
switch (kind) {
case BridgeDaliTargetKind::shortAddress:
if (shortAddress.has_value()) {
out["address"] = shortAddress.value();
out["shortAddress"] = shortAddress.value();
}
break;
case BridgeDaliTargetKind::group:
if (groupAddress.has_value()) {
out["address"] = groupAddress.value();
out["groupAddress"] = groupAddress.value();
}
break;
case BridgeDaliTargetKind::broadcast:
out["broadcast"] = true;
break;
}
if (rawAddress.has_value()) out["rawAddress"] = rawAddress.value();
if (rawCommand.has_value()) out["rawCommand"] = rawCommand.value();
return out;
}
std::optional<int> BridgeDaliTarget::logicalAddress() const {
switch (kind) {
case BridgeDaliTargetKind::shortAddress:
return shortAddress;
case BridgeDaliTargetKind::group:
if (!groupAddress.has_value()) {
return std::nullopt;
}
return kDaliGroupBaseAddress + groupAddress.value();
case BridgeDaliTargetKind::broadcast:
return kDaliBroadcastAddress;
}
return std::nullopt;
}
BridgeModel BridgeModel::fromJson(const DaliValue::Object& json) {
BridgeModel model;
model.id = getObjectString(json, "id").value_or("");
@@ -210,6 +275,26 @@ BridgeObjectType bridgeObjectTypeFromString(const std::string& value) {
return BridgeObjectType::unknown;
}
const char* bridgeDaliTargetKindToString(BridgeDaliTargetKind kind) {
switch (kind) {
case BridgeDaliTargetKind::shortAddress:
return "short_address";
case BridgeDaliTargetKind::group:
return "group";
case BridgeDaliTargetKind::broadcast:
return "broadcast";
default:
return "short_address";
}
}
BridgeDaliTargetKind bridgeDaliTargetKindFromString(const std::string& value) {
const std::string normalized = normalize(value);
if (normalized == "group") return BridgeDaliTargetKind::group;
if (normalized == "broadcast") return BridgeDaliTargetKind::broadcast;
return BridgeDaliTargetKind::shortAddress;
}
const char* bridgeOperationToString(BridgeOperation operation) {
switch (operation) {
case BridgeOperation::send:
@@ -250,6 +335,8 @@ const char* bridgeOperationToString(BridgeOperation operation) {
return "start_emergency_function_test";
case BridgeOperation::stopEmergencyTest:
return "stop_emergency_test";
case BridgeOperation::startEmergencyDurationTest:
return "start_emergency_duration_test";
case BridgeOperation::unknown:
default:
return "unknown";
@@ -279,6 +366,9 @@ BridgeOperation bridgeOperationFromString(const std::string& value) {
return BridgeOperation::startEmergencyFunctionTest;
}
if (normalized == "stop_emergency_test") return BridgeOperation::stopEmergencyTest;
if (normalized == "start_emergency_duration_test") {
return BridgeOperation::startEmergencyDurationTest;
}
return BridgeOperation::unknown;
}
+127 -63
View File
@@ -143,6 +143,98 @@ DaliValue bacnetToJson(const BacnetBridgeConfig& config) {
return DaliValue(std::move(out));
}
esp_err_t saveJsonObject(const std::string& nvs_namespace, const char* key,
const DaliValue::Object& object) {
if (key == nullptr || key[0] == '\0') {
return ESP_ERR_INVALID_ARG;
}
nvs_handle_t handle;
esp_err_t err = nvs_open(nvs_namespace.c_str(), NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGE(kTag, "nvs_open(save %s) failed: %s", key, esp_err_to_name(err));
return err;
}
cJSON* root = toCjson(DaliValue(object));
char* raw = cJSON_PrintUnformatted(root);
if (raw == nullptr) {
cJSON_Delete(root);
nvs_close(handle);
return ESP_ERR_NO_MEM;
}
err = nvs_set_str(handle, key, raw);
if (err == ESP_OK) {
err = nvs_commit(handle);
}
cJSON_Delete(root);
cJSON_free(raw);
nvs_close(handle);
if (err != ESP_OK) {
ESP_LOGE(kTag, "save %s failed: %s", key, esp_err_to_name(err));
}
return err;
}
esp_err_t loadJsonObject(const std::string& nvs_namespace, const char* key,
DaliValue::Object* object) {
if (object == nullptr || key == nullptr || key[0] == '\0') {
return ESP_ERR_INVALID_ARG;
}
nvs_handle_t handle;
esp_err_t err = nvs_open(nvs_namespace.c_str(), NVS_READONLY, &handle);
if (err != ESP_OK) {
return err;
}
std::string payload;
err = readString(handle, key, &payload);
nvs_close(handle);
if (err != ESP_OK) {
return err;
}
cJSON* root = cJSON_Parse(payload.c_str());
if (root == nullptr) {
return ESP_ERR_INVALID_RESPONSE;
}
const DaliValue value = fromCjson(root);
cJSON_Delete(root);
const auto* parsed = value.asObject();
if (parsed == nullptr) {
return ESP_ERR_INVALID_RESPONSE;
}
*object = *parsed;
return ESP_OK;
}
esp_err_t clearStoredKey(const std::string& nvs_namespace, const char* key) {
if (key == nullptr || key[0] == '\0') {
return ESP_ERR_INVALID_ARG;
}
nvs_handle_t handle;
esp_err_t err = nvs_open(nvs_namespace.c_str(), NVS_READWRITE, &handle);
if (err != ESP_OK) {
return err;
}
err = nvs_erase_key(handle, key);
if (err == ESP_ERR_NVS_NOT_FOUND) {
err = ESP_OK;
}
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
return err;
}
} // namespace
BridgeRuntimeConfig BridgeRuntimeConfig::fromJson(const DaliValue::Object& json) {
@@ -182,33 +274,7 @@ DaliValue::Object BridgeRuntimeConfig::toJson() const {
}
esp_err_t BridgeProvisioningStore::save(const BridgeRuntimeConfig& config) const {
nvs_handle_t handle;
esp_err_t err = nvs_open(nvsNamespace_.c_str(), NVS_READWRITE, &handle);
if (err != ESP_OK) {
ESP_LOGE(kTag, "nvs_open(save) failed: %s", esp_err_to_name(err));
return err;
}
cJSON* root = toCjson(DaliValue(config.toJson()));
char* raw = cJSON_PrintUnformatted(root);
if (raw == nullptr) {
cJSON_Delete(root);
nvs_close(handle);
return ESP_ERR_NO_MEM;
}
err = nvs_set_str(handle, kKeyConfig, raw);
if (err == ESP_OK) {
err = nvs_commit(handle);
}
cJSON_Delete(root);
cJSON_free(raw);
nvs_close(handle);
if (err != ESP_OK) {
ESP_LOGE(kTag, "save failed: %s", esp_err_to_name(err));
}
return err;
return saveJsonObject(nvsNamespace_, kKeyConfig, config.toJson());
}
esp_err_t BridgeProvisioningStore::load(BridgeRuntimeConfig* config) const {
@@ -216,51 +282,31 @@ esp_err_t BridgeProvisioningStore::load(BridgeRuntimeConfig* config) const {
return ESP_ERR_INVALID_ARG;
}
nvs_handle_t handle;
esp_err_t err = nvs_open(nvsNamespace_.c_str(), NVS_READONLY, &handle);
DaliValue::Object object;
const esp_err_t err = loadJsonObject(nvsNamespace_, kKeyConfig, &object);
if (err != ESP_OK) {
return err;
}
std::string payload;
err = readString(handle, kKeyConfig, &payload);
nvs_close(handle);
if (err != ESP_OK) {
return err;
}
cJSON* root = cJSON_Parse(payload.c_str());
if (root == nullptr) {
return ESP_ERR_INVALID_RESPONSE;
}
const DaliValue value = fromCjson(root);
cJSON_Delete(root);
const auto* object = value.asObject();
if (object == nullptr) {
return ESP_ERR_INVALID_RESPONSE;
}
*config = BridgeRuntimeConfig::fromJson(*object);
*config = BridgeRuntimeConfig::fromJson(object);
return ESP_OK;
}
esp_err_t BridgeProvisioningStore::clear() const {
nvs_handle_t handle;
esp_err_t err = nvs_open(nvsNamespace_.c_str(), NVS_READWRITE, &handle);
if (err != ESP_OK) {
return err;
}
return clearStoredKey(nvsNamespace_, kKeyConfig);
}
err = nvs_erase_key(handle, kKeyConfig);
if (err == ESP_ERR_NVS_NOT_FOUND) {
err = ESP_OK;
}
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
return err;
esp_err_t BridgeProvisioningStore::saveObject(const char* key,
const DaliValue::Object& object) const {
return saveJsonObject(nvsNamespace_, key, object);
}
esp_err_t BridgeProvisioningStore::loadObject(const char* key, DaliValue::Object* object) const {
return loadJsonObject(nvsNamespace_, key, object);
}
esp_err_t BridgeProvisioningStore::clearKey(const char* key) const {
return clearStoredKey(nvsNamespace_, key);
}
#else
@@ -284,4 +330,22 @@ esp_err_t BridgeProvisioningStore::load(BridgeRuntimeConfig* config) const {
esp_err_t BridgeProvisioningStore::clear() const { return -1; }
esp_err_t BridgeProvisioningStore::saveObject(const char* key,
const DaliValue::Object& object) const {
(void)key;
(void)object;
return -1;
}
esp_err_t BridgeProvisioningStore::loadObject(const char* key, DaliValue::Object* object) const {
(void)key;
(void)object;
return -1;
}
esp_err_t BridgeProvisioningStore::clearKey(const char* key) const {
(void)key;
return -1;
}
#endif