stm32cubeide/Relays/Core/Src/MySensors.c
Jochen Friedrich 66c5a26d69 Initial commit
2021-01-01 14:06:20 +01:00

445 lines
12 KiB
C

#include "main.h"
#include <string.h>
#include <stdlib.h>
#include "MySensors.h"
#define ICSC_SYS_PACK 0x58
#define SOH 1
#define STX 2
#define ETX 3
#define EOT 4
// message buffers
MyMessage _msg; // Buffer for incoming messages
MyMessage _msgTmp; // Buffer for temporary messages (acks and nonces among others)
// Receiving header information
char _header[6];
// Reception state machine control and storage variables
unsigned char _recPhase;
unsigned char _recPos;
unsigned char _recCommand;
unsigned char _recLen;
unsigned char _recStation;
unsigned char _recSender;
unsigned char _recCS;
unsigned char _recCalcCS;
unsigned char _packet_received;
UART_HandleTypeDef *_huart;
char _data[MY_RS485_MAX_MESSAGE_LENGTH];
uint8_t _packet_len;
unsigned char _packet_from;
_Bool send(MyMessage *message, uint8_t data_type)
{
message->last = MY_NODE_ID;
message->sender = MY_NODE_ID;
message->destination = GATEWAY_ADDRESS;
message->command_echo_payload = (data_type << 5) + C_SET;
return transportSend(message);
}
_Bool sendHeartbeat(void)
{
_msgTmp.last = MY_NODE_ID;
_msgTmp.sender = MY_NODE_ID;
_msgTmp.destination = GATEWAY_ADDRESS;
_msgTmp.command_echo_payload = (P_ULONG32 << 5) + C_INTERNAL;
_msgTmp.type = I_HEARTBEAT_RESPONSE;
_msgTmp.version_length = (4 << 3) + V2_MYS_HEADER_PROTOCOL_VERSION;
_msgTmp.ulValue = 0;
return transportSend(&_msgTmp);
}
_Bool present(const uint8_t childSensorId, const mysensors_sensor_t sensorType, char *desc)
{
_msgTmp.last = MY_NODE_ID;
_msgTmp.sender = MY_NODE_ID;
_msgTmp.destination = GATEWAY_ADDRESS;
_msgTmp.command_echo_payload = (P_STRING << 5) + C_PRESENTATION;
_msgTmp.type = sensorType;
_msgTmp.sensor = childSensorId;
_msgTmp.version_length = (strlen(desc) << 3) + V2_MYS_HEADER_PROTOCOL_VERSION;
strcpy((char *)_msgTmp.data, desc);
return transportSend(&_msgTmp);
}
void registerNode(void)
{
_msgTmp.last = MY_NODE_ID;
_msgTmp.sender = MY_NODE_ID;
_msgTmp.destination = GATEWAY_ADDRESS;
_msgTmp.command_echo_payload = (P_BYTE << 5) + C_INTERNAL;
_msgTmp.type = I_REGISTRATION_REQUEST;
_msgTmp.sensor = 0;
_msgTmp.version_length = (1 << 3) + V2_MYS_HEADER_PROTOCOL_VERSION;
_msgTmp.bValue = MY_CORE_VERSION;
transportSend(&_msgTmp);
}
void sendLibraryInfo(void)
{
_msgTmp.last = MY_NODE_ID;
_msgTmp.sender = MY_NODE_ID;
_msgTmp.destination = GATEWAY_ADDRESS;
_msgTmp.command_echo_payload = (P_STRING << 5) + C_INTERNAL;
_msgTmp.type = I_VERSION;
_msgTmp.version_length = (strlen(MY_LIBRARY_VERSION) << 3) + V2_MYS_HEADER_PROTOCOL_VERSION;
strcpy((char *)_msgTmp.data, MY_LIBRARY_VERSION);
transportSend(&_msgTmp);
}
_Bool sendSketchInfo(const char *name, const char *version)
{
_Bool result = 1;
if (name) {
_msgTmp.last = MY_NODE_ID;
_msgTmp.sender = MY_NODE_ID;
_msgTmp.destination = GATEWAY_ADDRESS;
_msgTmp.command_echo_payload = (P_STRING << 5) + C_INTERNAL;
_msgTmp.type = I_SKETCH_NAME;
_msgTmp.version_length = (strlen(name) << 3) + V2_MYS_HEADER_PROTOCOL_VERSION;
strcpy((char *)_msgTmp.data, name);
result &= transportSend(&_msgTmp);
}
if (version) {
_msgTmp.last = MY_NODE_ID;
_msgTmp.sender = MY_NODE_ID;
_msgTmp.destination = GATEWAY_ADDRESS;
_msgTmp.command_echo_payload = (P_STRING << 5) + C_INTERNAL;
_msgTmp.type = I_SKETCH_VERSION;
_msgTmp.version_length = (strlen(name) << 3) + V2_MYS_HEADER_PROTOCOL_VERSION;
strcpy((char *)_msgTmp.data, version);
result &= transportSend(&_msgTmp);
}
sendLibraryInfo();
return result;
}
// Message delivered through _msg
_Bool _processInternalCoreMessage(void)
{
const uint8_t type = _msg.type;
if (_msg.sender == GATEWAY_ADDRESS) {
if (type == I_PRESENTATION) {
// Re-send node presentation to controller
present_node();
} else if (type == I_HEARTBEAT_REQUEST) {
(void)sendHeartbeat();
} else if (type == I_REBOOT) {
NVIC_SystemReset();
} else if (type == I_VERSION) {
sendLibraryInfo();
} else {
return 0; // further processing required
}
} else {
return 0; // further processing required
}
return 1; // if not GW or no further processing required
}
void transportProcessMessage(void)
{
// get message length and limit size
const uint8_t msgLength = (_msg.version_length & 0xF8) >> 3;
// calculate expected length
const uint8_t command = _msg.command_echo_payload & 0x07;
const uint8_t type = _msg.type;
const uint8_t sender = _msg.sender;
const uint8_t destination = _msg.destination;
// Is message addressed to this node?
if (destination == MY_NODE_ID) {
// null terminate data
_msg.data[msgLength] = 0u;
// Check if sender requests an echo.
if (_msg.command_echo_payload & 0x08) {
memcpy(&_msgTmp, &_msg, sizeof(_msg)); // Copy message
// Reply without echo flag (otherwise we would end up in an eternal loop)
_msgTmp.command_echo_payload = _msgTmp.command_echo_payload & 0xE7;
_msgTmp.command_echo_payload = _msgTmp.command_echo_payload | 0x10;
_msgTmp.sender = MY_NODE_ID;
_msgTmp.destination = sender;
transportSend(&_msgTmp);
}
if(!(_msg.command_echo_payload & 0x10)) {
// only process if not ECHO
if (command == C_INTERNAL) {
if (type == I_ID_RESPONSE) {
return; // no further processing required
}
// general
if (type == I_PING) {
_msgTmp.last = MY_NODE_ID;
_msgTmp.sender = MY_NODE_ID;
_msgTmp.destination = sender;
_msgTmp.command_echo_payload = (P_BYTE << 5) + C_INTERNAL;
_msgTmp.type = I_PONG;
_msgTmp.bValue = 1;
transportSend(&_msgTmp);
return; // no further processing required
}
if (type == I_PONG) {
return; // no further processing required
}
if (_processInternalCoreMessage()) {
return; // no further processing required
}
}
}
// Call incoming message callback if available
receive(&_msg);
} else if (destination == BROADCAST_ADDRESS) {
if (command == C_INTERNAL) {
if (type == I_DISCOVER_REQUEST) {
HAL_Delay(MY_NODE_ID * 50);
_msgTmp.last = MY_NODE_ID;
_msgTmp.sender = MY_NODE_ID;
_msgTmp.destination = sender;
_msgTmp.command_echo_payload = (P_BYTE << 5) + C_INTERNAL;
_msgTmp.type = I_DISCOVER_RESPONSE;
_msgTmp.bValue = GATEWAY_ADDRESS;
transportSend(&_msgTmp);
return; // no further processing required
}
}
if (command != C_INTERNAL) {
receive(&_msg);
}
}
}
//Reset the state machine and release the data pointer
void _serialReset()
{
_recPhase = 0;
_recPos = 0;
_recLen = 0;
_recCommand = 0;
_recCS = 0;
_recCalcCS = 0;
}
// This is the main reception state machine. Progress through the states
// is keyed on either special control characters, or counted number of bytes
// received. If all the data is in the right format, and the calculated
// checksum matches the received checksum, AND the destination station is
// our station ID, then look for a registered command that matches the
// command code. If all the above is true, execute the command's
// function.
_Bool _serialProcess()
{
unsigned char i;
if (!_byte_received) {
return 0;
}
_byte_received = 0;
switch(_recPhase) {
// Case 0 looks for the header. Bytes arrive in the serial interface and get
// shifted through a header buffer. When the start and end characters in
// the buffer match the SOH/STX pair, and the destination station ID matches
// our ID, save the header information and progress to the next state.
case 0:
memcpy(&_header[0],&_header[1],5);
_header[5] = _byte;
if ((_header[0] == SOH) && (_header[5] == STX) && (_header[1] != _header[2])) {
_recCalcCS = 0;
_recStation = _header[1];
_recSender = _header[2];
_recCommand = _header[3];
_recLen = _header[4];
for (i=1; i<=4; i++) {
_recCalcCS += _header[i];
}
_recPhase = 1;
_recPos = 0;
//Avoid _data[] overflow
if (_recLen >= MY_RS485_MAX_MESSAGE_LENGTH) {
_serialReset();
break;
}
//Check if we should process this message
//We reject the message if we are the sender
//We reject if we are not the receiver and message is not a broadcast
if ((_recSender == MY_NODE_ID) ||
(_recStation != MY_NODE_ID &&
_recStation != BROADCAST_ADDRESS)) {
_serialReset();
break;
}
if (_recLen == 0) {
_recPhase = 2;
}
}
break;
// Case 1 receives the data portion of the packet. Read in "_recLen" number
// of bytes and store them in the _data array.
case 1:
_data[_recPos++] = _byte;
_recCalcCS += _byte;
if (_recPos == _recLen) {
_recPhase = 2;
}
break;
// After the data comes a single ETX character. Do we have it? If not,
// reset the state machine to default and start looking for a new header.
case 2:
// Packet properly terminated?
if (_byte == ETX) {
_recPhase = 3;
} else {
_serialReset();
}
break;
// Next comes the checksum. We have already calculated it from the incoming
// data, so just store the incoming checksum byte for later.
case 3:
_recCS = _byte;
_recPhase = 4;
break;
// The final state - check the last character is EOT and that the checksum matches.
// If that test passes, then look for a valid command callback to execute.
// Execute it if found.
case 4:
if (_byte == EOT) {
if (_recCS == _recCalcCS) {
// First, check for system level commands. It is possible
// to register your own callback as well for system level
// commands which will be called after the system default
// hook.
switch (_recCommand) {
case ICSC_SYS_PACK:
_packet_from = _recSender;
_packet_len = _recLen;
_packet_received = 1;
break;
}
}
}
//Clear the data
_serialReset();
//Return true, we have processed one command
return 1;
break;
}
return 1;
}
_Bool transportSend(MyMessage* data)
{
const char *datap = (const char *)data;
unsigned char i;
unsigned char len;
unsigned char cs = 0;
// This is how many times to try and transmit before failing.
unsigned char timeout = 10;
// Let's start out by looking for a collision. If there has been anything seen in
// the last millisecond, then wait for a random time and check again.
while (_serialProcess()) {
unsigned char del;
del = rand() % 20;
for (i = 0; i < del; i++) {
HAL_Delay(1);
_serialProcess();
}
timeout--;
if (timeout == 0) {
// Failed to transmit!!!
return 0;
}
}
HAL_GPIO_WritePin(TEN_GPIO_Port, TEN_Pin, SET);
// Start of header by writing multiple SOH
i = SOH;
for(uint8_t w=0; w<MY_RS485_SOH_COUNT; w++) {
HAL_UART_Transmit(_huart, (uint8_t*)&i, 1, 20);
}
HAL_UART_Transmit(_huart, (uint8_t*)&data->destination, 1, 20);
cs += data->destination;
i = MY_NODE_ID;
HAL_UART_Transmit(_huart, (uint8_t*)&i, 1, 20);
cs += MY_NODE_ID;
i = ICSC_SYS_PACK; // Command code
HAL_UART_Transmit(_huart, (uint8_t*)&i, 1, 20);
cs += ICSC_SYS_PACK;
len = (data->version_length >> 3) + V2_MYS_HEADER_SIZE;
HAL_UART_Transmit(_huart, (uint8_t*)&len, 1, 20);
cs += len;
i = STX;
HAL_UART_Transmit(_huart, (uint8_t*)&i, 1, 20);
HAL_UART_Transmit(_huart, (uint8_t*)datap, len, len + 20);
for(i=0; i<len; i++) {
cs += datap[i];
}
i = ETX;
HAL_UART_Transmit(_huart, (uint8_t*)&i, 1, 20);
HAL_UART_Transmit(_huart, (uint8_t*)&cs, 1, 20);
i = EOT;
HAL_UART_Transmit(_huart, (uint8_t*)&i, 1, 20);
HAL_GPIO_WritePin(TEN_GPIO_Port, TEN_Pin, RESET);
return 1;
}
void transportInitialise(UART_HandleTypeDef *huart)
{
_huart = huart;
HAL_GPIO_WritePin(TEN_GPIO_Port, TEN_Pin, RESET);
_serialReset();
_byte_received = 0;
srand(MY_NODE_ID);
HAL_Delay(MY_NODE_ID * 50);
present_node();
}
void transportProcess(void)
{
_serialProcess();
if (transportReceive(&_msg))
transportProcessMessage();
}
uint8_t transportReceive(void* data)
{
if (_packet_received) {
memcpy(data,_data,_packet_len);
_packet_received = 0;
return _packet_len;
} else {
return (0);
}
}