mirror of
https://github.com/intrepidcs/libicsneo.git
synced 2026-08-04 17:08:38 +02:00
C2: Add termination group support
This commit is contained in:
committed by
Kyle Schwarz
parent
743246e813
commit
d708f8081a
@@ -48,6 +48,11 @@ typedef struct icsneoc2_chip_versions_t {
|
||||
icsneoc2_chip_versions_t* next;
|
||||
} icsneoc2_chip_versions_t;
|
||||
|
||||
typedef struct icsneoc2_termination_group_t {
|
||||
std::vector<icsneoc2_netid_t> netids;
|
||||
icsneoc2_termination_group_t* next;
|
||||
} icsneoc2_termination_group_t;
|
||||
|
||||
typedef struct icsneoc2_mac_addr_entry_t {
|
||||
uint16_t network_id;
|
||||
uint8_t address[ICSNEO_MAC_ADDRESS_LEN];
|
||||
|
||||
@@ -179,6 +179,82 @@ icsneoc2_error_t icsneoc2_settings_termination_set(icsneoc2_device_t* device, ic
|
||||
return icsneoc2_error_success;
|
||||
}
|
||||
|
||||
icsneoc2_error_t icsneoc2_settings_termination_groups_enumerate(icsneoc2_device_t* device, icsneoc2_termination_group_t** groups, size_t* count) {
|
||||
// Make sure the device is valid
|
||||
auto res = icsneoc2_device_is_valid(device);
|
||||
if(res != icsneoc2_error_success) {
|
||||
return res;
|
||||
}
|
||||
if(!groups) {
|
||||
return icsneoc2_error_invalid_parameters;
|
||||
}
|
||||
|
||||
auto termination_groups = device->device->settings->getTerminationGroups();
|
||||
|
||||
icsneoc2_termination_group_t* head = nullptr;
|
||||
icsneoc2_termination_group_t* tail = nullptr;
|
||||
for(const auto& group : termination_groups) {
|
||||
auto* node = new (std::nothrow) icsneoc2_termination_group_t;
|
||||
if(!node) {
|
||||
icsneoc2_settings_termination_groups_free(head);
|
||||
return icsneoc2_error_out_of_memory;
|
||||
}
|
||||
node->netids.reserve(group.size());
|
||||
for(const auto& network : group) {
|
||||
node->netids.push_back(static_cast<icsneoc2_netid_t>(network.getNetID()));
|
||||
}
|
||||
node->next = nullptr;
|
||||
if(!head) {
|
||||
head = node;
|
||||
} else {
|
||||
tail->next = node;
|
||||
}
|
||||
tail = node;
|
||||
}
|
||||
|
||||
*groups = head;
|
||||
if(count) {
|
||||
*count = termination_groups.size();
|
||||
}
|
||||
return icsneoc2_error_success;
|
||||
}
|
||||
|
||||
icsneoc2_termination_group_t* icsneoc2_termination_group_next(const icsneoc2_termination_group_t* group) {
|
||||
if(!group) {
|
||||
return nullptr;
|
||||
}
|
||||
return group->next;
|
||||
}
|
||||
|
||||
icsneoc2_error_t icsneoc2_termination_group_networks_get(const icsneoc2_termination_group_t* group, icsneoc2_netid_t* networks, size_t* count) {
|
||||
if(!group || !count) {
|
||||
return icsneoc2_error_invalid_parameters;
|
||||
}
|
||||
if(!networks) {
|
||||
*count = group->netids.size();
|
||||
return icsneoc2_error_success;
|
||||
}
|
||||
size_t to_copy = std::min<size_t>(*count, group->netids.size());
|
||||
for(size_t i = 0; i < to_copy; i++) {
|
||||
networks[i] = group->netids[i];
|
||||
}
|
||||
*count = to_copy;
|
||||
return icsneoc2_error_success;
|
||||
}
|
||||
|
||||
icsneoc2_error_t icsneoc2_settings_termination_groups_free(icsneoc2_termination_group_t* groups) {
|
||||
if(!groups) {
|
||||
return icsneoc2_error_invalid_parameters;
|
||||
}
|
||||
|
||||
while(groups) {
|
||||
auto* next = groups->next;
|
||||
delete groups;
|
||||
groups = next;
|
||||
}
|
||||
return icsneoc2_error_success;
|
||||
}
|
||||
|
||||
icsneoc2_error_t icsneoc2_settings_commander_resistor_enabled(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool* enabled) {
|
||||
// Make sure the device is valid
|
||||
auto res = icsneoc2_device_is_valid(device);
|
||||
|
||||
@@ -114,3 +114,11 @@ PerfTest
|
||||
.. literalinclude:: ../../examples/c2/perf_test/src/main.c
|
||||
:language: c
|
||||
|
||||
Termination Groups
|
||||
==================
|
||||
|
||||
:download:`Download example <../../examples/c2/termination/src/main.c>`
|
||||
|
||||
.. literalinclude:: ../../examples/c2/termination/src/main.c
|
||||
:language: c
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ option(LIBICSNEO_BUILD_C2_DISKFORMAT_EXAMPLE "Build the C2 disk format example."
|
||||
option(LIBICSNEO_BUILD_C2_RECONNECT_EXAMPLE "Build the C2 reconnect example." ON)
|
||||
option(LIBICSNEO_BUILD_C2_DEVICE_INFO_EXAMPLE "Build the C2 device info example." ON)
|
||||
option(LIBICSNEO_BUILD_C2_CHIP_VERSIONS_EXAMPLE "Build the C2 chip versions example." ON)
|
||||
option(LIBICSNEO_BUILD_C2_TERMINATION_EXAMPLE "Build the C2 termination groups example." ON)
|
||||
option(LIBICSNEO_BUILD_C2_LIN_EXAMPLE "Build the C2 LIN example." ON)
|
||||
option(LIBICSNEO_BUILD_C2_LIN_TRANSMIT_EXAMPLE "Build the C2 LIN transmit example." ON)
|
||||
option(LIBICSNEO_BUILD_C2_ETHERNET_TRANSMIT_EXAMPLE "Build the C2 ethernet transmit example." ON)
|
||||
@@ -78,6 +79,10 @@ if(LIBICSNEO_BUILD_C2_CHIP_VERSIONS_EXAMPLE)
|
||||
add_subdirectory(c2/chip_versions)
|
||||
endif()
|
||||
|
||||
if(LIBICSNEO_BUILD_C2_TERMINATION_EXAMPLE)
|
||||
add_subdirectory(c2/termination)
|
||||
endif()
|
||||
|
||||
if(LIBICSNEO_BUILD_C2_LIN_EXAMPLE)
|
||||
add_subdirectory(c2/lin)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
add_executable(libicsneoc2-termination-example src/main.c)
|
||||
target_link_libraries(libicsneoc2-termination-example icsneoc2-static)
|
||||
|
||||
if(WIN32)
|
||||
target_compile_definitions(libicsneoc2-termination-example PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
@@ -0,0 +1,91 @@
|
||||
#include <icsneo/icsneoc2.h>
|
||||
#include <icsneo/icsneoc2messages.h>
|
||||
#include <icsneo/icsneoc2settings.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int print_error_code(const char* message, icsneoc2_error_t error) {
|
||||
char error_str[64];
|
||||
size_t error_str_len = sizeof(error_str);
|
||||
icsneoc2_error_t res = icsneoc2_error_code_get(error, error_str, &error_str_len);
|
||||
if(res != icsneoc2_error_success) {
|
||||
printf("%s: Failed to get string for error code %u with error code %u\n", message, error, res);
|
||||
return (int)res;
|
||||
}
|
||||
printf("%s: \"%s\" (%u)\n", message, error_str, error);
|
||||
return (int)error;
|
||||
}
|
||||
|
||||
int main() {
|
||||
icsneoc2_error_t res;
|
||||
|
||||
icsneoc2_device_t* device = NULL;
|
||||
res = icsneoc2_device_open_first(0, icsneoc2_open_options_default, &device);
|
||||
if(res != icsneoc2_error_success) {
|
||||
return print_error_code("Failed to open first device", res);
|
||||
}
|
||||
|
||||
char description[128] = {0};
|
||||
size_t description_length = sizeof(description);
|
||||
icsneoc2_device_description_get(device, description, &description_length);
|
||||
printf("Opened device: %s\n\n", description);
|
||||
|
||||
size_t count = 0;
|
||||
icsneoc2_termination_group_t* groups = NULL;
|
||||
res = icsneoc2_settings_termination_groups_enumerate(device, &groups, &count);
|
||||
if(res != icsneoc2_error_success) {
|
||||
print_error_code("Failed to enumerate termination groups", res);
|
||||
icsneoc2_device_close(device);
|
||||
icsneoc2_device_free(device);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(count == 0) {
|
||||
printf("This device does not support software switchable termination.\n");
|
||||
} else {
|
||||
printf("Found %zu termination group(s) (only one network per group may be terminated at a time):\n", count);
|
||||
}
|
||||
|
||||
size_t group_index = 0;
|
||||
for(icsneoc2_termination_group_t* group = groups; group; group = icsneoc2_termination_group_next(group), group_index++) {
|
||||
// First call with a NULL buffer to learn how many networks are in this group.
|
||||
size_t network_count = 0;
|
||||
icsneoc2_termination_group_networks_get(group, NULL, &network_count);
|
||||
|
||||
icsneoc2_netid_t* netids = (icsneoc2_netid_t*)malloc(network_count * sizeof(icsneoc2_netid_t));
|
||||
if(!netids) {
|
||||
print_error_code("Out of memory", icsneoc2_error_out_of_memory);
|
||||
break;
|
||||
}
|
||||
|
||||
// Second call to fill the buffer.
|
||||
res = icsneoc2_termination_group_networks_get(group, netids, &network_count);
|
||||
if(res != icsneoc2_error_success) {
|
||||
print_error_code("Failed to get termination group networks", res);
|
||||
free(netids);
|
||||
continue;
|
||||
}
|
||||
|
||||
printf(" Group %zu:", group_index);
|
||||
for(size_t i = 0; i < network_count; i++) {
|
||||
char name[64] = {0};
|
||||
size_t name_length = sizeof(name);
|
||||
if(icsneoc2_netid_name_get(netids[i], name, &name_length) == icsneoc2_error_success) {
|
||||
printf(" %s", name);
|
||||
} else {
|
||||
printf(" %u", netids[i]);
|
||||
}
|
||||
if(i + 1 < network_count) {
|
||||
printf(",");
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
free(netids);
|
||||
}
|
||||
|
||||
icsneoc2_settings_termination_groups_free(groups);
|
||||
icsneoc2_device_close(device);
|
||||
icsneoc2_device_free(device);
|
||||
return 0;
|
||||
}
|
||||
@@ -140,7 +140,61 @@ icsneoc2_error_t icsneoc2_settings_termination_is_enabled(icsneoc2_device_t* dev
|
||||
*/
|
||||
icsneoc2_error_t icsneoc2_settings_termination_set(icsneoc2_device_t* device, icsneoc2_netid_t netid, bool enable);
|
||||
|
||||
// TODO: getTerminationGroups
|
||||
/**
|
||||
* Enumerate the termination groups for a device.
|
||||
*
|
||||
* Some devices have groupings of networks where software switchable termination
|
||||
* can only be applied to one network in the group at a time. This enumerates
|
||||
* those groups. Use icsneoc2_termination_group_next() to walk the list and
|
||||
* icsneoc2_termination_group_networks_get() to read each group's networks.
|
||||
*
|
||||
* These groups apply to the CAN termination controlled by the
|
||||
* icsneoc2_settings_termination_* functions. 10BASE-T1S termination
|
||||
* (icsneoc2_settings_t1s_*) is per-network and is never grouped.
|
||||
*
|
||||
* If the device does not support software switchable termination, the list is
|
||||
* empty (*groups is set to NULL and *count, if provided, is set to 0).
|
||||
*
|
||||
* @param[in] device The device to query.
|
||||
* @param[out] groups Receives a newly allocated termination group handle, or NULL if there are no groups. The caller owns this handle and must free it with icsneoc2_settings_termination_groups_free() when done.
|
||||
* @param[out] count Receives the number of termination groups. May be NULL if not needed.
|
||||
*
|
||||
* @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters or icsneoc2_error_out_of_memory otherwise.
|
||||
*/
|
||||
icsneoc2_error_t icsneoc2_settings_termination_groups_enumerate(icsneoc2_device_t* device, icsneoc2_termination_group_t** groups, size_t* count);
|
||||
|
||||
/**
|
||||
* Advance to the next termination group in an enumeration list.
|
||||
*
|
||||
* @param[in] group The current termination group handle.
|
||||
*
|
||||
* @return The next termination group handle, or NULL at the end of the list.
|
||||
*/
|
||||
icsneoc2_termination_group_t* icsneoc2_termination_group_next(const icsneoc2_termination_group_t* group);
|
||||
|
||||
/**
|
||||
* Get the networks belonging to a termination group.
|
||||
*
|
||||
* Call with networks set to NULL to query the number of networks in the group;
|
||||
* *count will be set to the required size. Then call again with a buffer of at
|
||||
* least that size. On success *count is updated with the number of netids written.
|
||||
*
|
||||
* @param[in] group The termination group handle to query.
|
||||
* @param[out] networks Pointer to a buffer to copy the group's netids into. May be NULL to query the required size.
|
||||
* @param[in,out] count On input, the capacity of the networks buffer. On output, the number of netids written (or required, if networks is NULL).
|
||||
*
|
||||
* @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise.
|
||||
*/
|
||||
icsneoc2_error_t icsneoc2_termination_group_networks_get(const icsneoc2_termination_group_t* group, icsneoc2_netid_t* networks, size_t* count);
|
||||
|
||||
/**
|
||||
* Free a termination group list returned by icsneoc2_settings_termination_groups_enumerate().
|
||||
*
|
||||
* @param[in] groups The termination group handle to free. Passing NULL returns icsneoc2_error_invalid_parameters.
|
||||
*
|
||||
* @return icsneoc2_error_t icsneoc2_error_success if successful, icsneoc2_error_invalid_parameters otherwise.
|
||||
*/
|
||||
icsneoc2_error_t icsneoc2_settings_termination_groups_free(icsneoc2_termination_group_t* groups);
|
||||
|
||||
/**
|
||||
* Check if the commander resistor is currently enabled for a network.
|
||||
|
||||
@@ -546,6 +546,8 @@ typedef uint8_t icsneoc2_tc10_sleep_status_t;
|
||||
|
||||
typedef struct icsneoc2_chip_versions_t icsneoc2_chip_versions_t;
|
||||
|
||||
typedef struct icsneoc2_termination_group_t icsneoc2_termination_group_t;
|
||||
|
||||
typedef enum _icsneoc2_chip_id_t {
|
||||
icsneoc2_chip_id_neovifire_mchip = 0,
|
||||
icsneoc2_chip_id_neovifire_lchip = 1,
|
||||
|
||||
@@ -261,6 +261,13 @@ TEST(icsneoc2, test_icsneoc2_error_invalid_parameters_and_invalid_device)
|
||||
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_termination_can_enable(NULL, 0, &placeholderBool));
|
||||
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_termination_is_enabled(NULL, 0, &placeholderBool));
|
||||
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_termination_set(NULL, 0, false));
|
||||
{
|
||||
icsneoc2_termination_group_t* placeholderGroups = nullptr;
|
||||
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_termination_groups_enumerate(NULL, &placeholderGroups, &placeholderSizeT));
|
||||
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_termination_group_networks_get(NULL, NULL, &placeholderSizeT));
|
||||
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_termination_groups_free(NULL));
|
||||
ASSERT_EQ(nullptr, icsneoc2_termination_group_next(NULL));
|
||||
}
|
||||
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_commander_resistor_enabled(NULL, 0, &placeholderBool));
|
||||
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_commander_resistor_set(NULL, 0, false));
|
||||
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_lin_mode_get(NULL, 0, &placeholderLinMode));
|
||||
@@ -1143,6 +1150,64 @@ TEST(icsneoc2, test_icsneoc2_chip_versions_free_list)
|
||||
ASSERT_EQ(icsneoc2_error_success, icsneoc2_chip_versions_free(head));
|
||||
}
|
||||
|
||||
TEST(icsneoc2, test_icsneoc2_termination_group_walk_and_networks)
|
||||
{
|
||||
// Build an in-memory list of two termination groups:
|
||||
// group A: { DWCAN_01, DWCAN_02 }
|
||||
// group B: { DWCAN_03 }
|
||||
icsneoc2_termination_group_t second{};
|
||||
second.netids = { icsneoc2_netid_dwcan_03 };
|
||||
second.next = nullptr;
|
||||
|
||||
icsneoc2_termination_group_t first{};
|
||||
first.netids = { icsneoc2_netid_dwcan_01, icsneoc2_netid_dwcan_02 };
|
||||
first.next = &second;
|
||||
|
||||
// next() walks the list
|
||||
ASSERT_EQ(&second, icsneoc2_termination_group_next(&first));
|
||||
ASSERT_EQ(nullptr, icsneoc2_termination_group_next(&second));
|
||||
|
||||
// networks_get with NULL buffer reports the required count
|
||||
size_t count = 0;
|
||||
ASSERT_EQ(icsneoc2_error_success, icsneoc2_termination_group_networks_get(&first, NULL, &count));
|
||||
ASSERT_EQ(count, 2u);
|
||||
|
||||
// networks_get fills the buffer and updates count
|
||||
icsneoc2_netid_t netids[2] = {0, 0};
|
||||
count = 2;
|
||||
ASSERT_EQ(icsneoc2_error_success, icsneoc2_termination_group_networks_get(&first, netids, &count));
|
||||
ASSERT_EQ(count, 2u);
|
||||
ASSERT_EQ(netids[0], icsneoc2_netid_dwcan_01);
|
||||
ASSERT_EQ(netids[1], icsneoc2_netid_dwcan_02);
|
||||
|
||||
// second group has a single network
|
||||
count = 0;
|
||||
ASSERT_EQ(icsneoc2_error_success, icsneoc2_termination_group_networks_get(&second, NULL, &count));
|
||||
ASSERT_EQ(count, 1u);
|
||||
|
||||
// a buffer smaller than the group truncates to capacity
|
||||
icsneoc2_netid_t one[1] = {0};
|
||||
count = 1;
|
||||
ASSERT_EQ(icsneoc2_error_success, icsneoc2_termination_group_networks_get(&first, one, &count));
|
||||
ASSERT_EQ(count, 1u);
|
||||
ASSERT_EQ(one[0], icsneoc2_netid_dwcan_01);
|
||||
}
|
||||
|
||||
TEST(icsneoc2, test_icsneoc2_termination_groups_free_list)
|
||||
{
|
||||
// Allocate a two-node list the same way the API does, so free() walks and deletes it.
|
||||
auto* head = new icsneoc2_termination_group_t{};
|
||||
head->netids = { icsneoc2_netid_dwcan_01 };
|
||||
head->next = new icsneoc2_termination_group_t{};
|
||||
head->next->netids = { icsneoc2_netid_dwcan_02 };
|
||||
head->next->next = nullptr;
|
||||
|
||||
ASSERT_EQ(icsneoc2_error_success, icsneoc2_settings_termination_groups_free(head));
|
||||
|
||||
// Freeing an empty (NULL) list returns invalid_parameters, matching chip_versions_free.
|
||||
ASSERT_EQ(icsneoc2_error_invalid_parameters, icsneoc2_settings_termination_groups_free(nullptr));
|
||||
}
|
||||
|
||||
TEST(icsneoc2, test_icsneoc2_open_options_default)
|
||||
{
|
||||
icsneoc2_open_options_t expected = ICSNEOC2_OPEN_OPTIONS_GO_ONLINE | ICSNEOC2_OPEN_OPTIONS_SYNC_RTC | ICSNEOC2_OPEN_OPTIONS_ENABLE_AUTO_UPDATE;
|
||||
|
||||
Reference in New Issue
Block a user