Subscribing to felix-tohost =========================== felix-tohost publishes streams of data identified by the FID. felix-client allows you to subscribe to these streams to start receiving data. felix-client will attempt to minimize the amount of network connections it opens to the server and re-use existing connections where possible. The latency of the subscribe operation therefore depends open whether a new connection needs to be opened or not. felix-client receives the necessary parameters to open connections from the bus file. If a connection is lost, felix-client will automatically attempt to re-establish the connection. Synchronous and asynchronous subscription ----------------------------------------- There are two sets of functions to subscribe (or unsubscribe) to felix-tohost. The synchronous functions block until the subscription is done. The asynchronous functions return immediately and the subscription is done in the background. The synchronous functions take a timeout parameter and will block up to this timeout. If the timeout is reached and not all subscriptions are done, the function throws an exception. The asynchronous functions are postfixed with ``_nb`` (non-blocking). They return immediately and the subscription is done in the background. The user can register a callback to be notified when the subscription (or unsubscription) is done. The callback is called in the context of the event loop thread. .. doxygentypedef:: FelixClientThreadInterface::OnConnectCallback :no-link: :outline: .. doxygentypedef:: FelixClientThreadInterface::OnDisconnectCallback :no-link: :outline: .. warning:: Do not block in the callback. This can deadlock the event loop and potentially impact the sender as well through backpressure. Generally, it is recommended to use the synchronous functions. They massively simplify the code and should be sufficient in most cases. Only use the asynchronous functions if you have a good reason to do so. .. note:: The callbacks are only called for asynchronous subscriptions. For synchronous subscriptions, success is indicated by the function returning without exception. Subscribing ----------- There are two functions to subscribe to a stream of data. The :cpp:func:`first function` subscribes to a single FID, the :cpp:func:`second one &fids, std::chrono::milliseconds timeout)>` subscribes to a list of FIDs. .. doxygenfunction:: FelixClientThreadExtension520::subscribe(std::uint64_t fid, std::chrono::milliseconds timeout) :no-link: .. doxygenfunction:: FelixClientThreadExtension520::subscribe(const std::vector &fids, std::chrono::milliseconds timeout) :no-link: There are three different error cases: 1. The lookup in the bus failed. The underlying reason is reported by the exception and can be retrieved by calling :cpp:func:`get_underlying_message`. 2. The subscription failed or timed out. In this case, the exception reports the current state. There are four functions to retrieve the state: - :cpp:func:`get_fids_success`: returns the list of FIDs that were successfully subscribed to (you will receive data for these FIDs). - :cpp:func:`get_fids_bad`: returns the list of FIDs that were not subscribed to (you will not receive data for these FIDs). - :cpp:func:`get_fids_failed`: returns the list to which the attempt to subscribe failed to (increasing the timeout will not help, something fundamentally wrong happened). - :cpp:func:`get_fids_timeout`: returns the list of FIDs that timed out (increasing the timeout might help). If timed out subscription succeed after the function returned, felix-client will automatically attempt to unsubscribe from those links. 3. You are already subscribed to all provided FIDs. .. tip:: If this function returns without exception, you are subscribed to all provided FIDs. No callbacks will be called for synchronous requests. The asynchronous signatures are similar. They do not take a timeout parameter. They either take :cpp:func:`a single FID` or :cpp:func:`a list of FIDs &fids)>`. .. doxygenfunction:: FelixClientThreadExtension520::subscribe_nb(std::uint64_t fid) :no-link: .. doxygenfunction:: FelixClientThreadExtension520::subscribe_nb(const std::vector &fids) :no-link: There are still three different error cases: 1. The lookup in the bus failed. The underlying reason is reported by the exception and can be retrieved by calling :cpp:func:`get_underlying_message`. 2. Sending the subscription request failed. The list of FIDs that failed can be retrieved by calling :cpp:func:`get_fids_bad`. 3. You are already subscribed to all provided FIDs. .. note:: Because the underlying subscription mechanism is asynchronous, there are no guarantees that the subscription will be successful. If a request was sent, the only way to notice that the subscription failed is the absence of the :cpp:type:`This callback`. Unsubscribing ------------- Unsubscription either happens at destruction or explicitly. There is a :cpp:func:`synchronous` and an :cpp:func:`asynchronous version`. .. doxygenfunction:: FelixClientThreadExtension520::unsubscribe(std::uint64_t fid, std::chrono::milliseconds timeout) :no-link: .. doxygenfunction:: FelixClientThreadExtension520::unsubscribe_nb(std::uint64_t fid) :no-link: Errors are again reported by exceptions. The following error cases are possible: 1. You are not subscribed to the given link 2. Sending the unsubscription request failed. 3. (**Only for synchronous requests**) The unsubscription timed out. .. note:: If an unsubscription request timed out but succeeds in the future, the unsubscription will be effective. Loosing connection ------------------ If the a connection is lost (e.g. because felix-tohost went down), the client will automatically attempt to re-establish the connection. The user can register a callback to be notified when the :cpp:type:`connection is lost` or :cpp:type:`re-established`. .. doxygentypedef:: FelixClientThreadExtension520::OnSubscriptionsLostCallback :no-link: :outline: .. doxygentypedef:: FelixClientThreadExtension520::OnResubscriptionCallback :no-link: :outline: Deprecated API -------------- In the old API, these functions were synchronous whenever the config parameter ``FELIX_CLIENT_TIMEOUT`` was set to a value greater than 0. Otherwise it was asynchronous. Also, the old API always calls callbacks (even for synchronous requests) and reports most errors as a :cpp:class:`felix::ResourceNotAvailableException` without further context. In case of partial success, the state was not well defined. Example ------- .. code-block:: cpp :linenos: void on_data(const uint64_t fid, const std::span data, const uint8_t status) { std::println("Received data for fid {:#x}: {} bytes, status {}", fid, data.size(), status); } // Create a subscription try { fct.subscribe({0x1000000000000000, 0x1010000000000000}, 1000ms); } catch (const felix::FelixClientException& e) { std::println("Failed to subscribe: {}", e.what()); std::println("FIDs success: {::#x}", e.get_fids_success()); std::println("FIDs failed: {::#x}", e.get_fids_failed()); std::println("FIDs timeout: {::#x}", e.get_fids_timeout()); return; } // Receive data ... try { client.unsubscribe({0x1000000000000000, 0x1010000000000000}); } catch (const felix::FelixClientException& e) { std::println("Failed to unsubscribe: {}", e.what()); return; } (Incomplete) example using asynchronous connection establishment (exception handling omitted): .. code-block:: cpp :linenos: const auto fid = 0x1000000000000000; std::atomic_bool connected{false}; void on_connect_callback(const std::uint64_t fid_connected) { std::println("Connected to FID: {:#x}", fid_connected); if (fid_connected == fid) { connected = true; } } fct.subscribe_nb(fid); connected.wait(false); // Questions: // - What about timeout? // - If using a timeout, what happens if the connection is established after the timeout? // - Track multiple FIDs