Refactor/mstp zero config state machine (#676)

* Changed MS/TP master node self destination checks to be located in receive FSM

* Changed MSTP zero configuration: modified comments for state transition names; modified next station increment; refactored the UUID rand() to not be required by common zero config implementation; added more unit tests.

* Added another context to MS/TP user data to allow additional user data
This commit is contained in:
Steve Karg
2024-06-26 07:43:25 -05:00
committed by GitHub
parent 9e0751f8c9
commit ddb2b43125
16 changed files with 313 additions and 126 deletions
+3 -4
View File
@@ -77,7 +77,7 @@ static struct my_object_functions {
properties that are writable or that may change.
The properties that are constant can be hard coded
into the read-property encoding. */
static uint32_t Object_Instance_Number = 103;
static uint32_t Object_Instance_Number = BACNET_MAX_INSTANCE;
static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL;
static BACNET_CHARACTER_STRING My_Object_Name;
static uint32_t Database_Revision;
@@ -453,9 +453,8 @@ void Device_Init(object_functions_t *object_table)
pObject++;
}
dcc_set_status_duration(COMMUNICATION_ENABLE, 0);
if (Object_Instance_Number >= BACNET_MAX_INSTANCE) {
Object_Instance_Number = 103;
srand(Object_Instance_Number);
if (Object_Instance_Number > BACNET_MAX_INSTANCE) {
Object_Instance_Number = BACNET_MAX_INSTANCE;
}
characterstring_init_ansi(&My_Object_Name, "stm32-design-challenge-103");
}
+10 -2
View File
@@ -25,11 +25,13 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "hardware.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/datalink/datalink.h"
#include "bacnet/datalink/dlmstp.h"
#include "bacnet/datalink/mstp.h"
#include "bacnet/basic/object/device.h"
#include "rs485.h"
#include "led.h"
#include "bacnet.h"
@@ -38,7 +40,7 @@
char *BACnet_Version = "1.0";
/* MS/TP port */
static struct mstp_port_struct_t MSTP_Port;
static struct dlmstp_rs485_driver RS485_Driver = {
static struct dlmstp_rs485_driver RS485_Driver = {
.init = rs485_init,
.send = rs485_bytes_send,
.read = rs485_byte_available,
@@ -46,7 +48,7 @@ static struct dlmstp_rs485_driver RS485_Driver = {
.baud_rate = rs485_baud_rate,
.baud_rate_set = rs485_baud_rate_set,
.silence_milliseconds = rs485_silence_milliseconds,
.silence_reset = rs485_silence_reset
.silence_reset = rs485_silence_reset
};
static struct dlmstp_user_data_t MSTP_User_Data;
static uint8_t Input_Buffer[DLMSTP_MPDU_MAX];
@@ -144,6 +146,7 @@ static void mstp_configure(void)
/* user data */
MSTP_Port.ZeroConfigEnabled = true;
MSTP_Port.SlaveNodeEnabled = false;
MSTP_Zero_Config_UUID_Init(&MSTP_Port);
MSTP_User_Data.RS485_Driver = &RS485_Driver;
MSTP_Port.UserData = &MSTP_User_Data;
dlmstp_init((char *)&MSTP_Port);
@@ -163,6 +166,7 @@ static void mstp_configure(void)
int main(void)
{
struct mstimer Blink_Timer;
uint32_t Object_Instance_Number = 103;
/*At this stage the microcontroller clock setting is already configured,
this is done through SystemInit() function which is called from startup
@@ -178,6 +182,10 @@ int main(void)
mstimer_init();
lse_init();
led_init();
/* FIXME: get device instance from EEPROM */
(void)Device_Set_Object_Instance_Number(Object_Instance_Number);
/* seed libc random number generator */
srand(Object_Instance_Number);
/* initialize MSTP datalink layer */
mstp_configure();
/* initialize application layer*/
+73 -1
View File
@@ -81,6 +81,7 @@ static uint32_t Database_Revision;
static BACNET_REINITIALIZED_STATE Reinitialize_State = BACNET_REINIT_IDLE;
static const char *Reinit_Password = "stm32f4xx";
static const char *BACnet_Version = BACNET_VERSION_TEXT;
static uint8_t Device_UUID[16];
/* These three arrays are used by the ReadPropertyMultiple handler */
static const int Device_Properties_Required[] = { PROP_OBJECT_IDENTIFIER,
@@ -94,7 +95,8 @@ static const int Device_Properties_Required[] = { PROP_OBJECT_IDENTIFIER,
PROP_DATABASE_REVISION, -1 };
static const int Device_Properties_Optional[] = { PROP_DESCRIPTION,
PROP_LOCATION, PROP_MAX_MASTER, PROP_MAX_INFO_FRAMES, -1 };
PROP_LOCATION, PROP_MAX_MASTER, PROP_MAX_INFO_FRAMES, PROP_DEVICE_UUID,
-1 };
static const int Device_Properties_Proprietary[] = { -1 };
@@ -398,6 +400,71 @@ void Device_Inc_Database_Revision(void)
Database_Revision++;
}
/**
* @brief Initialize a UUID for storing the unique identifier of this device
* @note A Universally Unique IDentifier (UUID) - also called a
* Global Unique IDentifier (GUID) - is a 128-bit value, see RFC 4122.
*
* 4.4. Algorithms for Creating a UUID from Truly Random or
* Pseudo-Random Numbers
*
* The version 4 UUID is meant for generating UUIDs from truly-random or
* pseudo-random numbers.
*
* The algorithm is as follows:
*
* o Set the two most significant bits (bits 6 and 7) of the
* clock_seq_hi_and_reserved to zero and one, respectively.
*
* o Set the four most significant bits (bits 12 through 15) of the
* time_hi_and_version field to the 4-bit version number from
* Section 4.1.3.
*
* o Set all the other bits to randomly (or pseudo-randomly) chosen
* values.
*/
void Device_UUID_Init(void)
{
unsigned i = 0;
/* 1. Generate 16 random bytes = 128 bits */
for (i = 0; i < sizeof(Device_UUID); i++) {
Device_UUID[i] = rand() % 255;
}
/* 2. Adjust certain bits according to RFC 4122 section 4.4.
This just means do the following
(a) set the high nibble of the 7th byte equal to 4 and
(b) set the two most significant bits of the 9th byte to 10'B,
so the high nibble will be one of {8,9,A,B}.
From http://www.cryptosys.net/pki/Uuid.c.html */
Device_UUID[6] = 0x40 | (Device_UUID[6] & 0x0f);
Device_UUID[8] = 0x80 | (Device_UUID[8] & 0x3f);
}
/**
* @brief Set the UUID for this device
* @param new_uuid [in] The new UUID to set
* @param length [in] The length of the new UUID
*/
void Device_UUID_Set(uint8_t *new_uuid, size_t length)
{
if (new_uuid && (length == sizeof(Device_UUID))) {
memcpy(Device_UUID, new_uuid, sizeof(Device_UUID));
}
}
/**
* @brief Get the UUID for this device
* @param uuid [out] The UUID of this device
* @param length [in] The length of the UUID
*/
void Device_UUID_Get(uint8_t *uuid, size_t length)
{
if (uuid && (length == sizeof(Device_UUID))) {
memcpy(uuid, Device_UUID, sizeof(Device_UUID));
}
}
/** Get the total count of objects supported by this Device Object.
* @note Since many network clients depend on the object list
* for discovery, it must be consistent!
@@ -586,6 +653,7 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
int apdu_len = 0; /* return value */
BACNET_BIT_STRING bit_string = { 0 };
BACNET_CHARACTER_STRING char_string = { 0 };
BACNET_OCTET_STRING octet_string = { 0 };
uint32_t i = 0;
uint32_t count = 0;
uint8_t *apdu = NULL;
@@ -713,6 +781,10 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
apdu_len =
encode_application_unsigned(&apdu[0], dlmstp_max_master());
break;
case PROP_DEVICE_UUID:
octetstring_init(&octet_string, Device_UUID, sizeof(Device_UUID));
apdu_len = encode_application_octet_string(&apdu[0], &octet_string);
break;
default:
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+21 -17
View File
@@ -44,7 +44,7 @@
/* MS/TP port */
static struct mstp_port_struct_t MSTP_Port;
static struct dlmstp_rs485_driver RS485_Driver = {
static struct dlmstp_rs485_driver RS485_Driver = {
.init = rs485_init,
.send = rs485_bytes_send,
.read = rs485_byte_available,
@@ -52,7 +52,7 @@ static struct dlmstp_rs485_driver RS485_Driver = {
.baud_rate = rs485_baud_rate,
.baud_rate_set = rs485_baud_rate_set,
.silence_milliseconds = rs485_silence_milliseconds,
.silence_reset = rs485_silence_reset
.silence_reset = rs485_silence_reset
};
static struct dlmstp_user_data_t MSTP_User_Data;
static uint8_t Input_Buffer[DLMSTP_MPDU_MAX];
@@ -90,6 +90,24 @@ int main(void)
led_init();
rs485_init();
mstimer_set(&Blink_Timer, 500);
/* FIXME: get the device ID from EEPROM */
Device_Set_Object_Instance_Number(103);
/* seed stdlib rand() with device-id to get pseudo consistent
zero-config poll slot, or use hardware RNG to get a more random slot */
#ifdef BACNET_ZERO_CONFIG_RNG_HARDWARE
/* enable the random number generator hardware */
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_RNG, ENABLE);
RNG_Cmd(ENABLE);
while (RNG_GetFlagStatus(RNG_FLAG_DRDY) == RESET) {
/* wait for 32-bit random number to generate */
}
srand(RNG_GetRandomNumber());
#else
srand(Device_Object_Instance_Number());
#endif
/* initialize the Device UUID from rand() */
Device_UUID_Init();
Device_UUID_Get(MSTP_Port.UUID, sizeof(MSTP_Port.UUID));
/* initialize MSTP datalink layer */
MSTP_Port.Nmax_info_frames = DLMSTP_MAX_INFO_FRAMES;
MSTP_Port.Nmax_master = DLMSTP_MAX_MASTER;
@@ -104,6 +122,7 @@ int main(void)
MSTP_Port.UserData = &MSTP_User_Data;
dlmstp_init((char *)&MSTP_Port);
if (MSTP_Port.ZeroConfigEnabled) {
/* set node to monitor address */
dlmstp_set_mac_address(255);
} else {
/* FIXME: get the address from hardware DIP or from EEPROM */
@@ -113,21 +132,6 @@ int main(void)
dlmstp_set_baud_rate(DLMSTP_BAUD_RATE_DEFAULT);
/* initialize application layer*/
bacnet_init();
/* FIXME: get the device ID from EEPROM */
Device_Set_Object_Instance_Number(103);
/* seed stdlib rand() with device-id to get pweudo consisten
zero-config poll slot, or use hardware RNG to get a more random slot */
#ifdef BACNET_ZERO_CONFIG_RNG_HARDWARE
/* enable the random number generator hardware */
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_RNG, ENABLE);
RNG_Cmd(ENABLE);
while (RNG_GetFlagStatus(RNG_FLAG_DRDY) == RESET) {
/* wait for 32-bit random number to generate */
}
srand(RNG_GetRandomNumber());
#else
srand(Device_Object_Instance_Number());
#endif
for (;;) {
if (mstimer_expired(&Blink_Timer)) {
mstimer_reset(&Blink_Timer);
-1
View File
@@ -48,7 +48,6 @@ CSRC = main.c \
led.c \
mstimer-init.c \
netport.c \
nvmdata.c \
rs485.c \
stack.c
+49 -13
View File
@@ -6,6 +6,7 @@
* @copyright SPDX-License-Identifier: MIT
*/
#include <asf.h>
#include "bacnet/basic/object/device.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/datalink/datalink.h"
#include "bacnet/datalink/dlmstp.h"
@@ -14,12 +15,13 @@
#include "led.h"
#include "adc-hdw.h"
#include "bacnet.h"
#include "nvmdata.h"
static struct mstimer_callback_data_t BACnet_Callback;
/* MS/TP port */
static struct mstp_port_struct_t MSTP_Port;
static struct dlmstp_rs485_driver RS485_Driver = {
static struct dlmstp_rs485_driver RS485_Driver = {
.init = rs485_init,
.send = rs485_bytes_send,
.read = rs485_byte_available,
@@ -27,17 +29,49 @@ static struct dlmstp_rs485_driver RS485_Driver = {
.baud_rate = rs485_baud_rate,
.baud_rate_set = rs485_baud_rate_set,
.silence_milliseconds = rs485_silence_milliseconds,
.silence_reset = rs485_silence_reset
.silence_reset = rs485_silence_reset
};
static struct dlmstp_user_data_t MSTP_User_Data;
static uint8_t Input_Buffer[DLMSTP_MPDU_MAX];
static uint8_t Output_Buffer[DLMSTP_MPDU_MAX];
/**
* Initializes some data from non-volatile memory module
*/
static void nvm_data_init(void)
{
uint32_t device_id = 127;
uint8_t max_master = 127;
uint8_t mac_address = 127;
uint8_t kbaud_rate = 38;
nvm_read(NVM_BAUD_K, &kbaud_rate, 1);
rs485_kbaud_rate_set(kbaud_rate);
nvm_read(NVM_MAC_ADDRESS, &mac_address, 1);
dlmstp_set_mac_address(mac_address);
nvm_read(NVM_MAX_MASTER, &max_master, 1);
if (max_master > 127) {
max_master = 127;
}
dlmstp_set_max_master(max_master);
/* Get the device ID from the EEPROM */
nvm_read(NVM_DEVICE_0, (uint8_t *)&device_id, sizeof(device_id));
if (device_id < BACNET_MAX_INSTANCE) {
Device_Set_Object_Instance_Number(device_id);
} else {
Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE);
}
}
/**
* @brief MS/TP configuraiton
*/
static void dlmstp_configure(void)
{
uint8_t mac_address = 0;
/* initialize MSTP datalink layer */
MSTP_Port.Nmax_info_frames = DLMSTP_MAX_INFO_FRAMES;
MSTP_Port.Nmax_master = DLMSTP_MAX_MASTER;
@@ -46,19 +80,21 @@ static void dlmstp_configure(void)
MSTP_Port.OutputBuffer = Output_Buffer;
MSTP_Port.OutputBufferSize = sizeof(Output_Buffer);
/* user data */
MSTP_Port.ZeroConfigEnabled = true;
MSTP_Port.SlaveNodeEnabled = false;
mac_address = dlmstp_mac_address();
if (mac_address == 255) {
MSTP_Port.ZeroConfigEnabled = true;
} else {
MSTP_Port.ZeroConfigEnabled = false;
}
if (mac_address <= 127) {
MSTP_Port.SlaveNodeEnabled = false;
} else {
MSTP_Port.SlaveNodeEnabled = true;
}
MSTP_Zero_Config_UUID_Init(&MSTP_Port);
MSTP_User_Data.RS485_Driver = &RS485_Driver;
MSTP_Port.UserData = &MSTP_User_Data;
dlmstp_init((char *)&MSTP_Port);
if (MSTP_Port.ZeroConfigEnabled) {
dlmstp_set_mac_address(255);
} else {
/* FIXME: get the address from hardware DIP or from EEPROM */
dlmstp_set_mac_address(1);
}
/* FIXME: get the baud rate from hardware DIP or from EEPROM */
dlmstp_set_baud_rate(DLMSTP_BAUD_RATE_DEFAULT);
}
/**
@@ -87,7 +123,7 @@ int main(void)
}
cpu_irq_enable();
/* application initialization */
rs485_baud_rate_set(38400);
nvm_data_init();
dlmstp_configure();
bacnet_init();
/* run forever - timed tasks */
-41
View File
@@ -1,41 +0,0 @@
/**
* @file
* @brief Store and retrieve non-volatile data
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2013
* @copyright SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdbool.h>
#include "nvmdata.h"
#include "bacnet/datalink/dlmstp.h"
#include "bacnet/basic/object/device.h"
/**
* Initializes the non-volatile memory module
*/
void nvm_data_init(void)
{
uint32_t device_id = 127;
uint8_t max_master = 127;
uint8_t mac_address = 127;
nvm_read(NVM_MAC_ADDRESS, &mac_address, 1);
if (mac_address == 255) {
/* uninitialized */
mac_address = 123;
}
dlmstp_set_mac_address(mac_address);
nvm_read(NVM_MAX_MASTER, &max_master, 1);
if (max_master > 127) {
max_master = 127;
}
dlmstp_set_max_master(max_master);
/* Get the device ID from the EEPROM */
nvm_read(NVM_DEVICE_0, (uint8_t *)&device_id, sizeof(device_id));
if (device_id < BACNET_MAX_INSTANCE) {
Device_Set_Object_Instance_Number(device_id);
} else {
Device_Set_Object_Instance_Number(mac_address);
}
}
+2 -12
View File
@@ -10,13 +10,13 @@
#include <avr/eeprom.h>
/* compatible functions could put in nvm.h to abstract more */
/* compatible functions could put in header to abstract more */
#define nvm_write(dst, src, len) \
eeprom_write_block((uint8_t *)(src),(uint8_t *)(dst), (size_t)(len))
#define nvm_read(src, dst, len) \
eeprom_read_block((uint8_t *)dst, (const uint8_t *)(src),(size_t)(len))
/*=============== EEPROM ================*/
/* define EEPROM signature version */
#define NVM_SIGNATURE 0
@@ -54,14 +54,4 @@
/* free space 128..4096 */
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
void nvm_data_init(void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+38
View File
@@ -210,6 +210,44 @@ uint32_t rs485_baud_rate(void)
return Baud_Rate;
}
/**
* @brief Set the baud in kili-baud
* @param baud_k baud rate in approximate kilobaud
*/
bool rs485_kbaud_rate_set(uint8_t baud_k)
{
uint32_t baud = 38400;
if (baud_k == 255) {
baud = 38400;
} else if (baud_k >= 115) {
baud = 115200;
} else if (baud_k >= 76) {
baud = 76800;
} else if (baud_k >= 57) {
baud = 57600;
} else if (baud_k >= 38) {
baud = 38400;
} else if (baud_k >= 19) {
baud = 19200;
} else if (baud_k >= 9) {
baud = 9600;
}
return rs485_baud_rate_set(baud);
}
/**
* Converts baud in bps to kili-baud
*
* @param baud - baud rate in bps
* @return: baud rate in approximate kilo-baud
*/
uint8_t rs485_kbaud_rate(void)
{
return Baud_Rate/1000;
}
/**
* Initialize the RS-485 baud rate
*
+3
View File
@@ -25,6 +25,9 @@ void rs485_bytes_send(uint8_t *buffer, uint16_t nbytes);
uint32_t rs485_baud_rate(void);
bool rs485_baud_rate_set(uint32_t baud);
bool rs485_kbaud_rate_set(uint8_t baud_k);
uint8_t rs485_kbaud_rate(void);
uint32_t rs485_silence_milliseconds(void);
void rs485_silence_reset(void);