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_t *) old_value);
490 nyx_variant_t new_val = internal_string_to_variant(format, (
nyx_string_t *) new_value);
494 case NYX_VARIANT_TYPE_INT:
495 if((success = object2->in_callback._int == NULL || object2->in_callback._int(vector, (
nyx_dict_t *) object2, new_val.value._int, old_val.value._int))) {
496 modified = nyx_dict_set((
nyx_dict_t *) object2,
"$", internal_variant_to_string(format, new_val));
499 case NYX_VARIANT_TYPE_UINT:
500 if((success = object2->in_callback._uint == NULL || object2->in_callback._uint(vector, (
nyx_dict_t *) object2, new_val.value._uint, old_val.value._uint))) {
501 modified = nyx_dict_set((
nyx_dict_t *) object2,
"$", internal_variant_to_string(format, new_val));
504 case NYX_VARIANT_TYPE_LONG:
505 if((success = object2->in_callback._long == NULL || object2->in_callback._long(vector, (
nyx_dict_t *) object2, new_val.value._long, old_val.value._long))) {
506 modified = nyx_dict_set((
nyx_dict_t *) object2,
"$", internal_variant_to_string(format, new_val));
509 case NYX_VARIANT_TYPE_ULONG:
510 if((success = object2->in_callback._ulong == NULL || object2->in_callback._ulong(vector, (
nyx_dict_t *) object2, new_val.value._ulong, old_val.value._ulong))) {
511 modified = nyx_dict_set((
nyx_dict_t *) object2,
"$", internal_variant_to_string(format, new_val));
514 case NYX_VARIANT_TYPE_DOUBLE:
515 if((success = object2->in_callback._double == NULL || object2->in_callback._double(vector, (
nyx_dict_t *) object2, new_val.value._double, old_val.value._double))) {
516 modified = nyx_dict_set((
nyx_dict_t *) object2,
"$", internal_variant_to_string(format, new_val));
532 if((success = object2->in_callback._str == NULL || object2->in_callback._str(vector, (
nyx_dict_t *) object2, new_val, old_val)))
534 modified = nyx_dict_set((
nyx_dict_t *) object2,
"$", nyx_string_from_dup(new_val));
547 if((success = object2->in_callback._int == NULL || object2->in_callback._int(vector, (
nyx_dict_t *) object2, (
int) new_val, (
int) old_val)))
549 modified = nyx_dict_set((
nyx_dict_t *) object2,
"$", nyx_string_from_unmanaged(nyx_state_to_str(new_val)));
562 if((success = object2->in_callback._int == NULL || object2->in_callback._int(vector, (
nyx_dict_t *) object2, (
int) new_val, (
int) old_val)))
564 modified = nyx_dict_set((
nyx_dict_t *) object2,
"$", nyx_string_from_unmanaged(nyx_onoff_to_str(new_val)));
579 nyx_string_get_buff((
nyx_string_t *) new_value, &src_size, &src_buff);
586 if(internal_blob_is_compressed((
nyx_dict_t *) object2)) {
595 if((success = object2->in_callback._buffer == NULL || object2->in_callback._buffer(vector, (
nyx_dict_t *) object2, dst_size, dst_buff)))
597 modified = nyx_dict_set((
nyx_dict_t *) object2,
"$", nyx_string_from_buff_managed(dst_size, dst_buff));
622 str_t str = nyx_object_to_string(object2);
623 NYX_LOG_DEBUG(
"Updating (modified: %s) `%s::%s` with %s", modified ?
"true" :
"false", device1, name1, str);
629 vector_modified = vector_modified || modified;
646 if(vector->base.in_callback._vector != NULL) vector->base.in_callback._vector(vector, vector_modified);
648 nyx_node_notify(&vector->base);
677 if(strcmp(tag,
"getProperties") == 0)
681 else if(strcmp(tag,
"enableBLOB") == 0)
685 else if(strcmp(tag,
"enableStream") == 0)
689 else if(strcmp(tag,
"newNumberVector") == 0
691 strcmp(tag,
"newTextVector") == 0
693 strcmp(tag,
"newLightVector") == 0
695 strcmp(tag,
"newSwitchVector") == 0
697 strcmp(tag,
"newBLOBVector") == 0
709static size_t _tcp_handler(
nyx_node_t *node, nyx_event_type_t event_type,
const nyx_str_t payload)
715 if(event_type == NYX_NODE_EVENT_MSG)
717 nyx_xml_stream_t xml_stream = NYX_XML_STREAM();
719 if(nyx_xml_stream_detect_opening_tag(&xml_stream, payload.len, payload.buf))
721 if(nyx_xml_stream_detect_closing_tag(&xml_stream, payload.len, payload.buf))
725 nyx_xmldoc_t *xmldoc = nyx_xmldoc_parse_buff(xml_stream.len, xml_stream.s_ptr);
733 _process_message(node,
object);
735 nyx_object_free(
object);
738 nyx_xmldoc_free(xmldoc);
743 return xml_stream.pos + xml_stream.len;
759static 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)
765 if(event_type == NYX_NODE_EVENT_OPEN)
767 for(
size_t i = 0; i <
sizeof(SPECIAL_TOPICS) /
sizeof(nyx_str_t); i++)
771 if(sprintf(topic,
"%s/%s", SPECIAL_TOPICS[i].buf, node->node_id.buf) > 0)
774 SPECIAL_TOPICS[i].buf,
778 internal_mqtt_sub(node, SPECIAL_TOPICS[i], 2);
780 internal_mqtt_sub(node, nyx_str_s(topic), 2);
788 if(node->user_mqtt_handler != NULL)
790 node->user_mqtt_handler(
800 _get_properties(node, NULL);
809 else if(event_type == NYX_NODE_EVENT_MSG)
811 if(event_topic.len > 0 && event_topic.buf != NULL)
817 if(_starts_with(event_topic, SPECIAL_TOPICS[0]))
829 if(event_payload.len > 0 && event_payload.buf != NULL)
831 if(_starts_with(event_topic, SPECIAL_TOPICS[1]))
841 node->master_client_message.buf =
nyx_string_ndup(event_payload.buf, node->master_client_message.len = event_payload.len);
845 else if(_starts_with(event_topic, SPECIAL_TOPICS[2]))
851 nyx_object_t *
object = nyx_object_parse_buff(event_payload.len, event_payload.buf);
855 _process_message(node,
object);
857 nyx_object_free(
object);
862 else if(_starts_with(event_topic, SPECIAL_TOPICS[3]))
867 #if !defined(ARDUINO)
870 nyx_xmldoc_t *xmldoc = nyx_xmldoc_parse_buff(event_payload.len, event_payload.buf);
878 _process_message(node,
object);
880 nyx_object_free(
object);
883 nyx_xmldoc_free(xmldoc);
897 if(node->user_mqtt_handler != NULL)
899 node->user_mqtt_handler(
929 nyx_mqtt_handler_t mqtt_handler,
946 for(
nyx_dict_t **vector_ptr = vectors; *vector_ptr != NULL; vector_ptr++)
952 nyx_dict_set(vector,
"@client", nyx_string_from_dup(node_id));
956 nyx_object_t *children = nyx_dict_get(vector,
"children");
966 vector_def->node = node;
970 vector->base.node = node;
985 if(sprintf(master_client_topic,
"nyx/master_client/%s", node->node_id.buf) > 0)
989 node->master_client_topic = nyx_str_s(master_client_topic);
994 node->indi_url = _safe_dup(indi_url);
995 node->mqtt_url = _safe_dup(mqtt_url);
996 node->nss_url = _safe_dup(nss_url);
998 node->mqtt_username = _safe_dup(mqtt_username);
999 node->mqtt_password = _safe_dup(mqtt_password);
1003 node->enable_xml = enable_xml;
1007 node->vectors = vectors;
1011 #if !defined(ARDUINO)
1012 node->tcp_handler = _tcp_handler;
1014 node->mqtt_handler = _mqtt_handler;
1016 node->user_mqtt_handler = mqtt_handler;
1022 internal_stack_initialize(node, retry_ms);
1039 internal_stack_finalize(node);
1047 for(
nyx_dict_t **vector_ptr = node->vectors; *vector_ptr != NULL; vector_ptr++)
1049 nyx_dict_free(*vector_ptr);
1084 internal_mqtt_pub(node, nyx_str_s(
"nyx/ping/node"), node->node_id, 0);
1086 internal_mqtt_pub(node, node->master_client_topic, node->master_client_message, 0);
1093 if(object->type ==
NYX_TYPE_DICT && (object->flags & NYX_FLAGS_DISABLED) == 0)
1097 STR_t tag = nyx_dict_get_string(vector,
"<>");
1105 if(strcmp(
"defNumberVector", tag) == 0) {
1106 set_vector = nyx_number_set_vector_new(vector);
1108 else if(strcmp(
"defTextVector", tag) == 0) {
1109 set_vector = nyx_text_set_vector_new(vector);
1111 else if(strcmp(
"defLightVector", tag) == 0) {
1112 set_vector = nyx_light_set_vector_new(vector);
1114 else if(strcmp(
"defSwitchVector", tag) == 0) {
1115 set_vector = nyx_switch_set_vector_new(vector);
1117 else if(strcmp(
"defStreamVector", tag) == 0) {
1118 set_vector = nyx_stream_set_vector_new(vector);
1120 else if(strcmp(
"defBLOBVector", tag) == 0) {
1122 if((vector->base.flags & NYX_FLAGS_BLOB_MASK) != 0) {
1124 set_vector = nyx_blob_set_vector_new(vector);
1136 STR_t perm = nyx_dict_get_string(vector,
"@perm");
1138 bool is_not_wo = perm == NULL || strcmp(perm,
"wo") != 0;
1140 if(is_not_wo) _sub_object(object->node, (
nyx_object_t *) set_vector);
1144 nyx_dict_free(set_vector);
1159 for(;
object != NULL;
object =
object->parent)
1176 if(node != NULL && device != NULL)
1180 for(
nyx_dict_t **vector_ptr = node->vectors; *vector_ptr != NULL; vector_ptr++)
1186 STR_t device2 = nyx_dict_get_string(vector,
"@device");
1187 STR_t name2 = nyx_dict_get_string(vector,
"@name");
1191 if(device2 == NULL || strcmp(device, device2) != 0)
1198 if(name2 == NULL || strcmp(name, name2) != 0)
1209 vector->base.flags |= NYX_FLAGS_DISABLED;
1213 vector->base.flags &= ~NYX_FLAGS_DISABLED;
1226 nyx_dict_t *del_property_new = nyx_del_property_new(device, name, message);
1230 nyx_dict_free(del_property_new);
1241 _device_onoff(node, device, name, message,
NYX_ONOFF_ON);
1255 nyx_dict_t *dict = nyx_message_new(device, message);
1259 nyx_dict_free(dict);
1266 nyx_dict_t *dict = nyx_del_property_new(device, name, message);
1270 nyx_dict_free(dict);
#define NYX_LIST_ITER(list)
Initializes a JSON list iterator.
#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 *.
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.
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 a JSON dict object.
Struct describing a JSON list iterator.
Struct describing a JSON list object.
Opaque struct describing a Nyx node.
Struct describing a JSON object.
Struct describing a JSON string object.
nyx_object_t base
Common object header for JSON objects.
Struct describing an XML document.