Skip to content

Commit

Permalink
Merge pull request #64 from qmsk/leds-artnet-soft-sync
Browse files Browse the repository at this point in the history
Implement soft artnet sync for LEDs
  • Loading branch information
SpComb committed Feb 11, 2024
2 parents 19a4ed8 + 7d8a7a6 commit d5abe73
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 9 deletions.
158 changes: 150 additions & 8 deletions main/leds_artnet.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
#include <logging.h>
#include <leds.h>

#define SYNC_BIT(index) (1 << (index))
#define SYNC_BITS_MASK(count) ((1 << (count)) - 1)

static unsigned config_leds_artnet_universe_leds_count(const struct leds_config *config)
{
unsigned universe_leds_count = config->artnet_dmx_leds;
Expand Down Expand Up @@ -62,6 +65,85 @@ unsigned count_leds_artnet_outputs()
return count;
}

static bool leds_artnet_sync_set(struct leds_state *state, unsigned index)
{
const struct leds_config *config = state->config;
bool update = true;

if (!(state->artnet->sync_bits & SYNC_BIT(index))) {
// mark for sync as normal
state->artnet->sync_bits |= SYNC_BIT(index);
} else {
LOG_DEBUG("missed index=%u", index);

// mark for missed sync
state->artnet->sync_missed |= SYNC_BIT(index);

// delay update to next sync
update = false;
}

if (config->artnet_sync_timeout) {
// set on first update
if (!state->artnet->sync_tick) {
state->artnet->sync_tick = xTaskGetTickCount() + config->artnet_sync_timeout / portTICK_PERIOD_MS;
}
} else {
state->artnet->sync_tick = 0;
}

return update;
}

static bool leds_artnet_sync_check(struct leds_state *state)
{
const struct leds_config *config = state->config;
struct leds_stats *stats = &leds_stats[state->index];

if (!config->artnet_sync_timeout && state->artnet->sync_bits) {
// any output set, free-running
return true;
}

if (state->artnet->sync_bits == SYNC_BITS_MASK(state->artnet->universe_count)) {
// all outputs set
stats_counter_increment(&stats->sync_full);

return true;
}

if (state->artnet->sync_missed) {
// any outputs missed
stats_counter_increment(&stats->sync_missed);

return true;
}

if (state->artnet->sync_tick && xTaskGetTickCount() >= state->artnet->sync_tick) {
// timed out waiting for remaining outputs
stats_counter_increment(&stats->sync_timeout);

return true;
}

// wait for remaining outputs update before sync
return false;
}

static void leds_artnet_sync_reset(struct leds_state *state)
{
const struct leds_config *config = state->config;

state->artnet->sync_bits = 0;

if (state->artnet->sync_missed && config->artnet_sync_timeout) {
// prepare to sync any missed updates per this tick
state->artnet->sync_tick = xTaskGetTickCount() + config->artnet_sync_timeout / portTICK_PERIOD_MS;
} else {
state->artnet->sync_tick = 0;
}
}

void leds_artnet_timeout_reset(struct leds_state *state)
{
const struct leds_config *config = state->config;
Expand All @@ -75,6 +157,15 @@ void leds_artnet_timeout_reset(struct leds_state *state)

TickType_t leds_artnet_wait(struct leds_state *state)
{
if (state->artnet->sync_missed) {
// immediately
return xTaskGetTickCount();
}

if (state->artnet->sync_tick) {
return state->artnet->sync_tick;
}

if (state->artnet->timeout_tick && !leds_test_active(state, 0)) {
// use loss-of-signal timeout
return state->artnet->timeout_tick;
Expand Down Expand Up @@ -152,6 +243,16 @@ bool leds_artnet_active(struct leds_state *state, EventBits_t event_bits)
return true;
}

if (state->artnet->sync_missed) {
return true;
}

if (state->artnet->sync_tick) {
if (xTaskGetTickCount() >= state->artnet->sync_tick) {
return true;
}
}

if (state->artnet->timeout_tick && !leds_test_active(state, 0)) {
if (xTaskGetTickCount() >= state->artnet->timeout_tick) {
return true;
Expand All @@ -163,12 +264,15 @@ bool leds_artnet_active(struct leds_state *state, EventBits_t event_bits)

int leds_artnet_update(struct leds_state *state, EventBits_t event_bits)
{
struct leds_stats *stats = &leds_stats[state->index];
const struct leds_config *config = state->config;

bool data = event_bits & ARTNET_OUTPUT_EVENT_INDEX_BITS;
bool sync = event_bits & (1 << ARTNET_OUTPUT_EVENT_SYNC_BIT);
bool unsync = false;
bool timeout = false;
bool miss = false;

bool sync_mode = false;
bool update = false;

if (state->test) {
if (data || sync) {
Expand All @@ -177,13 +281,35 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits)
}
}

if (data) {
if (state->artnet->sync_missed) {
miss = true;

LOG_DEBUG("event_bits=%08x + sync_missed=%08x", event_bits, state->artnet->sync_missed);

// handle updates with missed sync
event_bits |= state->artnet->sync_missed;

state->artnet->sync_missed = 0;
}

if (data || miss) {
// set output from artnet universe
for (unsigned index = 0; index < state->artnet->universe_count; index++) {
if (!(event_bits & (1 << index))) {
continue;
}

if (leds_artnet_sync_set(state, index)) {
// normal update
} else if (miss) {
LOG_DEBUG("hit index=%u", index);
} else {
LOG_DEBUG("skip index=%u", index);

// skip for update of previous missed sync
continue;
}

if (artnet_output_read(state->artnet->outputs[index], &state->artnet->dmx, 0)) {
// this can race under normal conditions, we have already handled the output
LOG_DEBUG("leds%d: artnet_output[%d] empty", state->index + 1, index);
Expand All @@ -195,13 +321,25 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits)
continue;
}

if (!state->artnet->dmx.sync_mode) {
// at least one DMX update is in non-synchronous mode, so force update
unsync = true;
if (state->artnet->dmx.sync_mode) {
sync_mode = true;
}
}
}

if (sync) {
// hard sync
stats_counter_increment(&stats->artnet_sync);

update = true;
} else if (sync_mode) {
// wait for hard sync
update = false;
} else if (leds_artnet_sync_check(state)) {
// soft sync
update = true;
}

// timeouts
if (data || sync) {
leds_artnet_timeout_reset(state);
Expand All @@ -211,14 +349,18 @@ int leds_artnet_update(struct leds_state *state, EventBits_t event_bits)
LOG_WARN("leds_artnet_timeout");
}

timeout = true;
update = true;

// repeat
leds_artnet_timeout_reset(state);
}
}

return unsync || sync || timeout;
if (update) {
leds_artnet_sync_reset(state);
}

return update;
}

int init_leds_artnet(struct leds_state *state, int index, const struct leds_config *config)
Expand Down
6 changes: 5 additions & 1 deletion main/leds_artnet.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ struct leds_artnet_state {
struct artnet_dmx dmx;
struct artnet_output **outputs;

TickType_t timeout_tick;
unsigned sync_bits; // bitmask of outputs waiting for sync
unsigned sync_missed; // bitmask of outputs with missed sync
TickType_t sync_tick; // tick for soft sync

TickType_t timeout_tick; // tick for forced reset
};

unsigned count_leds_artnet_outputs();
Expand Down
9 changes: 9 additions & 0 deletions main/leds_cmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,13 @@ int leds_cmd_stats(int argc, char **argv, void *ctx)
}

for (unsigned i = 0; i < LEDS_COUNT; i++) {
const struct leds_config *config = &leds_configs[i];
const struct leds_stats *stats = &leds_stats[i];

if (!config->enabled) {
continue;
}

printf("leds%u:\n", i + 1);

print_stats_timer("task", "loop", &stats->loop);
Expand All @@ -360,6 +365,10 @@ int leds_cmd_stats(int argc, char **argv, void *ctx)
print_stats_timer("task", "update", &stats->update);
printf("\n");
print_stats_counter("artnet", "timeout", &stats->artnet_timeout);
print_stats_counter("artnet", "sync", &stats->artnet_sync);
print_stats_counter("sync", "timeout", &stats->sync_timeout);
print_stats_counter("sync", "missed", &stats->sync_missed);
print_stats_counter("sync", "full", &stats->sync_full);
print_stats_counter("update", "timeout", &stats->update_timeout);
printf("\n");
}
Expand Down
1 change: 1 addition & 0 deletions main/leds_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ struct leds_config {
uint16_t artnet_dmx_addr;
uint16_t artnet_dmx_leds;
uint16_t artnet_dmx_timeout;
uint16_t artnet_sync_timeout;
int artnet_leds_format;
uint16_t artnet_leds_segment;
uint16_t artnet_leds_group;
Expand Down
4 changes: 4 additions & 0 deletions main/leds_configtab.i
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ const struct configtab LEDS_CONFIGTAB[] = {
.description = "Clear LED outputs after given milliseconds without any DMX updates. Default 0 -> hold output.",
.uint16_type = { .value = &LEDS_CONFIG.artnet_dmx_timeout },
},
{ CONFIG_TYPE_UINT16, "artnet_sync_timeout",
.description = "Sync LED outputs after given milliseconds from first updated universe, or once all universes updated. Default 0 -> sync on each updated universe.",
.uint16_type = { .value = &LEDS_CONFIG.artnet_sync_timeout },
},
{ CONFIG_TYPE_ENUM, "artnet_leds_format",
.alias = "artnet_mode",
.description = "LED color format for Art-Net DMX channels",
Expand Down
4 changes: 4 additions & 0 deletions main/leds_stats.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ void init_leds_stats()
stats_timer_init(&stats->update);

stats_counter_init(&stats->artnet_timeout);
stats_counter_init(&stats->artnet_sync);
stats_counter_init(&stats->sync_timeout);
stats_counter_init(&stats->sync_missed);
stats_counter_init(&stats->sync_full);
stats_counter_init(&stats->update_timeout);
}
}
5 changes: 5 additions & 0 deletions main/leds_stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ struct leds_stats {

struct stats_timer artnet;
struct stats_counter artnet_timeout;
struct stats_counter artnet_sync;

struct stats_counter sync_timeout;
struct stats_counter sync_missed;
struct stats_counter sync_full;

struct stats_timer sequence;

Expand Down

0 comments on commit d5abe73

Please sign in to comment.