11#include "nyx_node_internal.h"
24static bool _starts_with(
const nyx_str_t topic,
const nyx_str_t prefix)
26 return topic.len >= prefix.len && memcmp(topic.buf, prefix.buf, prefix.len) == 0;
33#define NYX_C_STR(a) {(str_t) (a), sizeof(a) - 1}
35static const nyx_str_t SPECIAL_TOPICS[] = {
36 NYX_C_STR(
"nyx/cmd/trigger_ping"),
37 NYX_C_STR(
"nyx/cmd/set_master_client"),
38 NYX_C_STR(
"nyx/cmd/json"),
39 NYX_C_STR(
"nyx/cmd/xml"),
58 str_t xml = nyx_xmldoc_to_string(xmldoc);
59 internal_mqtt_pub(node, nyx_str_s(
"nyx/xml"), nyx_str_s(xml), 2);
60 internal_indi_pub(node, nyx_str_s(xml));
65 nyx_xmldoc_free(xmldoc);
75 str_t json = nyx_object_to_string(
object);
76 internal_mqtt_pub(node, nyx_str_s(
"nyx/json"), nyx_str_s(json), 2);
96 device1 = nyx_dict_get_string(dict,
"@device");
97 name1 = nyx_dict_get_string(dict,
"@name");
107 for(
nyx_dict_t **vector_ptr = node->vectors; *vector_ptr != NULL; vector_ptr++)
111 if((vector->base.flags & NYX_FLAGS_DISABLED) == 0)
115 STR_t device2 = nyx_dict_get_string(vector,
"@device");
116 STR_t name2 = nyx_dict_get_string(vector,
"@name");
120 if(device2 != NULL && name2 != NULL)
126 if(strcmp(device1, device2) != 0)
133 if(strcmp(name1, name2) != 0)
169 for(
size_t i = 0; i <
sizeof(node->client_hashes) /
sizeof(uint32_t); i++)
171 if(node->client_hashes[i] == 0x00
173 node->client_hashes[i] == hash
175 node->client_hashes[i] = hash;
192 STR_t client = nyx_dict_get_string(dict,
"@client");
194 int index = _get_client_index(node, client);
205 STR_t device1 = nyx_dict_get_string(dict,
"@device");
206 STR_t name1 = nyx_dict_get_string(dict,
"@name");
207 STR_t value1 = nyx_dict_get_string(dict,
"$");
211 int value = str_to_xxx(value1);
215 for(
nyx_dict_t **vector_ptr = node->vectors; *vector_ptr != NULL; vector_ptr++)
221 STR_t device2 = nyx_dict_get_string(vector,
"@device");
222 STR_t name2 = nyx_dict_get_string(vector,
"@name");
223 STR_t tag2 = nyx_dict_get_string(vector,
"<>");
229 if(device2 == NULL || strcmp(device1, device2) != 0)
236 if(name2 == NULL || strcmp(name1, name2) != 0)
245 if(tag2 != NULL && strcmp(tag, tag2) == 0)
255 case NYX_BLOB_STATE_ENABLED:
256 vector->base.flags |= UINT64_C(1) << (2 + 0 * 31 + index);
259 case NYX_BLOB_STATE_DISABLED:
260 vector->base.flags &= ~(UINT64_C(1) << (2 + 0 * 31 + index));
267 case NYX_STREAM_STATE_ENABLED:
268 vector->base.flags |= UINT64_C(1) << (2 + 1 * 31 + index);
271 case NYX_STREAM_STATE_DISABLED:
272 vector->base.flags &= ~(UINT64_C(1) << (2 + 1 * 31 + index));
287 NYX_LOG_DEBUG(
"%s:%s %s", device2, name2, (vector->base.flags & mask) == 0 ?
"disabled" :
"enabled");
302 _enable_xxx(node, dict,
"defBLOBVector", (
int (*)(
STR_t)) &nyx_str_to_blob_state, NYX_FLAGS_BLOB_MASK);
309 _enable_xxx(node, dict,
"defStreamVector", (
int (*)(
STR_t)) &nyx_str_to_stream_state, NYX_FLAGS_STREAM_MASK);
318 STR_t client1 = node->master_client_message.buf;
320 if(client1 != NULL && strcmp(NYX_ALL, client1) == 0)
327 STR_t client2 = nyx_dict_get_string(dict,
"@client");
329 if(client1 != NULL && client2 != NULL && strcmp(client1, client2) == 0)
345 .value = (
str_t)
"Off",
352 if(!_is_allowed(node, dict))
360 nyx_object_t *device1_string = nyx_dict_get(dict,
"@device");
361 nyx_object_t *name1_string = nyx_dict_get(dict,
"@name");
362 nyx_object_t *children1_list = nyx_dict_get(dict,
"children");
372 children1_list != NULL && children1_list->type ==
NYX_TYPE_LIST
382 for(
nyx_dict_t **vector_ptr = node->vectors; *vector_ptr != NULL; vector_ptr++)
389 nyx_object_t *device2_string = nyx_dict_get(vector,
"@device");
390 nyx_object_t *name2_string = nyx_dict_get(vector,
"@name");
391 nyx_object_t *children2_list = nyx_dict_get(vector,
"children");
401 children2_list != NULL && children2_list->type ==
NYX_TYPE_LIST
411 if(strlen(tag1) > 3 && strlen(tag2) > 3
413 strcmp(tag1 + 3, tag2 + 3) == 0
415 strcmp(device1, device2) == 0
417 strcmp(name1, name2) == 0
425 bool vector_modified =
false;
427 uint32_t hash =
nyx_hash(strlen(tag2), tag2, 0);
431 STR_t rule = nyx_dict_get_string(vector,
"@rule");
433 bool is_one_of_many = rule != NULL && strcmp(rule,
"OneOfMany") == 0;
461 bool is_current = strcmp(prop1, prop2) == 0;
463 if(is_current || is_one_of_many)
474 bool success =
false;
475 bool modified =
false;
489 nyx_variant_t old_val = internal_string_to_variant(format, nyx_string_get((
nyx_string_t *) old_value));
490 nyx_variant_t new_val = internal_string_to_variant(format, nyx_string_get((
nyx_string_t *) new_value));
494 case NYX_VARIANT_TYPE_INT:
495 if((success = object2->callback._int == NULL || object2->callback._int(vector, (
nyx_dict_t *) object2, new_val.value._int, old_val.value._int))) {
496 modified = nyx_dict_set_string((
nyx_dict_t *) object2,
"$", internal_variant_to_string(format, new_val),
true);
499 case NYX_VARIANT_TYPE_UINT:
500 if((success = object2->callback._uint == NULL || object2->callback._uint(vector, (
nyx_dict_t *) object2, new_val.value._uint, old_val.value._uint))) {
501 modified = nyx_dict_set_string((
nyx_dict_t *) object2,
"$", internal_variant_to_string(format, new_val),
true);
504 case NYX_VARIANT_TYPE_LONG:
505 if((success = object2->callback._long == NULL || object2->callback._long(vector, (
nyx_dict_t *) object2, new_val.value._long, old_val.value._long))) {
506 modified = nyx_dict_set_string((
nyx_dict_t *) object2,
"$", internal_variant_to_string(format, new_val),
true);
509 case NYX_VARIANT_TYPE_ULONG:
510 if((success = object2->callback._ulong == NULL || object2->callback._ulong(vector, (
nyx_dict_t *) object2, new_val.value._ulong, old_val.value._ulong))) {
511 modified = nyx_dict_set_string((
nyx_dict_t *) object2,
"$", internal_variant_to_string(format, new_val),
true);
514 case NYX_VARIANT_TYPE_DOUBLE:
515 if((success = object2->callback._double == NULL || object2->callback._double(vector, (
nyx_dict_t *) object2, new_val.value._double, old_val.value._double))) {
516 modified = nyx_dict_set_string((
nyx_dict_t *) object2,
"$", internal_variant_to_string(format, new_val),
true);
532 if((success = object2->callback._str == NULL || object2->callback._str(vector, (
nyx_dict_t *) object2, new_val, old_val)))
547 if((success = object2->callback._int == NULL || object2->callback._int(vector, (
nyx_dict_t *) object2, (
int) new_val, (
int) old_val)))
549 modified = nyx_dict_set_string((
nyx_dict_t *) object2,
"$", nyx_state_to_str(new_val),
false);
562 if((success = object2->callback._int == NULL || object2->callback._int(vector, (
nyx_dict_t *) object2, (
int) new_val, (
int) old_val)))
564 modified = nyx_dict_set_string((
nyx_dict_t *) object2,
"$", nyx_onoff_to_str(new_val),
false);
578 nyx_string_get_buff((
nyx_string_t *) new_value, &src_size, &src_buff);
585 if(internal_blob_is_compressed((
nyx_dict_t *) object2)) {
594 if((success = object2->callback._buffer == NULL || object2->callback._buffer(vector, (
nyx_dict_t *) object2, dst_size, dst_buff)))
596 modified = nyx_dict_set_buff((
nyx_dict_t *) object2,
"$", dst_size, dst_buff,
true);
621 str_t str = nyx_object_to_string(object2);
622 NYX_LOG_DEBUG(
"Updating (modified: %s) `%s::%s` with %s", modified ?
"true" :
"false", device1, name1, str);
628 vector_modified = vector_modified || modified;
645 if(vector->base.callback._vector != NULL) vector->base.callback._vector(vector, vector_modified);
647 nyx_object_notify(&vector->base);
676 if(strcmp(tag,
"getProperties") == 0)
680 else if(strcmp(tag,
"enableBLOB") == 0)
684 else if(strcmp(tag,
"enableStream") == 0)
688 else if(strcmp(tag,
"newNumberVector") == 0
690 strcmp(tag,
"newTextVector") == 0
692 strcmp(tag,
"newLightVector") == 0
694 strcmp(tag,
"newSwitchVector") == 0
696 strcmp(tag,
"newBLOBVector") == 0
708static size_t _tcp_handler(
nyx_node_t *node, nyx_event_type_t event_type,
const nyx_str_t payload)
714 if(event_type == NYX_NODE_EVENT_MSG)
716 nyx_xml_stream_t xml_stream = NYX_XML_STREAM();
718 if(nyx_xml_stream_detect_opening_tag(&xml_stream, payload.len, payload.buf))
720 if(nyx_xml_stream_detect_closing_tag(&xml_stream, payload.len, payload.buf))
724 nyx_xmldoc_t *xmldoc = nyx_xmldoc_parse_buff(xml_stream.len, xml_stream.s_ptr);
732 _process_message(node,
object);
734 nyx_object_unref(
object);
737 nyx_xmldoc_free(xmldoc);
742 return xml_stream.pos + xml_stream.len;
758static void _mqtt_handler(
nyx_node_t *node, nyx_event_type_t event_type,
const nyx_str_t event_topic,
const nyx_str_t event_payload)
764 if(event_type == NYX_NODE_EVENT_OPEN)
766 for(
size_t i = 0; i <
sizeof(SPECIAL_TOPICS) /
sizeof(nyx_str_t); i++)
770 if(sprintf(topic,
"%s/%s", SPECIAL_TOPICS[i].buf, node->node_id.buf) > 0)
773 SPECIAL_TOPICS[i].buf,
777 internal_mqtt_sub(node, SPECIAL_TOPICS[i], 2);
779 internal_mqtt_sub(node, nyx_str_s(topic), 2);
787 if(node->user_mqtt_handler != NULL)
789 node->user_mqtt_handler(
799 _get_properties(node, NULL);
808 else if(event_type == NYX_NODE_EVENT_MSG)
810 if(event_topic.len > 0 && event_topic.buf != NULL)
816 if(_starts_with(event_topic, SPECIAL_TOPICS[0]))
828 if(event_payload.len > 0 && event_payload.buf != NULL)
830 if(_starts_with(event_topic, SPECIAL_TOPICS[1]))
840 node->master_client_message.buf =
nyx_string_ndup(event_payload.buf, node->master_client_message.len = event_payload.len);
844 else if(_starts_with(event_topic, SPECIAL_TOPICS[2]))
850 nyx_object_t *
object = nyx_object_parse_buff(event_payload.len, event_payload.buf);
854 _process_message(node,
object);
856 nyx_object_unref(
object);
861 else if(_starts_with(event_topic, SPECIAL_TOPICS[3]))
866 #if !defined(ARDUINO)
869 nyx_xmldoc_t *xmldoc = nyx_xmldoc_parse_buff(event_payload.len, event_payload.buf);
877 _process_message(node,
object);
879 nyx_object_unref(
object);
882 nyx_xmldoc_free(xmldoc);
896 if(node->user_mqtt_handler != NULL)
898 node->user_mqtt_handler(
928 nyx_mqtt_handler_t mqtt_handler,
945 for(
nyx_dict_t **vector_ptr = vectors; *vector_ptr != NULL; vector_ptr++)
951 nyx_dict_set_string(vector,
"@client",
nyx_string_dup(node_id),
true);
955 nyx_object_t *children = nyx_dict_get(vector,
"children");
965 vector_def->node = node;
969 vector->base.node = node;
984 if(sprintf(master_client_topic,
"nyx/master_client/%s", node->node_id.buf) > 0)
988 node->master_client_topic = nyx_str_s(master_client_topic);
993 node->indi_url = _safe_dup(indi_url);
994 node->mqtt_url = _safe_dup(mqtt_url);
995 node->nss_url = _safe_dup(nss_url);
997 node->mqtt_username = _safe_dup(mqtt_username);
998 node->mqtt_password = _safe_dup(mqtt_password);
1002 node->enable_xml = enable_xml;
1006 node->vectors = vectors;
1010 #if !defined(ARDUINO)
1011 node->tcp_handler = _tcp_handler;
1013 node->mqtt_handler = _mqtt_handler;
1015 node->user_mqtt_handler = mqtt_handler;
1021 internal_stack_initialize(node, retry_ms);
1038 internal_stack_finalize(node);
1046 for(
nyx_dict_t **vector_ptr = node->vectors; *vector_ptr != NULL; vector_ptr++)
1048 nyx_object_unref(*vector_ptr);
1083 internal_mqtt_pub(node, nyx_str_s(
"nyx/ping/node"), node->node_id, 0);
1085 internal_mqtt_pub(node, node->master_client_topic, node->master_client_message, 0);
1092 if(object->node != NULL && object->type ==
NYX_TYPE_DICT && (object->flags & NYX_FLAGS_DISABLED) == 0)
1096 STR_t tag = nyx_dict_get_string(vector,
"<>");
1104 if(strcmp(
"defNumberVector", tag) == 0) {
1105 set_vector = nyx_number_set_vector_new(vector);
1107 else if(strcmp(
"defTextVector", tag) == 0) {
1108 set_vector = nyx_text_set_vector_new(vector);
1110 else if(strcmp(
"defLightVector", tag) == 0) {
1111 set_vector = nyx_light_set_vector_new(vector);
1113 else if(strcmp(
"defSwitchVector", tag) == 0) {
1114 set_vector = nyx_switch_set_vector_new(vector);
1116 else if(strcmp(
"defStreamVector", tag) == 0) {
1117 set_vector = nyx_stream_set_vector_new(vector);
1119 else if(strcmp(
"defBLOBVector", tag) == 0) {
1121 if((vector->base.flags & NYX_FLAGS_BLOB_MASK) != 0) {
1123 set_vector = nyx_blob_set_vector_new(vector);
1135 STR_t perm = nyx_dict_get_string(vector,
"@perm");
1137 bool is_not_wo = perm == NULL || strcmp(perm,
"wo") != 0;
1139 if(is_not_wo) _sub_object(object->node, (
nyx_object_t *) set_vector);
1143 nyx_object_unref(&set_vector->base);
1160 if(node != NULL && device != NULL)
1164 for(
nyx_dict_t **vector_ptr = node->vectors; *vector_ptr != NULL; vector_ptr++)
1170 STR_t device2 = nyx_dict_get_string(vector,
"@device");
1171 STR_t name2 = nyx_dict_get_string(vector,
"@name");
1175 if(device2 == NULL || strcmp(device, device2) != 0)
1182 if(name2 == NULL || strcmp(name, name2) != 0)
1193 vector->base.flags |= NYX_FLAGS_DISABLED;
1197 vector->base.flags &= ~NYX_FLAGS_DISABLED;
1210 nyx_dict_t *del_property_new = nyx_del_property_new(device, name, message);
1214 nyx_dict_free(del_property_new);
1225 _device_onoff(node, device, name, message,
NYX_ONOFF_ON);
1239 nyx_dict_t *dict = nyx_message_new(device, message);
1243 nyx_dict_free(dict);
1250 nyx_dict_t *dict = nyx_del_property_new(device, name, message);
1254 nyx_dict_free(dict);
Struct describing a JSON dict object.
#define NYX_LIST_ITER(list)
Initializes a JSON list iterator.
Struct describing a JSON list iterator.
Struct describing a JSON list object.
#define NYX_LOG_FATAL(fmt,...)
Logs a fatal message.
#define NYX_LOG_DEBUG(fmt,...)
Logs a debug message.
#define NYX_LOG_INFO(fmt,...)
Logs an info message.
#define NYX_LOG_ERROR(fmt,...)
Logs an error message.
#define STR_t
Alias for const char *.
__NYX_NULLABLE__ buff_t nyx_memory_alloc(__NYX_ZEROABLE__ size_t size)
Similar to libc malloc except that a memory overflow causes the node to stop.
__NYX_ZEROABLE__ size_t nyx_memory_free(__NYX_NULLABLE__ buff_t buff)
Similar to libc free except that it returns the amount of memory freed.
#define buff_t
Alias for void *.
__NYX_NULLABLE__ str_t nyx_string_dup(__NYX_NULLABLE__ STR_t s)
Similar to libc strdup.
__NYX_NULLABLE__ str_t nyx_string_ndup(__NYX_NULLABLE__ STR_t s, __NYX_ZEROABLE__ size_t n)
Similar to libc strndup.
#define str_t
Alias for char *.
void nyx_node_finalize(nyx_node_t *node, bool free_vectors)
Finalizes a Nyx node.
Opaque struct describing a Nyx node.
nyx_state_t
Vector state hint.
@ NYX_ONOFF_ON
Switch is ON.
@ NYX_ONOFF_OFF
Switch is OFF.
#define NYX_OBJECT_MAGIC
Magic number for identifying JSON objects.
@ NYX_TYPE_DICT
Dict object.
@ NYX_TYPE_LIST
List object.
@ NYX_TYPE_STRING
String object.
Struct describing a JSON object.
Struct describing a JSON string object.
uint32_t nyx_hash(__NYX_ZEROABLE__ size_t size, __NYX_NULLABLE__ BUFF_t buff, uint32_t seed)
Hashes a buffer using the MurmurHash2 algorithm.
__NYX_NULLABLE__ buff_t nyx_zlib_base64_inflate(__NYX_NOTNULL__ size_t *result_size, __NYX_ZEROABLE__ size_t len, __NYX_NULLABLE__ STR_t str)
Decompresses a string using the ZLib+Base64 algorithm.
__NYX_NULLABLE__ buff_t nyx_base64_decode(__NYX_NULLABLE__ size_t *result_size, __NYX_ZEROABLE__ size_t len, __NYX_NULLABLE__ STR_t str)
Decodes a string using the Base64 algorithm.
Struct describing an XML document.