Skip to content

File syn_dali.c

File List > proto > syn_dali.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_DALI) || SYN_USE_DALI

#include "../util/syn_assert.h"
#include "syn_dali.h"

#include <string.h>

uint16_t syn_dali_encode_forward(uint8_t addr, uint8_t data_cmd)
{
    return (((uint16_t)addr) << 8) | (uint16_t)data_cmd;
}

bool syn_dali_decode_forward(uint16_t raw_16, SYN_DALI_ForwardFrame *frame)
{
    if (frame == NULL)
        return false;

    frame->raw = raw_16;
    uint8_t addr = (uint8_t)((raw_16 >> 8) & 0xFFU);
    frame->data_cmd = (uint8_t)(raw_16 & 0xFFU);

    /* Address decoding:
     * - Bit 7 = 0: Short Address (0a aaaa aS) -> short addr = a aaaa
     * - Bit 7..4 = 1000..1001: Group Address (100g gggS) -> group = g ggg
     * - Bit 7..1 = 1111 111: Broadcast (1111 111S)
     * - Otherwise: Special / Configuration
     */
    bool selector_bit = (addr & 0x01U) != 0U; /* Bit 0: 0 = Direct Arc Level, 1 = Command */
    frame->is_direct = !selector_bit;

    if ((addr & 0x80U) == 0x00U) {
        frame->addr_type = SYN_DALI_ADDR_SHORT;
        frame->address = (addr >> 1) & 0x3FU;
    } else if ((addr & 0xE0U) == 0x80U) {
        frame->addr_type = SYN_DALI_ADDR_GROUP;
        frame->address = (addr >> 1) & 0x0FU;
    } else if ((addr & 0xFEU) == 0xFEU) {
        frame->addr_type = SYN_DALI_ADDR_BROADCAST;
        frame->address = 0xFFU;
    } else {
        frame->addr_type = SYN_DALI_ADDR_SPECIAL;
        frame->address = addr;
    }

    return true;
}

uint8_t syn_dali_encode_backward(uint8_t data)
{
    return data;
}

bool syn_dali_decode_backward(uint8_t raw_8, SYN_DALI_BackwardFrame *frame)
{
    if (frame == NULL)
        return false;
    frame->data = raw_8;
    return true;
}

SYN_Status syn_dali_slave_init(SYN_DALI_SlaveState *slave, const SYN_DALI_SlaveConfig *cfg)
{
    if (slave == NULL || cfg == NULL) {
        return SYN_INVALID_PARAM;
    }

    memset(slave, 0, sizeof(*slave));
    slave->cfg = *cfg;

    if (slave->cfg.min_level == 0)
        slave->cfg.min_level = 1;
    if (slave->cfg.max_level == 0)
        slave->cfg.max_level = 254;
    if (slave->cfg.power_on_level != SYN_DALI_MASK_LEVEL) {
        slave->actual_level = slave->cfg.power_on_level;
    } else {
        slave->actual_level = slave->cfg.max_level;
    }

    memset(slave->scenes, SYN_DALI_MASK_LEVEL, sizeof(slave->scenes));
    slave->lamp_on = (slave->actual_level > 0);

    return SYN_OK;
}

static bool check_address_match(const SYN_DALI_SlaveState *slave, const SYN_DALI_ForwardFrame *req)
{
    if (req->addr_type == SYN_DALI_ADDR_BROADCAST) {
        return true;
    }
    if (req->addr_type == SYN_DALI_ADDR_SHORT) {
        return (slave->cfg.short_address != SYN_DALI_SHORT_ADDR_UNASSIGNED &&
                slave->cfg.short_address == req->address);
    }
    if (req->addr_type == SYN_DALI_ADDR_GROUP) {
        if (req->address < 16) {
            return ((slave->cfg.group_mask & (1U << req->address)) != 0U);
        }
    }
    return false;
}

SYN_Status syn_dali_slave_process(SYN_DALI_SlaveState *slave, const SYN_DALI_ForwardFrame *req,
                                  uint8_t *resp_data, bool *has_resp)
{
    if (slave == NULL || req == NULL || resp_data == NULL || has_resp == NULL) {
        return SYN_INVALID_PARAM;
    }

    *has_resp = false;
    *resp_data = 0;

    bool is_match = check_address_match(slave, req);

    if (is_match) {
        if (req->is_direct) {
            /* Direct Arc Power Control */
            uint8_t target_level = req->data_cmd;
            if (target_level == SYN_DALI_MASK_LEVEL) {
                /* MASK level: do nothing */
            } else if (target_level == 0) {
                slave->actual_level = 0;
                slave->lamp_on = false;
            } else {
                if (target_level < slave->cfg.min_level)
                    target_level = slave->cfg.min_level;
                if (target_level > slave->cfg.max_level)
                    target_level = slave->cfg.max_level;
                slave->actual_level = target_level;
                slave->lamp_on = true;
            }
            return SYN_OK;
        }

        /* Indirect Standard Commands */
        uint8_t cmd = req->data_cmd;
        switch (cmd) {
        case SYN_DALI_CMD_OFF:
            slave->actual_level = 0;
            slave->lamp_on = false;
            break;

        case SYN_DALI_CMD_UP:
            if (slave->actual_level < slave->cfg.max_level) {
                slave->actual_level++;
                slave->lamp_on = true;
            }
            break;

        case SYN_DALI_CMD_DOWN:
            if (slave->actual_level > slave->cfg.min_level) {
                slave->actual_level--;
            }
            break;

        case SYN_DALI_CMD_STEP_UP:
            if (slave->actual_level < slave->cfg.max_level) {
                slave->actual_level++;
                slave->lamp_on = true;
            }
            break;

        case SYN_DALI_CMD_STEP_DOWN:
            if (slave->actual_level > slave->cfg.min_level) {
                slave->actual_level--;
            } else {
                slave->actual_level = 0;
                slave->lamp_on = false;
            }
            break;

        case SYN_DALI_CMD_RECALL_MAX:
            slave->actual_level = slave->cfg.max_level;
            slave->lamp_on = true;
            break;

        case SYN_DALI_CMD_RECALL_MIN:
            slave->actual_level = slave->cfg.min_level;
            slave->lamp_on = true;
            break;

        case SYN_DALI_CMD_STEP_DOWN_AND_OFF:
            if (slave->actual_level > slave->cfg.min_level) {
                slave->actual_level--;
            } else {
                slave->actual_level = 0;
                slave->lamp_on = false;
            }
            break;

        case SYN_DALI_CMD_ON_AND_STEP_UP:
            if (!slave->lamp_on || slave->actual_level == 0) {
                slave->actual_level = slave->cfg.min_level;
                slave->lamp_on = true;
            } else if (slave->actual_level < slave->cfg.max_level) {
                slave->actual_level++;
            }
            break;

        case SYN_DALI_CMD_ENABLE_DAPC_SEQUENCE:
            slave->dapc_sequence_active = true;
            break;

        case SYN_DALI_CMD_RESET:
            slave->actual_level = slave->cfg.power_on_level;
            slave->lamp_on = (slave->actual_level > 0);
            break;

        case SYN_DALI_CMD_STORE_ACTUAL_LEVEL_IN_DTR:
            slave->dtr0 = slave->actual_level;
            break;

        case SYN_DALI_CMD_STORE_DTR_AS_MAX_LEVEL:
            slave->cfg.max_level = slave->dtr0;
            break;

        case SYN_DALI_CMD_STORE_DTR_AS_MIN_LEVEL:
            slave->cfg.min_level = slave->dtr0;
            break;

        case SYN_DALI_CMD_STORE_DTR_AS_SYS_FAIL_LEVEL:
            slave->cfg.system_failure_level = slave->dtr0;
            break;

        case SYN_DALI_CMD_STORE_DTR_AS_POWER_ON_LEVEL:
            slave->cfg.power_on_level = slave->dtr0;
            break;

        case SYN_DALI_CMD_STORE_DTR_AS_FADE_TIME:
            slave->cfg.fade_time = slave->dtr0;
            break;

        case SYN_DALI_CMD_STORE_DTR_AS_FADE_RATE:
            slave->cfg.fade_rate = slave->dtr0;
            break;

        case SYN_DALI_CMD_STORE_DTR_AS_SHORT_ADDR:
            slave->cfg.short_address = (slave->dtr0 >> 1) & 0x3FU;
            break;

        case SYN_DALI_CMD_ENABLE_WRITE_MEMORY:
            slave->write_mem_enabled = true;
            break;

        case SYN_DALI_CMD_QUERY_STATUS: {
            uint8_t status = 0;
            if (slave->control_gear_failure)
                status |= (1U << 0);
            if (slave->lamp_failure)
                status |= (1U << 1);
            if (slave->lamp_on)
                status |= (1U << 2);
            if (slave->cfg.short_address == SYN_DALI_SHORT_ADDR_UNASSIGNED)
                status |= (1U << 6);
            *resp_data = status;
            *has_resp = true;
            break;
        }

        case SYN_DALI_CMD_QUERY_CONTENT_DTR:
            *resp_data = slave->dtr0;
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_CONTENT_DTR1:
            *resp_data = slave->dtr1;
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_CONTENT_DTR2:
            *resp_data = slave->dtr2;
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_GROUPS_0_7:
            *resp_data = (uint8_t)(slave->cfg.group_mask & 0xFFU);
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_GROUPS_8_15:
            *resp_data = (uint8_t)((slave->cfg.group_mask >> 8) & 0xFFU);
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_RANDOM_ADDR_H:
            *resp_data = (uint8_t)((slave->random_address >> 16) & 0xFFU);
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_RANDOM_ADDR_M:
            *resp_data = (uint8_t)((slave->random_address >> 8) & 0xFFU);
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_RANDOM_ADDR_L:
            *resp_data = (uint8_t)(slave->random_address & 0xFFU);
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_ACTUAL_LEVEL:
            *resp_data = slave->actual_level;
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_MAX_LEVEL:
            *resp_data = slave->cfg.max_level;
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_MIN_LEVEL:
            *resp_data = slave->cfg.min_level;
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_POWER_ON_LEVEL:
            *resp_data = slave->cfg.power_on_level;
            *has_resp = true;
            break;

        case SYN_DALI_CMD_QUERY_SYS_FAIL_LEVEL:
            *resp_data = slave->cfg.system_failure_level;
            *has_resp = true;
            break;

        default:
            if (cmd >= SYN_DALI_CMD_GO_TO_SCENE_BASE &&
                cmd <= (SYN_DALI_CMD_GO_TO_SCENE_BASE + 15U)) {
                uint8_t scene_idx = cmd - SYN_DALI_CMD_GO_TO_SCENE_BASE;
                uint8_t scene_lvl = slave->scenes[scene_idx];
                if (scene_lvl != SYN_DALI_MASK_LEVEL) {
                    slave->actual_level = scene_lvl;
                    slave->lamp_on = (scene_lvl > 0);
                }
            } else if (cmd >= SYN_DALI_CMD_STORE_DTR_AS_SCENE_BASE &&
                       cmd <= (SYN_DALI_CMD_STORE_DTR_AS_SCENE_BASE + 15U)) {
                slave->scenes[cmd - SYN_DALI_CMD_STORE_DTR_AS_SCENE_BASE] = slave->dtr0;
            } else if (cmd >= SYN_DALI_CMD_REMOVE_FROM_SCENE_BASE &&
                       cmd <= (SYN_DALI_CMD_REMOVE_FROM_SCENE_BASE + 15U)) {
                slave->scenes[cmd - SYN_DALI_CMD_REMOVE_FROM_SCENE_BASE] = SYN_DALI_MASK_LEVEL;
            } else if (cmd >= SYN_DALI_CMD_ADD_TO_GROUP_BASE &&
                       cmd <= (SYN_DALI_CMD_ADD_TO_GROUP_BASE + 15U)) {
                slave->cfg.group_mask |= (1U << (cmd - SYN_DALI_CMD_ADD_TO_GROUP_BASE));
            } else if (cmd >= SYN_DALI_CMD_REMOVE_FROM_GROUP_BASE &&
                       cmd <= (SYN_DALI_CMD_REMOVE_FROM_GROUP_BASE + 15U)) {
                slave->cfg.group_mask &= ~(1U << (cmd - SYN_DALI_CMD_REMOVE_FROM_GROUP_BASE));
            }
            break;
        }
    }

    /* Special / Configuration Commands */
    if (req->addr_type == SYN_DALI_ADDR_SPECIAL) {
        uint8_t spec_code = req->address;
        uint8_t spec_data = req->data_cmd;

        switch (spec_code) {
        case SYN_DALI_SPEC_TERMINATE:
            slave->initialise_active = false;
            break;

        case SYN_DALI_SPEC_DTR0:
            slave->dtr0 = spec_data;
            break;

        case SYN_DALI_SPEC_DTR1:
            slave->dtr1 = spec_data;
            break;

        case SYN_DALI_SPEC_DTR2:
            slave->dtr2 = spec_data;
            break;

        case SYN_DALI_SPEC_INITIALISE:
            slave->initialise_active = true;
            break;

        case SYN_DALI_SPEC_RANDOMISE:
            if (slave->initialise_active) {
                slave->random_address = ((uint32_t)spec_data << 16) | 0x1234U;
            }
            break;

        case SYN_DALI_SPEC_SEARCHADDRH:
            slave->search_address =
                (slave->search_address & 0x00FFFFU) | ((uint32_t)spec_data << 16);
            break;

        case SYN_DALI_SPEC_SEARCHADDRM:
            slave->search_address =
                (slave->search_address & 0xFF00FFU) | ((uint32_t)spec_data << 8);
            break;

        case SYN_DALI_SPEC_SEARCHADDRL:
            slave->search_address = (slave->search_address & 0xFFFF00U) | (uint32_t)spec_data;
            break;

        case SYN_DALI_SPEC_COMPARE:
            if (slave->initialise_active && slave->random_address <= slave->search_address) {
                *resp_data = 0xFFU;
                *has_resp = true;
            }
            break;

        case SYN_DALI_SPEC_PROGRAM_SHORT_ADDR:
            if (slave->initialise_active) {
                slave->cfg.short_address = (spec_data >> 1) & 0x3FU;
            }
            break;

        case SYN_DALI_SPEC_VERIFY_SHORT_ADDR:
            if (((spec_data >> 1) & 0x3FU) == slave->cfg.short_address) {
                *resp_data = 0xFFU;
                *has_resp = true;
            }
            break;

        case SYN_DALI_SPEC_QUERY_SHORT_ADDR:
            if (slave->cfg.short_address != SYN_DALI_SHORT_ADDR_UNASSIGNED) {
                *resp_data = (slave->cfg.short_address << 1) | 0x01U;
                *has_resp = true;
            } else {
                *resp_data = 0xFFU;
                *has_resp = true;
            }
            break;

        default:
            break;
        }
    }

    return SYN_OK;
}

size_t syn_dali_manchester_encode_byte(uint8_t val, uint8_t *bit_out)
{
    if (bit_out == NULL)
        return 0;

    size_t idx = 0;
    for (int i = 7; i >= 0; i--) {
        bool bit = (val & (1U << i)) != 0U;
        if (bit) {
            /* Logical 1: Low -> High */
            bit_out[idx++] = 0;
            bit_out[idx++] = 1;
        } else {
            /* Logical 0: High -> Low */
            bit_out[idx++] = 1;
            bit_out[idx++] = 0;
        }
    }
    return idx;
}

bool syn_dali_manchester_decode_byte(const uint8_t *bit_in, uint8_t *val_out)
{
    if (bit_in == NULL || val_out == NULL)
        return false;

    uint8_t val = 0;
    size_t idx = 0;

    for (int i = 7; i >= 0; i--) {
        uint8_t b0 = bit_in[idx++];
        uint8_t b1 = bit_in[idx++];

        if (b0 == 0 && b1 == 1) {
            val |= (1U << i); /* Logical 1 */
        } else if (b0 == 1 && b1 == 0) {
            /* Logical 0 */
        } else {
            return false; /* Invalid Manchester bi-phase transition */
        }
    }

    *val_out = val;
    return true;
}

#endif /* SYN_USE_DALI */