This demo shows how to manually develop a node that exposes a single device — a signal generator with its own properties.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include "../src/nyx_node.h"
static volatile sig_atomic_t s_signo = 0;
static void signal_handler(int signo)
{
s_signo = signo;
}
typedef enum demo_mode_e
{
DEMO_MODE_NOISE = 0,
DEMO_MODE_DELTA = 1,
DEMO_MODE_COMB = 2,
} demo_mode_t;
static demo_mode_t s_mode = DEMO_MODE_NOISE;
static float s_samp_rate = 2000000.0f;
static float s_frequency = 143050000.0f;
static float s_power = -30.0f;
static unsigned int s_fft_size = 512U;
static bool run_callback(__NYX_UNUSED__
nyx_dict_t *vector, __NYX_UNUSED__
nyx_dict_t *prop,
int new_value, __NYX_UNUSED__
int old_value)
{
return true;
}
static bool mode_noise_callback(__NYX_UNUSED__
nyx_dict_t *vector, __NYX_UNUSED__
nyx_dict_t *prop, __NYX_UNUSED__
int new_value, __NYX_UNUSED__
int old_value)
{
{
s_mode = DEMO_MODE_NOISE;
}
return true;
}
static bool mode_delta_callback(__NYX_UNUSED__
nyx_dict_t *vector, __NYX_UNUSED__
nyx_dict_t *prop, __NYX_UNUSED__
int new_value, __NYX_UNUSED__
int old_value)
{
{
s_mode = DEMO_MODE_DELTA;
}
return true;
}
static bool mode_comb_callback(__NYX_UNUSED__
nyx_dict_t *vector, __NYX_UNUSED__
nyx_dict_t *prop, __NYX_UNUSED__
int new_value, __NYX_UNUSED__
int old_value)
{
{
s_mode = DEMO_MODE_COMB;
}
return true;
}
static bool samp_rate_callback(__NYX_UNUSED__
nyx_dict_t *vector, __NYX_UNUSED__
nyx_dict_t *prop,
double new_value, __NYX_UNUSED__
double old_value)
{
s_samp_rate = (float) new_value;
return true;
}
static bool frequency_callback(__NYX_UNUSED__
nyx_dict_t *vector, __NYX_UNUSED__
nyx_dict_t *prop,
double new_value, __NYX_UNUSED__
double old_value)
{
s_frequency = (float) new_value;
return true;
}
static bool power_callback(__NYX_UNUSED__
nyx_dict_t *vector, __NYX_UNUSED__
nyx_dict_t *prop,
double new_value, __NYX_UNUSED__
double old_value)
{
s_power = (float) new_value;
return true;
}
static bool fftsize_callback(__NYX_UNUSED__
nyx_dict_t *vector, __NYX_UNUSED__
nyx_dict_t *prop,
unsigned int new_value, __NYX_UNUSED__
unsigned int old_value)
{
s_fft_size = new_value;
return true;
}
static void gen_noise(float *dst, size_t n, float mean_db)
{
for(size_t i = 0; i < n; i++)
{
float u = (float) rand() / (float) RAND_MAX;
float v = (float) rand() / (float) RAND_MAX;
float w = (u + v - 1.0f) * 6.0f;
dst[i] = mean_db + w;
}
}
static void gen_delta(float *dst, size_t n, float mean_db)
{
gen_noise(dst, n, mean_db);
dst[n / 2U] += 20.0f;
}
static void gen_comb(float *dst, size_t n, float mean_db)
{
gen_noise(dst, n, mean_db);
size_t step = n / 8u;
for(size_t i = step / 2u; i < n; i += step)
{
dst[i] += 20.0f;
}
}
static void timer_stream(__NYX_UNUSED__ void *arg)
{
{
return;
}
static float spectrum[4096];
const size_t n = (s_fft_size <= 4096U) ? (size_t) s_fft_size
: (size_t) 4096U
;
switch(s_mode)
{
case DEMO_MODE_NOISE: gen_noise(spectrum, n, s_power); break;
case DEMO_MODE_DELTA: gen_delta(spectrum, n, s_power); break;
default: gen_comb(spectrum, n, s_power); break;
}
size_t sizes[] = {sizeof(s_samp_rate), sizeof(s_frequency), n * sizeof(float)};
BUFF_t buffs[] = {&s_samp_rate , &s_frequency , spectrum };
nyx_stream_pub(stream_vector, 3, sizes, buffs);
}
static void mqtt_callback(
nyx_event_type_t event_type,
size_t topic_size,
__NYX_UNUSED__ size_t message_size,
__NYX_UNUSED__
BUFF_t message_buff
) {
if(event_type == NYX_NODE_EVENT_OPEN)
{
nyx_mqtt_sub(node, "demo/exit", 0);
}
else if(event_type == NYX_NODE_EVENT_MSG)
{
if(topic_size == 9 && memcmp(topic_buff, "demo/exit", 9) == 0)
{
s_signo = 1;
}
}
}
int main()
{
.group = "Demo"
};
run_prop->base.callback._int = run_callback;
"Demo",
"run",
run_props,
&opt
);
mode_noise_prop->base.callback._int = mode_noise_callback;
mode_delta_prop->base.callback._int = mode_delta_callback;
mode_comb_prop->base.callback._int = mode_comb_callback;
nyx_dict_t *mode_props[] = {mode_noise_prop, mode_delta_prop, mode_comb_prop, NULL};
"Demo",
"signal_mode",
mode_props,
&opt
);
nyx_dict_t *samp_rate_prop = nyx_number_prop_new_double(
"samp_rate",
"Sample rate [Hz]",
"%.0f", 1000.0, 50000000.0, 1000.0, s_samp_rate);
nyx_dict_t *frequency_prop = nyx_number_prop_new_double(
"frequency",
"Frequency [Hz]",
"%.0f", 1000000.0, 2000000000.0, 1000.0, s_frequency);
nyx_dict_t *power_prop = nyx_number_prop_new_double(
"power",
"Power (dB)",
"%.1f", -150.0, 20.0, 1.0, s_power);
samp_rate_prop->base.callback._double = samp_rate_callback;
frequency_prop->base.callback._double = frequency_callback;
power_prop->base.callback._double = power_callback;
nyx_dict_t *signal_props[] = {samp_rate_prop, frequency_prop, power_prop, NULL};
nyx_dict_t *signal_vector = nyx_number_vector_new(
"Demo",
"signal_params",
signal_props,
&opt
);
nyx_dict_t *fft_size_prop = nyx_number_prop_new_uint(
"fft_size",
"FFT size",
"%u", 1U, 4096U, 1U, s_fft_size);
fft_size_prop->base.callback._uint = fftsize_callback;
"Demo",
"fft_params",
fft_props,
&opt
);
nyx_dict_t *stream_samp_rate_prop = nyx_stream_prop_new(
"samp_rate",
"Sample rate [Hz]");
nyx_dict_t *stream_frequency_prop = nyx_stream_prop_new(
"frequency",
"Frequency [Hz]");
nyx_dict_t *stream_samples_prop = nyx_stream_prop_new(
"samples",
"Samples");
stream_samp_rate_prop,
stream_frequency_prop,
stream_samples_prop,
NULL,
};
stream_vector = nyx_stream_vector_new(
"Demo",
"spectrum",
spectrum_props,
&opt
);
run_vector,
mode_vector,
signal_vector,
fft_vector,
stream_vector,
NULL,
};
"NYX_DEMO",
vector_list,
"tcp://0.0.0.0:7625",
getenv("MQTT_URL"),
getenv("STREAM_URL"),
getenv("MQTT_USERNAME"),
getenv("MQTT_PASSWORD"),
mqtt_callback,
3000,
true
);
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
nyx_node_add_timer(node, 50, timer_stream, NULL);
while(s_signo == 0)
{
nyx_node_poll(node, 25);
}
nyx_node_finalize(node, true);
printf("Bye.\n");
return 0;
}
Struct describing a JSON dict object.
void nyx_set_log_level(nyx_log_level_t level)
Sets the log level threshold.
@ NYX_LOG_LEVEL_DEBUG
Debug level.
#define BUFF_t
Alias for const void *.
void nyx_memory_initialize(void)
Initializes the memory subsystem.
bool nyx_memory_finalize(void)
Finalizes the memory subsystem.
Opaque struct describing a Nyx node.
@ NYX_PERM_RW
Read & write.
@ NYX_STATE_OK
State is ok.
@ NYX_ONOFF_ON
Switch is ON.
@ NYX_ONOFF_OFF
Switch is OFF.
@ NYX_RULE_ONE_OF_MANY
Only one switch of many can be ON (e.g., radio buttons).
@ NYX_RULE_AT_MOST_ONE
At most one switch can be ON, but all switches can be OFF.
Struct describing the options for INDI / Nyx vectors.
import os
import sys
import random
import signal
import struct
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import nyx
def main():
MODE_NOISE = 0
MODE_DELTA = 1
MODE_COMB = 2
run = nyx.NyxOnOff.OFF
mode = MODE_NOISE
samp_rate = 2000000.0
frequency = 143050000.0
power = -30.0
fft_size = 512
stop = False
def gen_noise(n: int, mean_db: float) -> list[float]:
return [mean_db + (random.random() + random.random() - 1.0) * 6.0 for _ in range(n)]
def gen_delta(n: int, mean_db: float) -> list[float]:
spectrum = gen_noise(n, mean_db)
spectrum[n // 2] += 20.0
return spectrum
def gen_comb(n: int, mean_db: float) -> list[float]:
spectrum = gen_noise(n, mean_db)
step = max(n // 8, 1)
for index in range(step // 2, n, step):
spectrum[index] += 20.0
return spectrum
def get_spectrum() -> list[float]:
n = min(fft_size, 4096)
if mode == MODE_NOISE:
return gen_noise(n, power)
if mode == MODE_DELTA:
return gen_delta(n, power)
return gen_comb(n, power)
nyx.nyx_set_log_level(nyx.NyxLogLevel.DEBUG)
'run',
'Run',
nyx.NyxOnOff.OFF,
)
@run_prop.on
def on_run_changed(new_value, _old_value):
nonlocal run
run = new_value
return True
'Demo',
'run',
nyx.NyxState.OK,
nyx.NyxPerm.RW,
nyx.NyxRule.AT_MOST_ONE,
[run_prop],
group = 'Demo',
)
'mode_noise',
'Noise only',
nyx.NyxOnOff.ON,
)
@mode_noise_prop.on
def on_mode_noise_changed(new_value, _old_value):
nonlocal mode
if new_value == nyx.NyxOnOff.ON:
mode = MODE_NOISE
return True
'mode_delta',
'Dirac delta',
nyx.NyxOnOff.OFF,
)
@mode_delta_prop.on
def on_mode_delta_changed(new_value, _old_value):
nonlocal mode
if new_value == nyx.NyxOnOff.ON:
mode = MODE_DELTA
return True
'mode_comb',
'Dirac comb',
nyx.NyxOnOff.OFF,
)
@mode_comb_prop.on
def on_mode_comb_changed(new_value, _old_value):
nonlocal mode
if new_value == nyx.NyxOnOff.ON:
mode = MODE_COMB
return True
'Demo',
'signal_mode',
nyx.NyxState.OK,
nyx.NyxPerm.RW,
nyx.NyxRule.ONE_OF_MANY,
[
mode_noise_prop,
mode_delta_prop,
mode_comb_prop,
],
group = 'Demo',
)
'samp_rate',
'Sample rate [Hz]',
'%.0f',
1000.0,
50000000.0,
1000.0,
samp_rate,
)
@samp_rate_prop.on
def on_samp_rate_changed(new_value, _old_value):
nonlocal samp_rate
samp_rate = new_value
return True
'frequency',
'Frequency [Hz]',
'%.0f',
1000000.0,
2000000000.0,
1000.0,
frequency,
)
@frequency_prop.on
def on_frequency_changed(new_value, _old_value):
nonlocal frequency
frequency = new_value
return True
'power',
'Power (dB)',
'%.1f',
-150.0,
20.0,
1.0,
power,
)
@power_prop.on
def on_power_changed(new_value, _old_value):
nonlocal power
power = new_value
return True
'Demo',
'signal_params',
nyx.NyxState.OK,
nyx.NyxPerm.RW,
[
samp_rate_prop,
frequency_prop,
power_prop,
],
group = 'Demo',
)
'fft_size',
'FFT size',
'%u',
1,
4096,
1,
fft_size,
)
@fft_size_prop.on
def on_fft_size_changed(new_value, _old_value):
nonlocal fft_size
fft_size = new_value
return True
'Demo',
'fft_params',
nyx.NyxState.OK,
nyx.NyxPerm.RW,
[fft_size_prop],
group = 'Demo',
)
'samp_rate',
'Sample rate [Hz]',
)
'frequency',
'Frequency [Hz]',
)
'samples',
'Samples',
)
'Demo',
'spectrum',
nyx.NyxState.OK,
[
stream_samp_rate_prop,
stream_frequency_prop,
stream_samples_prop,
],
group = 'Demo',
)
'NYX_DEMO_PY',
[
mode_vector,
run_vector,
signal_vector,
fft_vector,
stream_vector,
],
'tcp://0.0.0.0:7625',
os.getenv('MQTT_URL'),
os.getenv('STREAM_URL'),
os.getenv('MQTT_USERNAME'),
os.getenv('MQTT_PASSWORD'),
3000,
True
) as node:
@node.on_timer(50)
def on_timer():
if run == nyx.NyxOnOff.ON:
spectrum = get_spectrum()
stream_vector.stream_pub([
struct.pack('=f', samp_rate),
struct.pack('=f', frequency),
struct.pack(f'={len(spectrum)}f', *spectrum),
])
@node.on_mqtt(nyx.NyxMQTTEvent.OPEN)
def on_mqtt_open():
node.mqtt_sub('demo/exit')
@node.on_mqtt(nyx.NyxMQTTEvent.MSG)
def on_mqtt_msg(topic, _message):
nonlocal stop
if topic == 'demo/exit':
stop = True
stop = False
def signal_handler(_signo, _frame):
nonlocal stop
stop = True
signal.signal(signal.SIGINT, signal_handler)
while not stop:
node.poll(25)
print('Bye.')
return 0
if __name__ == '__main__':
sys.exit(main())
Nyx node exposing INDI, MQTT and Nyx Stream endpoints.
INDI / Nyx double number property.
INDI / Nyx uint32_t number property.
INDI / Nyx number vector.
INDI / Nyx switch property.
INDI / Nyx switch vector.