-
Notifications
You must be signed in to change notification settings - Fork 1
MQTT/Protobuf Middleware API #390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
4dced85
experimenting with adding nanopb to embedded-base
bjackson312006 a47bd23
fixes for u_nx_debug.c
bjackson312006 dc90760
somemore stuff
bjackson312006 8888b45
better static asserts
bjackson312006 dc72e96
fixed static asserts
bjackson312006 73dec53
nx_protobuf_mqtt_message_send()
bjackson312006 cc61547
include the client file
bjackson312006 6356440
print when ran init
bjackson312006 3688ab6
fix
bjackson312006 9f74482
stuff core
bjackson312006 34a8cba
comments for the config limit macros
bjackson312006 0194701
don't read ptp if not initialized
bjackson312006 76af13c
ptp
bjackson312006 1467590
removed outdated comments about ptp blocking
bjackson312006 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| #pragma once | ||
|
|
||
| // clang-format off | ||
|
|
||
| /* | ||
| * Wrapper for structuring and sending protobuf Ethernet messages over MQTT. | ||
| * This API sits above the lower-level u_nx_ethernet.h driver layer. Messages with nonstandard formats can still be sent using the u_nx_ethernet.h API directly. | ||
| */ | ||
|
|
||
| #include <stddef.h> | ||
| #include <stdbool.h> | ||
| #include "serverdata.pb.h" | ||
|
|
||
| /* Helper macros. */ | ||
| #define PB_STR_HELPER(x) #x // Helper for PB_TOSTR(). Probably should never use directly. | ||
| #define PB_TOSTR(x) PB_STR_HELPER(x) // Converts a macro's value into a string. | ||
| #define PB_COUNT_ARGS(...) (sizeof((float[]){ __VA_ARGS__ }) / sizeof(float)) // Returns the number of arguments passed into it. | ||
| #define PB_STR_LEN(s) (sizeof(s) - 1) // Returns the length of a string literal. | ||
|
|
||
| /* CONFIG: Compile-time validation of topic size, unit size, and number of values. */ | ||
| #define PB_MAX_TOPIC_LENGTH 100 // Maximum length of topic string literal (in characters). | ||
| #define PB_MAX_UNIT_LENGTH 15 // Maximum length of unit string literal (in characters). | ||
| #define PB_MIN_DATAPOINTS 1 // Minimum number of datapoints (i.e., variable `...` arguments passed into `nx_protobuf_mqtt_message_create()`). | ||
| #define PB_MAX_DATAPOINTS 5 // Maximum number of datapoints (i.e., variable `...` arguments passed into `nx_protobuf_mqtt_message_create()`). | ||
| #define PB_VALIDATE_ARGS(topic, unit, num_values) \ | ||
| do { \ | ||
| _Static_assert( \ | ||
| PB_STR_LEN(topic) <= PB_MAX_TOPIC_LENGTH, \ | ||
| "MQTT topic parameter exceeds maximum length of " PB_TOSTR(PB_MAX_TOPIC_LENGTH) " allowed by `nx_protobuf_mqtt_message_create()`."\ | ||
| ); \ | ||
| _Static_assert( \ | ||
| PB_STR_LEN(unit) <= PB_MAX_UNIT_LENGTH, \ | ||
| "MQTT unit parameter exceeds maximum length of " PB_TOSTR(PB_MAX_UNIT_LENGTH) " allowed by `nx_protobuf_mqtt_message_create()`." \ | ||
| ); \ | ||
| _Static_assert( \ | ||
| (num_values) >= PB_MIN_DATAPOINTS, \ | ||
| "Must pass at least " PB_TOSTR(PB_MIN_DATAPOINTS) " value into the variable argument of `nx_protobuf_mqtt_message_create()`." \ | ||
| ); \ | ||
| _Static_assert( \ | ||
| (num_values) <= PB_MAX_DATAPOINTS, \ | ||
| "Cannot pass more than " PB_TOSTR(PB_MAX_DATAPOINTS) " values into the variable argument of `nx_protobuf_mqtt_message_create()`." \ | ||
| ); \ | ||
| } while (0) | ||
|
|
||
| /** | ||
| * @brief Creates and formats a `ethernet_mqtt_message_t` object, and returns it to the caller. | ||
| * @param topic (const char*) String literal representing the message's MQTT topic name. | ||
| * @param unit (const char*) String literal representing the unit of the message's data. | ||
| * @param ... (float) The data to be sent in the message. This is a variable argument, so it can be repeated depending on how many datapoints you want to send. If you pass in more datapoints than allowed, you will get a compile-time error. | ||
| * @return An `ethernet_mqtt_message_t` object. | ||
| * @note If message creation was not completed for any reason, .initialized will be false in the returned `ethernet_mqtt_message_t` object. You may still use the object as you please (including attempting to initialize it again), but attempting to send the message (via `nx_protobuf_mqtt_message_send()`) will return an error. | ||
| */ | ||
| #define nx_protobuf_mqtt_message_create(topic, unit, ...) \ | ||
| ({ \ | ||
| PB_VALIDATE_ARGS(topic, unit, PB_COUNT_ARGS(__VA_ARGS__)); \ | ||
| _nx_protobuf_mqtt_message_create( \ | ||
| (topic), PB_STR_LEN(topic), \ | ||
| (unit), PB_STR_LEN(unit), \ | ||
| (float[]){ __VA_ARGS__ }, \ | ||
| PB_COUNT_ARGS(__VA_ARGS__) \ | ||
| ); \ | ||
| }) | ||
|
|
||
|
|
||
| /* Ethernet MQTT Message. */ | ||
| typedef struct { | ||
| const char* topic; | ||
| int topic_size; | ||
| serverdata_v2_ServerData protobuf; | ||
| bool initialized; | ||
| } ethernet_mqtt_message_t; | ||
|
|
||
| /** | ||
| * @brief Dispatches a `ethernet_mqtt_message_t` message over MQTT. | ||
| * @param message The message to send. | ||
| * @return U_SUCCESS if successful, U_ERROR is not successful. | ||
| */ | ||
| int nx_protobuf_mqtt_message_send(ethernet_mqtt_message_t* message); | ||
|
|
||
| /* MACRO IMPLEMENTATIONS */ | ||
| ethernet_mqtt_message_t _nx_protobuf_mqtt_message_create(const char* topic, size_t topic_size, const char* unit, size_t unit_len, const float values[], int values_count); | ||
|
|
||
| // clang-format on | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| #include <string.h> | ||
| #include "u_tx_debug.h" | ||
| #include "u_nx_debug.h" | ||
| #include "u_nx_protobuf.h" | ||
| #include "u_nx_ethernet.h" | ||
| #include "pb.h" | ||
| #include "pb_encode.h" | ||
| #include "tx_api.h" | ||
| #include "nxd_mqtt_client.h" | ||
|
|
||
| ethernet_mqtt_message_t _nx_protobuf_mqtt_message_create(const char* topic, size_t topic_len, const char* unit, size_t unit_len, const float values[], int values_size) { | ||
| /* Zero-initialize the protobuf struct and the sendable ethernet_mqtt_message_t message. */ | ||
| serverdata_v2_ServerData protobuf = serverdata_v2_ServerData_init_zero; | ||
| ethernet_mqtt_message_t message = { 0 }; | ||
| message.initialized = false; | ||
|
|
||
| /* Enforce topic length. */ | ||
| if(topic_len > PB_MAX_TOPIC_LENGTH) { | ||
| PRINTLN_ERROR("MQTT Message topic exceeds maximum length of %d (Topic: %s, Current Topic Length: %d).", PB_MAX_TOPIC_LENGTH, topic, topic_len); | ||
| return message; // Return empty, uninitialized message. | ||
| } | ||
| // NOTE: If using the `nx_protobuf_mqtt_message_create()` macro (as intended), the static asserts should catch this. This is just an extra check in case a caller uses this function directly for whatever reason. | ||
|
|
||
| /* Enforce unit length. */ | ||
| if(unit_len > PB_MAX_UNIT_LENGTH) { | ||
| PRINTLN_ERROR("MQTT Unit string length exceeds maximum length of %d (Topic: %s, Current Unit String Length: %d).", PB_MAX_UNIT_LENGTH, topic, unit_len); | ||
| return message; // Return empty, uninitialized message. | ||
| } | ||
| // NOTE: If using the `nx_protobuf_mqtt_message_create()` macro (as intended), the static asserts should catch this. This is just an extra check in case a caller uses this function directly for whatever reason. | ||
|
|
||
| /* Enforce minimum number of datapoints. */ | ||
| if(values_size < PB_MIN_DATAPOINTS) { | ||
| PRINTLN_ERROR("Message must have at least %d datapoints (Topic: %s, Current values_size: %d).", PB_MIN_DATAPOINTS, topic, values_size); | ||
| return message; // Return empty, uninitialized message. | ||
| } | ||
| // NOTE: If using the `nx_protobuf_mqtt_message_create()` macro (as intended), the static asserts should catch this. This is just an extra check in case a caller uses this function directly for whatever reason. | ||
|
|
||
| /* Enforce maximum number of datapoints. */ | ||
| if(values_size > PB_MAX_DATAPOINTS) { | ||
| PRINTLN_ERROR("Message cannot have more than %d datapoints (Topic: %s, Current values_size: %d).", PB_MAX_DATAPOINTS, topic, values_size); | ||
| return message; // Return empty, uninitialized message. | ||
| } | ||
| // NOTE: If using the `nx_protobuf_mqtt_message_create()` macro (as intended), the static asserts should catch this. This is just an extra check in case a caller uses this function directly for whatever reason. | ||
|
|
||
| /* Get the PTP time and convert to appropriate protobuf time. */ | ||
| uint64_t datetime = 0; | ||
| int status = ethernet_ptp_get_unix_microseconds(&datetime); | ||
| if(status != U_SUCCESS) { | ||
| PRINTLN_ERROR("Failed to get PTP Unix time (Status: %d).", status); | ||
| return message; // Return empty, uninitialized message. | ||
| } | ||
|
|
||
|
|
||
| PRINTLN_INFO("got time: %d", datetime); | ||
|
|
||
| /* Pack the protobuf message. */ | ||
| // CURRENT PROTOBUF SCHEMA LOOKS LIKE THIS: | ||
| // typedef struct _serverdata_v2_ServerData { | ||
| // char unit[15]; | ||
| // uint64_t time_us; | ||
| // pb_size_t values_count; | ||
| // float values[5]; | ||
| // } serverdata_v2_ServerData; | ||
| memcpy(protobuf.unit, unit, unit_len); | ||
| protobuf.time_us = datetime; | ||
| protobuf.values_count = values_size; | ||
| memcpy(protobuf.values, values, values_size * sizeof(float)); | ||
|
|
||
| /* Pack the `ethernet_mqtt_message_t` object and return it as successfully initialized. */ | ||
| message.topic = topic; | ||
| message.topic_size = topic_len + 1; // u_TODO - for some reason you need to do + 1 here or else it will cut the last letter off of the topic in MQTT ui. The macro probably just calculates the topic length with one less than it should or something | ||
| message.protobuf = protobuf; | ||
| message.initialized = true; | ||
| return message; | ||
| } | ||
|
|
||
| int nx_protobuf_mqtt_message_send(ethernet_mqtt_message_t* message) { | ||
| /* Make sure message isn't nullptr. */ | ||
| if(!message) { | ||
| PRINTLN_ERROR("Null pointer to `ethernet_mqtt_message_t` message."); | ||
| return U_ERROR; | ||
| } | ||
|
|
||
| /* Make sure message is initialized. */ | ||
| if(!message->initialized) { | ||
| PRINTLN_ERROR("Attempting to send an uninitialized `ethernet_mqtt_message_t` message."); | ||
| return U_ERROR; | ||
| } | ||
|
|
||
| /* Set up the buffer. */ | ||
| unsigned char buffer[serverdata_v2_ServerData_size]; | ||
| pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); | ||
|
|
||
| /* Encode the protobuf. */ | ||
| int status = pb_encode(&stream, serverdata_v2_ServerData_fields, &message->protobuf); | ||
| if(status != true) { | ||
| PRINTLN_ERROR("Failed to serialize protobuf message (Topic: %s): %s", message->topic, PB_GET_ERROR(&stream)); | ||
| return U_ERROR; | ||
| } | ||
|
|
||
| /* Publish over MQTT. */ | ||
| status = ethernet_mqtt_publish(message->topic, message->topic_size, (char*)buffer, stream.bytes_written); // u_TODO - ethernet_mqtt_publish should return U_SUCCESS/U_ERROR instead of the internal netx error macro | ||
| if(status != NXD_MQTT_SUCCESS) { | ||
| PRINTLN_WARNING("Failed to publish MQTT message (Topic: %s, Status: %d).", message->topic, status); | ||
|
|
||
| /* If disconnected, attempt reconnection. */ | ||
| if(status == NXD_MQTT_NOT_CONNECTED) { | ||
| PRINTLN_WARNING("Detected disconnect from MQTT. Attempting reconnection..."); | ||
| do { | ||
| tx_thread_sleep(1000); | ||
| status = ethernet_mqtt_reconnect(); | ||
| PRINTLN_WARNING("Attempting MQTT reconnection (Status: %d/%s).", status, nx_status_toString(status)); | ||
| } while ((status != NXD_MQTT_SUCCESS) && (status != NXD_MQTT_ALREADY_CONNECTED)); | ||
| PRINTLN_WARNING("MQTT reconnection successful."); | ||
| } | ||
|
|
||
| return U_ERROR; | ||
| } | ||
|
|
||
| /* Return successful! */ | ||
| PRINTLN_INFO("Sent MQTT message (Topic: %s).", message->topic); | ||
| return U_SUCCESS; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| cmake_minimum_required(VERSION 3.22) | ||
|
|
||
| set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/nanopb/extra) | ||
| find_package(Nanopb REQUIRED) | ||
|
|
||
| nanopb_generate_cpp(TARGET proto serverdata.proto) | ||
|
|
||
| target_link_libraries(${CMAKE_PROJECT_NAME} proto) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| serverdata.v2.ServerData.unit max_size: 15 | ||
| serverdata.v2.ServerData.values max_count: 5 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this exposed somewhere in the generated serverdata.h? That way we dont need to hardcode it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None are afaik, they seem to be hardcoded without macros. The MQTT topic length is technically something just in the mqtt layer rather than protobuf but we have to hardcode it somewhere