diff --git a/src/bacnet/datalink/dlenv.c b/src/bacnet/datalink/dlenv.c index 99199693..a49e04f3 100644 --- a/src/bacnet/datalink/dlenv.c +++ b/src/bacnet/datalink/dlenv.c @@ -823,22 +823,62 @@ void dlenv_network_port_bsc_init(uint32_t instance) #endif } -void bsc_register_as_node(uint32_t instance) +/** + * Check if BACnet/SC hub connection is established. + * + * If no hub URIs (primary or failover) are configured, connection/registration + * is not required and this function returns true, consistent with + * bsc_register_as_node(). + */ +bool dlenv_is_bsc_hub_connected(void) { #if defined(BACDL_BSC) - /* if a user has configured BACnet/SC port with primary hub URI, */ - /* wait for a establishing of a connection to BACnet/SC hub at first */ - /* to reduce possibility of packet losses. */ - if (Network_Port_SC_Primary_Hub_URI_char(instance)) { - debug_printf_stderr("Waiting for a BACnet/SC connection to hub...\n"); - while (!dlenv_hub_connection_status_check(instance)) { - bsc_wait(1); - bsc_maintenance_timer(1); - } - debug_printf_stderr("Connected to a BACnet/SC hub!\n"); + const char *primary_uri = + Network_Port_SC_Primary_Hub_URI_char(Network_Port_Instance); + const char *failover_uri = + Network_Port_SC_Failover_Hub_URI_char(Network_Port_Instance); + if (!primary_uri && !failover_uri) { + /* No hub URIs configured: registration/connection not required. */ + return true; } + return dlenv_hub_connection_status_check(Network_Port_Instance); +#else + return true; +#endif +} + +static bool bsc_register_as_node(uint32_t instance, bool wait_until_connected) +{ +#if defined(BACDL_BSC) + bool is_connected = false; + const char *primary_uri = + Network_Port_SC_Primary_Hub_URI_char(Network_Port_Instance); + const char *failover_uri = + Network_Port_SC_Failover_Hub_URI_char(Network_Port_Instance); + if (primary_uri || failover_uri) { + debug_printf_stderr("Waiting for a BACnet/SC connection to hub...\n"); + is_connected = dlenv_hub_connection_status_check(instance); + if (!is_connected) { + do { + bsc_wait(1); + bsc_maintenance_timer(1); + is_connected = dlenv_hub_connection_status_check(instance); + } while (wait_until_connected && !is_connected); + } + if (is_connected) { + debug_printf_stderr("Connected to a BACnet/SC hub!\n"); + } + } else { + /* No primary/failover hub URI configured: registration/connection + * not required. + */ + is_connected = true; + } + return is_connected; #else (void)instance; + (void)wait_until_connected; + return true; #endif } @@ -900,6 +940,200 @@ void dlenv_maintenance_timer(uint16_t elapsed_seconds) } } +/** Determine the DataLink port type from Environment variables, + * or else to defaults. In BACDL_MULTIPLE builds, this selection + * is also used to choose the active Datalink_Transport. + * + * @return Detected port type based on environment variables and + * compile-time configuration, which can be PORT_TYPE_BIP, + * PORT_TYPE_BIP6, PORT_TYPE_MSTP, PORT_TYPE_BSC, PORT_TYPE_ETHERNET, + * PORT_TYPE_ARCNET, PORT_TYPE_ZIGBEE, or PORT_TYPE_NON_BACNET. + */ +uint8_t dlenv_get_port_type(void) +{ + uint8_t port_type = PORT_TYPE_BIP; + + if (getenv("BACNET_DATALINK_DEBUG")) { + dlenv_debug_enable(); + } +#if defined(BACDL_MULTIPLE) + char *pEnv = getenv("BACNET_DATALINK"); + if (pEnv) { + datalink_set(pEnv); + if (bacnet_stricmp("none", pEnv) == 0) { + port_type = PORT_TYPE_NON_BACNET; + } else if (bacnet_stricmp("bip", pEnv) == 0) { + port_type = PORT_TYPE_BIP; + } else if (bacnet_stricmp("bip6", pEnv) == 0) { + port_type = PORT_TYPE_BIP6; + } else if (bacnet_stricmp("ethernet", pEnv) == 0) { + port_type = PORT_TYPE_ETHERNET; + } else if (bacnet_stricmp("arcnet", pEnv) == 0) { + port_type = PORT_TYPE_ARCNET; + } else if (bacnet_stricmp("mstp", pEnv) == 0) { + port_type = PORT_TYPE_MSTP; + } else if (bacnet_stricmp("bsc", pEnv) == 0) { + port_type = PORT_TYPE_BSC; + } + } else { +#if defined(BACDL_BIP) + datalink_set("bip"); + port_type = PORT_TYPE_BIP; +#elif defined(BACDL_BIP6) + datalink_set("bip6"); + port_type = PORT_TYPE_BIP6; +#elif defined(BACDL_MSTP) + datalink_set("mstp"); + port_type = PORT_TYPE_MSTP; +#elif defined(BACDL_ETHERNET) + datalink_set("ethernet"); + port_type = PORT_TYPE_ETHERNET; +#elif defined(BACDL_ARCNET) + datalink_set("arcnet"); + port_type = PORT_TYPE_ARCNET; +#elif defined(BACDL_ZIGBEE) + datalink_set("zigbee"); + port_type = PORT_TYPE_ZIGBEE; +#elif defined(BACDL_BSC) + datalink_set("bsc"); + port_type = PORT_TYPE_BSC; +#else + datalink_set("none"); + port_type = PORT_TYPE_NON_BACNET; +#endif + } +#else + /* if we are not compiling with multiple datalinks, + then we are using the only one available */ +#if defined(BACDL_BIP) + port_type = PORT_TYPE_BIP; +#elif defined(BACDL_BIP6) + port_type = PORT_TYPE_BIP6; +#elif defined(BACDL_MSTP) + port_type = PORT_TYPE_MSTP; +#elif defined(BACDL_ETHERNET) + port_type = PORT_TYPE_ETHERNET; +#elif defined(BACDL_ARCNET) + port_type = PORT_TYPE_ARCNET; +#elif defined(BACDL_ZIGBEE) + port_type = PORT_TYPE_ZIGBEE; +#elif defined(BACDL_BSC) + port_type = PORT_TYPE_BSC; +#else + port_type = PORT_TYPE_NON_BACNET; +#endif +#endif + return port_type; +} + +/** + * Initialize the Datalink configuration according to the given port type + * without registering the devices to the network. This is useful when the + * device registration needs to be delayed or shall be done separately, e.g. in + * a different thread. + * + * @param port_type Port type to initialize, such as PORT_TYPE_BIP, + * PORT_TYPE_BIP6, PORT_TYPE_MSTP, PORT_TYPE_BSC, or other supported PORT_TYPE_* + * values. It should be determined before by calling dlenv_get_port_type() + */ +void dlenv_init_no_device_registration(uint8_t port_type) +{ + char *pEnv = NULL; + +#if defined(BACFILE) + /* initialize the POSIX file objects */ + bacfile_posix_init(); + if (Datalink_Debug) { + debug_printf_stderr("POSIX file services initialized.\n"); + } +#endif + /* === Initialize the Network Port Object Here === */ + Network_Port_Type_Set(Network_Port_Instance, port_type); + switch (port_type) { + case PORT_TYPE_BIP: + dlenv_network_port_bip_init(Network_Port_Instance); + break; + case PORT_TYPE_MSTP: + dlenv_network_port_mstp_init(Network_Port_Instance); + break; + case PORT_TYPE_BIP6: + dlenv_network_port_bip6_init(Network_Port_Instance); + break; + case PORT_TYPE_ZIGBEE: + dlenv_network_port_zigbee_init(Network_Port_Instance); + break; + case PORT_TYPE_BSC: + dlenv_network_port_bsc_init(Network_Port_Instance); + break; + default: + break; + } + pEnv = getenv("BACNET_APDU_TIMEOUT"); + if (pEnv) { + apdu_timeout_set((uint16_t)strtol(pEnv, NULL, 0)); + } else { + if (port_type == PORT_TYPE_MSTP) { + apdu_timeout_set(60000); + } + } + pEnv = getenv("BACNET_APDU_RETRIES"); + if (pEnv) { + apdu_retries_set((uint8_t)strtol(pEnv, NULL, 0)); + } + /* === INIT - Initialize the Datalink Here === */ + pEnv = getenv("BACNET_IFACE"); + if (Datalink_Debug) { + debug_fprintf(stderr, "BACNET_IFACE=%s\n", pEnv ? pEnv : "none"); + } + if (!datalink_init(pEnv)) { + exit(1); + } + /* === POST INIT - After the Datalink is Initialized === */ +#if (MAX_TSM_TRANSACTIONS) + pEnv = getenv("BACNET_INVOKE_ID"); + if (pEnv) { + tsm_invokeID_set((uint8_t)strtol(pEnv, NULL, 0)); + } +#endif +} + +/** + * Registers a device to the network according to the given port type. + * As prerequisite, the Datalink configuration should have been initialized by + * calling dlenv_init_no_device_registration() with the same port type. + * + * This function is not thread safe. It must be called only after + * dlenv_init_no_device_registration() has completed for the same port type, + * and while no other thread is concurrently using or modifying the datalink + * or Network Port Object state. The call may be made immediately after init + * returns or at a later time (including from a different thread), provided + * these conditions are satisfied. + * + * @param port_type + * The network port type that determines which registration mechanism + * (e.g., BIP, BIP6, BSC) is used. For other port types, no registration + * is performed. + * @param wait_until_connected + * Only used for PORT_TYPE_BSC. If true, block until the + * device is connected/registered; otherwise return immediately. + * @return + * true if the device was successfully registered or registration is not + * required for the given port type; false if registration was attempted + * but failed. + */ +bool dlenv_register_device(uint8_t port_type, bool wait_until_connected) +{ + if (port_type == PORT_TYPE_BIP) { + return (bbmd_register_as_foreign_device() >= 0); + } else if (port_type == PORT_TYPE_BIP6) { + return (bbmd6_register_as_foreign_device() >= 0); + } else if (port_type == PORT_TYPE_BSC) { + return bsc_register_as_node( + Network_Port_Instance, wait_until_connected); + } + return true; +} + /** Initialize the DataLink configuration from Environment variables, * or else to defaults. * @ingroup DataLink @@ -975,139 +1209,11 @@ void dlenv_maintenance_timer(uint16_t elapsed_seconds) */ void dlenv_init(void) { - char *pEnv = NULL; - uint8_t port_type = PORT_TYPE_BIP; - - if (getenv("BACNET_DATALINK_DEBUG")) { - dlenv_debug_enable(); - } -#if defined(BACDL_MULTIPLE) - pEnv = getenv("BACNET_DATALINK"); - if (pEnv) { - datalink_set(pEnv); - if (bacnet_stricmp("none", pEnv) == 0) { - port_type = PORT_TYPE_NON_BACNET; - } else if (bacnet_stricmp("bip", pEnv) == 0) { - port_type = PORT_TYPE_BIP; - } else if (bacnet_stricmp("bip6", pEnv) == 0) { - port_type = PORT_TYPE_BIP6; - } else if (bacnet_stricmp("ethernet", pEnv) == 0) { - port_type = PORT_TYPE_ETHERNET; - } else if (bacnet_stricmp("arcnet", pEnv) == 0) { - port_type = PORT_TYPE_ARCNET; - } else if (bacnet_stricmp("mstp", pEnv) == 0) { - port_type = PORT_TYPE_MSTP; - } else if (bacnet_stricmp("bsc", pEnv) == 0) { - port_type = PORT_TYPE_BSC; - } - } else { -#if defined(BACDL_BIP) - datalink_set("bip"); - port_type = PORT_TYPE_BIP; -#elif defined(BACDL_BIP6) - datalink_set("bip6"); - port_type = PORT_TYPE_BIP6; -#elif defined(BACDL_MSTP) - datalink_set("mstp"); - port_type = PORT_TYPE_MSTP; -#elif defined(BACDL_ETHERNET) - datalink_set("ethernet"); - port_type = PORT_TYPE_ETHERNET; -#elif defined(BACDL_ARCNET) - datalink_set("arcnet"); - port_type = PORT_TYPE_ARCNET; -#elif defined(BACDL_ZIGBEE) - datalink_set("zigbee"); - port_type = PORT_TYPE_ZIGBEE; -#elif defined(BACDL_BSC) - datalink_set("bsc"); - port_type = PORT_TYPE_BSC; -#else - datalink_set("none"); - port_type = PORT_TYPE_NON_BACNET; -#endif - } -#else - /* if we are not compiling with multiple datalinks, - then we are using the only one available */ -#if defined(BACDL_BIP) - port_type = PORT_TYPE_BIP; -#elif defined(BACDL_BIP6) - port_type = PORT_TYPE_BIP6; -#elif defined(BACDL_MSTP) - port_type = PORT_TYPE_MSTP; -#elif defined(BACDL_ETHERNET) - port_type = PORT_TYPE_ETHERNET; -#elif defined(BACDL_ARCNET) - port_type = PORT_TYPE_ARCNET; -#elif defined(BACDL_ZIGBEE) - port_type = PORT_TYPE_ZIGBEE; -#elif defined(BACDL_BSC) - port_type = PORT_TYPE_BSC; -#else - port_type = PORT_TYPE_NON_BACNET; -#endif -#endif -#if defined(BACFILE) - /* initialize the POSIX file objects */ - bacfile_posix_init(); - if (Datalink_Debug) { - debug_printf_stderr("POSIX file services initialized.\n"); - } -#endif - /* === Initialize the Network Port Object Here === */ - Network_Port_Type_Set(Network_Port_Instance, port_type); - switch (port_type) { - case PORT_TYPE_BIP: - dlenv_network_port_bip_init(Network_Port_Instance); - break; - case PORT_TYPE_MSTP: - dlenv_network_port_mstp_init(Network_Port_Instance); - break; - case PORT_TYPE_BIP6: - dlenv_network_port_bip6_init(Network_Port_Instance); - break; - case PORT_TYPE_ZIGBEE: - dlenv_network_port_zigbee_init(Network_Port_Instance); - break; - case PORT_TYPE_BSC: - dlenv_network_port_bsc_init(Network_Port_Instance); - break; - default: - break; - } - pEnv = getenv("BACNET_APDU_TIMEOUT"); - if (pEnv) { - apdu_timeout_set((uint16_t)strtol(pEnv, NULL, 0)); - } else { - if (port_type == PORT_TYPE_MSTP) { - apdu_timeout_set(60000); - } - } - pEnv = getenv("BACNET_APDU_RETRIES"); - if (pEnv) { - apdu_retries_set((uint8_t)strtol(pEnv, NULL, 0)); - } - /* === INIT - Initialize the Datalink Here === */ - pEnv = getenv("BACNET_IFACE"); - if (Datalink_Debug) { - debug_fprintf(stderr, "BACNET_IFACE=%s\n", pEnv ? pEnv : "none"); - } - if (!datalink_init(pEnv)) { - exit(1); - } - /* === POST INIT - After the Datalink is Initialized === */ -#if (MAX_TSM_TRANSACTIONS) - pEnv = getenv("BACNET_INVOKE_ID"); - if (pEnv) { - tsm_invokeID_set((uint8_t)strtol(pEnv, NULL, 0)); - } -#endif - if (port_type == PORT_TYPE_BIP) { - bbmd_register_as_foreign_device(); - } else if (port_type == PORT_TYPE_BIP6) { - bbmd6_register_as_foreign_device(); - } else if (port_type == PORT_TYPE_BSC) { - bsc_register_as_node(Network_Port_Instance); + uint8_t port_type = dlenv_get_port_type(); + dlenv_init_no_device_registration(port_type); + if (!dlenv_register_device(port_type, true)) { + debug_fprintf( + stderr, "dlenv_init: failed to register device for port type %u\n", + (unsigned)port_type); } } diff --git a/src/bacnet/datalink/dlenv.h b/src/bacnet/datalink/dlenv.h index be1fac85..a4ef6d16 100644 --- a/src/bacnet/datalink/dlenv.h +++ b/src/bacnet/datalink/dlenv.h @@ -20,6 +20,15 @@ extern "C" { #endif /* __cplusplus */ +BACNET_STACK_EXPORT +uint8_t dlenv_get_port_type(void); + +BACNET_STACK_EXPORT +void dlenv_init_no_device_registration(uint8_t port_type); + +BACNET_STACK_EXPORT +bool dlenv_register_device(uint8_t port_type, bool wait_until_connected); + BACNET_STACK_EXPORT void dlenv_init(void); @@ -47,6 +56,9 @@ void dlenv_bbmd_ttl_set(uint16_t ttl_secs); BACNET_STACK_EXPORT int dlenv_bbmd_result(void); +BACNET_STACK_EXPORT +bool dlenv_is_bsc_hub_connected(void); + #ifdef __cplusplus } #endif /* __cplusplus */