Transport update (#722)

* Major transport rework

* Adjust signal report calculations

* Minor driver optimizations

* Implement sanity check in legacy RFM69 driver

* Additional RFM69 rework

* RFM95 encryption

* Polishing
This commit is contained in:
Olivier
2017-03-28 20:31:08 +02:00
committed by Henrik Ekblad
parent 63b8d935c8
commit b132a8a81e
37 changed files with 5986 additions and 1063 deletions
+157
View File
@@ -0,0 +1,157 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2017 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#ifndef MyTransportHAL_h
#define MyTransportHAL_h
#define INVALID_SNR ((int16_t)-256) //!< INVALID_SNR
#define INVALID_RSSI ((int16_t)-256) //!< INVALID_RSSI
#define INVALID_PERCENT ((int16_t)-100) //!< INVALID_PERCENT
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
#if defined(MY_RADIO_RFM69)
#error Receive message buffering not supported for RFM69!
#endif
#if defined(MY_RADIO_RFM95)
#error Receive message buffering not supported for RFM95!
#endif
#if defined(MY_RS485)
#error Receive message buffering not supported for RS485!
#endif
#elif defined(MY_RX_MESSAGE_BUFFER_SIZE)
#error Receive message buffering requires message buffering feature enabled!
#endif
/**
* @brief Signal report selector
*/
typedef enum {
SR_RX_RSSI, //!< SR_RX_RSSI
SR_TX_RSSI, //!< SR_TX_RSSI
SR_RX_SNR, //!< SR_RX_SNR
SR_TX_SNR, //!< SR_TX_SNR
SR_TX_POWER_LEVEL, //!< SR_TX_POWER_LEVEL
SR_TX_POWER_PERCENT, //!< SR_TX_POWER_PERCENT
SR_UPLINK_QUALITY, //!< SR_UPLINK_QUALITY
SR_NOT_DEFINED //!< SR_NOT_DEFINED
} signalReport_t;
/**
* @brief Initialize transport HW
* @return true if initialization successful
*/
bool transportInit(void);
/**
* @brief Set node address
*/
void transportSetAddress(const uint8_t address);
/**
* @brief Retrieve node address
*/
uint8_t transportGetAddress(void);
/**
* @brief Send message
* @param to recipient
* @param data message to be sent
* @param len length of message (header + payload)
* @param noACK do not wait for ACK
* @return true if message sent successfully
*/
bool transportSend(const uint8_t to, const void* data, const uint8_t len,
const bool noACK = false);
/**
* @brief Verify if RX FIFO has pending messages
* @return true if message available in RX FIFO
*/
bool transportAvailable(void);
/**
* @brief Sanity check for transport: is transport still responsive?
* @return true if transport HW is ok
*/
bool transportSanityCheck(void);
/**
* @brief Receive message from FIFO
* @return length of recevied message (header + payload)
*/
uint8_t transportReceive(void* data);
/**
* @brief Power down transport HW (if corresponding MY_XXXX_POWER_PIN defined)
*/
void transportPowerDown(void);
/**
* @brief Power up transport HW (if corresponding MY_XXXX_POWER_PIN defined)
*/
void transportPowerUp(void);
/**
* @brief Set transport HW to sleep (no power down)
*/
void transportSleep(void);
/**
* @brief Set transport HW to standby
*/
void transportStandBy(void);
// experimental
/**
* @brief transportSignalReport
* @param command
* @return
*/
int16_t transportSignalReport(const char command);
/**
* @brief transportGetSignalReport
* @param signalReport
* @return
*/
int16_t transportGetSignalReport(const signalReport_t signalReport = SR_RX_RSSI);
/**
* @brief transportGetSendingRSSI
* @return RSSI
*/
int16_t transportGetSendingRSSI(void);
/**
* @brief transportGetReceivingRSSI
* @return RSSI
*/
int16_t transportGetReceivingRSSI(void);
/**
* @brief transportGetSendingSNR
* @return SNR
*/
int16_t transportGetSendingSNR(void);
/**
* @brief transportGetReceivingSNR
* @return SNR
*/
int16_t transportGetReceivingSNR(void);
/**
* @brief transportGetTxPowerPercent
* @return TX power in percent
*/
int16_t transportGetTxPowerPercent(void);
/**
* @brief transportGetTxPowerLevel
* @return TX power in dBm
*/
int16_t transportGetTxPowerLevel(void);
#endif // MyTransportHAL_h
+205
View File
@@ -0,0 +1,205 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2017 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyTransportHAL.h"
#include "drivers/RF24/RF24.h"
#if defined(MY_RF24_ENABLE_ENCRYPTION)
#include "drivers/AES/AES.h"
#endif
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
#include "drivers/CircularBuffer/CircularBuffer.h"
typedef struct _transportQueuedMessage {
uint8_t m_len; // Length of the data
uint8_t m_data[MAX_MESSAGE_LENGTH]; // The raw data
} transportQueuedMessage;
/** Buffer to store queued messages in. */
static transportQueuedMessage transportRxQueueStorage[MY_RX_MESSAGE_BUFFER_SIZE];
/** Circular buffer, which uses the transportRxQueueStorage and administers stored messages. */
static CircularBuffer<transportQueuedMessage> transportRxQueue(transportRxQueueStorage,
MY_RX_MESSAGE_BUFFER_SIZE);
static volatile uint8_t transportLostMessageCount = 0;
static void transportRxCallback(void)
{
// Called for each message received by radio, from interrupt context.
// This function _must_ call RF24_readMessage() to de-assert interrupt line!
if (!transportRxQueue.full()) {
transportQueuedMessage* msg = transportRxQueue.getFront();
msg->m_len = RF24_readMessage(msg->m_data); // Read payload & clear RX_DR
(void)transportRxQueue.pushFront(msg);
} else {
// Queue is full. Discard message.
(void)RF24_readMessage(NULL); // Read payload & clear RX_DR
// Keep track of messages lost. Max 255, prevent wrapping.
if (transportLostMessageCount < 255) {
++transportLostMessageCount;
}
}
}
#endif
#if defined(MY_RF24_ENABLE_ENCRYPTION)
AES _aes;
uint8_t _dataenc[32] = {0};
uint8_t _psk[16];
#endif
bool transportInit(void)
{
#if defined(MY_RF24_ENABLE_ENCRYPTION)
hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
//set up AES-key
_aes.set_key(_psk, 16);
// Make sure it is purged from memory when set
memset(_psk, 0, 16);
#endif
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
RF24_registerReceiveCallback( transportRxCallback );
#endif
return RF24_initialize();
}
void transportSetAddress(const uint8_t address)
{
RF24_setNodeAddress(address);
RF24_startListening();
}
uint8_t transportGetAddress(void)
{
return RF24_getNodeID();
}
bool transportSend(const uint8_t to, const void* data, const uint8_t len, const bool noACK)
{
#if defined(MY_RF24_ENABLE_ENCRYPTION)
// copy input data because it is read-only
(void)memcpy(_dataenc,data,len);
// has to be adjusted, WIP!
_aes.set_IV(0);
const uint8_t finalLength = len > 16 ? 32 : 16;
//encrypt data
_aes.cbc_encrypt(_dataenc, _dataenc, finalLength /16);
return RF24_sendMessage(to, _dataenc, finalLength, noACK);
#else
return RF24_sendMessage(to, data, len, noACK);
#endif
}
bool transportAvailable(void)
{
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
(void)RF24_isDataAvailable; // Prevent 'defined but not used' warning
return !transportRxQueue.empty();
#else
return RF24_isDataAvailable();
#endif
}
bool transportSanityCheck(void)
{
return RF24_sanityCheck();
}
uint8_t transportReceive(void* data)
{
uint8_t len = 0;
#if defined(MY_RX_MESSAGE_BUFFER_FEATURE)
transportQueuedMessage* msg = transportRxQueue.getBack();
if (msg) {
len = msg->m_len;
(void)memcpy(data, msg->m_data, len);
(void)transportRxQueue.popBack();
}
#else
len = RF24_readMessage(data);
#endif
#if defined(MY_RF24_ENABLE_ENCRYPTION)
// has to be adjusted, WIP!
_aes.set_IV(0);
// decrypt data
if (_aes.cbc_decrypt((uint8_t*)(data), (uint8_t*)(data), len > 16 ? 2 : 1) != AES_SUCCESS) {
len = 0;
}
#endif
return len;
}
void transportSleep(void)
{
RF24_sleep();
}
void transportStandBy(void)
{
RF24_standBy();
}
void transportPowerDown(void)
{
RF24_powerDown();
}
void transportPowerUp(void)
{
RF24_powerUp();
}
int16_t transportGetSendingRSSI(void)
{
return RF24_getSendingRSSI();
}
int16_t transportGetReceivingRSSI(void)
{
// not available, only bool RPD
return static_cast<int16_t>(INVALID_RSSI);
}
int16_t transportGetSendingSNR(void)
{
return static_cast<int16_t>(INVALID_SNR);
}
int16_t transportGetReceivingSNR(void)
{
return static_cast<int16_t>(INVALID_SNR);
}
int16_t transportGetTxPowerPercent(void)
{
return static_cast<int16_t>(RF24_getTxPowerPercent());
}
int16_t transportGetTxPowerLevel(void)
{
return static_cast<int16_t>(RF24_getTxPowerLevel());
}
bool transportSetTxPowerPercent(const uint8_t powerPercent)
{
return RF24_setTxPowerPercent(powerPercent);
}
+263
View File
@@ -0,0 +1,263 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2017 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include "MyTransportHAL.h"
#if defined(MY_RFM69_NEW_DRIVER)
#include "drivers/RFM69/new/RFM69_new.h"
bool transportInit(void)
{
const bool result = RFM69_initialise(MY_RFM69_FREQUENCY);
#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_RFM69_ATC_MODE_DISABLED)
// only enable ATC mode nodes
RFM69_ATCmode(true, MY_RFM69_ATC_TARGET_RSSI_DBM);
#endif
#ifdef MY_RFM69_ENABLE_ENCRYPTION
uint8_t _psk[16];
hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
RFM69_encrypt((const char*)_psk);
(void)memset(_psk, 0, 16); // Make sure it is purged from memory when set
#endif
return result;
}
void transportSetAddress(const uint8_t address)
{
RFM69_setAddress(address);
}
uint8_t transportGetAddress(void)
{
return RFM69_getAddress();
}
bool transportSend(const uint8_t to, const void* data, uint8_t len, const bool noACK)
{
if (noACK) {
(void)RFM69_sendWithRetry(to, data, len, 0, 0);
return true;
}
return RFM69_sendWithRetry(to, data, len);
}
bool transportAvailable(void)
{
return RFM69_available();
}
bool transportSanityCheck(void)
{
return RFM69_sanityCheck();
}
uint8_t transportReceive(void* data)
{
return RFM69_recv((uint8_t*)data, MAX_MESSAGE_LENGTH);
}
void transportSleep(void)
{
(void)RFM69_sleep();
}
void transportStandBy(void)
{
(void)RFM69_standBy();
}
void transportPowerDown(void)
{
(void)RFM69_powerDown();
}
void transportPowerUp(void)
{
(void)RFM69_powerUp();
}
bool transportSetTxPowerLevel(const uint8_t powerLevel)
{
// range 0..23
return RFM69_setTxPowerLevel(powerLevel);
}
void transportSetTargetRSSI(int16_t targetSignalStrength)
{
#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_RFM69_ATC_MODE_DISABLED)
RFM69_ATCmode(true, targetSignalStrength);
#endif
}
int16_t transportGetSendingRSSI(void)
{
return RFM69_getSendingRSSI();
}
int16_t transportGetReceivingRSSI(void)
{
return RFM69_getReceivingRSSI();
}
int16_t transportGetSendingSNR(void)
{
return INVALID_SNR;
}
int16_t transportGetReceivingSNR(void)
{
return INVALID_SNR;
}
int16_t transportGetTxPowerPercent(void)
{
return RFM69_getTxPowerPercent();
}
int16_t transportGetTxPowerLevel(void)
{
return RFM69_getTxPowerLevel();
}
bool transportSetTxPowerPercent(const uint8_t powerPercent)
{
return RFM69_setTxPowerPercent(powerPercent);
}
#else
#include "drivers/RFM69/old/RFM69_old.h"
RFM69 _radio(MY_RFM69_CS_PIN, MY_RFM69_IRQ_PIN, MY_RFM69HW, MY_RFM69_IRQ_NUM);
uint8_t _address;
bool transportInit(void)
{
#if defined(MY_RFM69_POWER_PIN)
//hwPinMode(MY_RFM69_POWER_PIN, OUTPUT);
#endif
#ifdef MY_RF69_DIO5
//hwPinMode(MY_RF69_DIO5, INPUT);
#endif
// Start up the radio library (_address will be set later by the MySensors library)
if (_radio.initialize(MY_RFM69_FREQUENCY, _address, MY_RFM69_NETWORKID)) {
#ifdef MY_RFM69_ENABLE_ENCRYPTION
uint8_t _psk[16];
hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16);
_radio.encrypt((const char*)_psk);
(void)memset(_psk, 0, 16); // Make sure it is purged from memory when set
#endif
return true;
}
return false;
}
void transportSetAddress(const uint8_t address)
{
_address = address;
_radio.setAddress(address);
}
uint8_t transportGetAddress(void)
{
return _address;
}
bool transportSend(const uint8_t to, const void* data, const uint8_t len, const bool noACK)
{
if (noACK) {
(void)_radio.sendWithRetry(to, data, len, 0, 0);
return true;
}
return _radio.sendWithRetry(to, data, len);
}
bool transportAvailable(void)
{
return _radio.receiveDone();
}
bool transportSanityCheck(void)
{
return _radio.sanityCheck();
}
uint8_t transportReceive(void* data)
{
// save payload length
const uint8_t dataLen = min(MAX_MESSAGE_LENGTH,_radio.DATALEN);
(void)memcpy((void*)data, (void*)_radio.DATA, dataLen);
// Send ack back if this message wasn't a broadcast
if (_radio.ACKRequested()) {
_radio.sendACK();
}
return dataLen;
}
void transportSleep(void)
{
_radio.sleep();
}
void transportStandBy(void)
{
_radio.standBy();
}
void transportPowerDown(void)
{
_radio.powerDown();
}
void transportPowerUp(void)
{
_radio.powerUp();
}
int16_t transportGetSendingRSSI(void)
{
return INVALID_RSSI;
}
int16_t transportGetReceivingRSSI(void)
{
return _radio.RSSI;
}
int16_t transportGetSendingSNR(void)
{
return INVALID_SNR;
}
int16_t transportGetReceivingSNR(void)
{
return INVALID_SNR;
}
int16_t transportGetTxPowerPercent(void)
{
return 0;
}
int16_t transportGetTxPowerLevel(void)
{
return 0;
}
#endif
+131
View File
@@ -0,0 +1,131 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2017 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
*/
#include "MyTransportHAL.h"
#include "drivers/RFM95/RFM95.h"
bool transportInit(void)
{
const bool result = RFM95_initialise(MY_RFM95_FREQUENCY);
#if defined(MY_RFM95_TCXO)
RFM95_enableTCXO();
#endif
#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_RFM95_ATC_MODE_DISABLED)
// only enable ATC mode in nodes
RFM95_ATCmode(true, MY_RFM95_ATC_TARGET_RSSI);
#endif
return result;
}
void transportSetAddress(const uint8_t address)
{
RFM95_setAddress(address);
}
uint8_t transportGetAddress(void)
{
return RFM95_getAddress();
}
bool transportSend(const uint8_t to, const void* data, const uint8_t len, const bool noACK)
{
if (noACK) {
(void)RFM95_sendWithRetry(to, data, len, 0, 0);
return true;
}
return RFM95_sendWithRetry(to, data, len);
}
bool transportAvailable(void)
{
return RFM95_available();
}
bool transportSanityCheck(void)
{
return RFM95_sanityCheck();
}
uint8_t transportReceive(void* data)
{
const uint8_t len = RFM95_recv((uint8_t*)data, MAX_MESSAGE_LENGTH);
return len;
}
void transportSleep(void)
{
(void)RFM95_sleep();
}
void transportStandBy(void)
{
(void)RFM95_standBy();
}
void transportPowerDown(void)
{
RFM95_powerDown();
}
void transportPowerUp(void)
{
RFM95_powerUp();
}
void transportToggleATCmode(const bool OnOff, const int16_t targetRSSI)
{
RFM95_ATCmode(OnOff, targetRSSI);
}
int16_t transportGetSendingRSSI(void)
{
return RFM95_getSendingRSSI();
}
int16_t transportGetReceivingRSSI(void)
{
return RFM95_getReceivingRSSI();
}
int16_t transportGetSendingSNR(void)
{
return static_cast<int16_t>(RFM95_getSendingSNR());
}
int16_t transportGetReceivingSNR(void)
{
return static_cast<int16_t>(RFM95_getReceivingSNR());
}
int16_t transportGetTxPowerPercent(void)
{
return static_cast<int16_t>(RFM95_getTxPowerPercent());
}
int16_t transportGetTxPowerLevel(void)
{
return static_cast<int16_t>(RFM95_getTxPowerLevel());
}
bool transportSetTxPowerPercent(const uint8_t powerPercent)
{
return RFM95_setTxPowerPercent(powerPercent);
}
+422
View File
@@ -0,0 +1,422 @@
/*
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2017 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* -------------------------------------------------------------------------------
*
* Copyright (c) 2013, Majenko Technologies and S.J.Hoeksma
* Copyright (c) 2015, LeoDesigner
* https://github.com/leodesigner/mysensors-serial-transport
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of Majenko Technologies.
********************************************************************************/
// transport(Serial,0,-1); // serial port, node, dePin (-1 disabled)
// Serial Transport
#include "MyTransportHAL.h"
#ifdef __linux__
#include "SerialPort.h"
#endif
#if defined(MY_RS485_DE_PIN)
#define assertDE() hwDigitalWrite(MY_RS485_DE_PIN, HIGH); delayMicroseconds(5)
#define deassertDE() hwDigitalWrite(MY_RS485_DE_PIN, LOW)
#else
#define assertDE()
#define deassertDE()
#endif
// We only use SYS_PACK in this application
#define ICSC_SYS_PACK 0x58
// 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;
#if defined(__linux__)
SerialPort _dev = SerialPort(MY_RS485_HWSERIAL);
#elif defined(MY_RS485_HWSERIAL)
HardwareSerial& _dev = MY_RS485_HWSERIAL;
#else
AltSoftSerial _dev;
#endif
unsigned char _nodeId;
char _data[MY_RS485_MAX_MESSAGE_LENGTH];
uint8_t _packet_len;
unsigned char _packet_from;
bool _packet_received;
// Packet wrapping characters, defined in standard ASCII table
#define SOH 1
#define STX 2
#define ETX 3
#define EOT 4
//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()
{
char inch;
unsigned char i;
if (!_dev.available()) {
return false;
}
while(_dev.available()) {
inch = _dev.read();
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] = inch;
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;
//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 == _nodeId) ||
(_recStation != _nodeId &&
_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++] = inch;
_recCalcCS += inch;
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 (inch == 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 = inch;
_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 (inch == 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 = true;
break;
}
}
}
//Clear the data
_serialReset();
//Return true, we have processed one command
return true;
break;
}
}
return true;
}
bool transportSend(const uint8_t to, const void* data, const uint8_t len, const bool noACK)
{
(void)noACK; // not implemented
const char *datap = static_cast<char const *>(data);
unsigned char i;
unsigned char cs = 0;
unsigned char del;
// 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()) {
del = rand() % 20;
for (i = 0; i < del; i++) {
delay(1);
_serialProcess();
}
timeout--;
if (timeout == 0) {
// Failed to transmit!!!
return false;
}
}
#if defined(MY_RS485_DE_PIN)
hwDigitalWrite(MY_RS485_DE_PIN, HIGH);
delayMicroseconds(5);
#endif
// Start of header by writing multiple SOH
for(byte w=0; w<1; w++) {
_dev.write(SOH);
}
_dev.write(to); // Destination address
cs += to;
_dev.write(_nodeId); // Source address
cs += _nodeId;
_dev.write(ICSC_SYS_PACK); // Command code
cs += ICSC_SYS_PACK;
_dev.write(len); // Length of text
cs += len;
_dev.write(STX); // Start of text
for(i=0; i<len; i++) {
_dev.write(datap[i]); // Text bytes
cs += datap[i];
}
_dev.write(ETX); // End of text
_dev.write(cs);
_dev.write(EOT);
#if defined(MY_RS485_DE_PIN)
#ifdef __PIC32MX__
// MPIDE has nothing yet for this. It uses the hardware buffer, which
// could be up to 8 levels deep. For now, let's just delay for 8
// characters worth.
delayMicroseconds((F_CPU/9600)+1);
#else
#if defined(ARDUINO) && ARDUINO >= 100
#if ARDUINO >= 104
// Arduino 1.0.4 and upwards does it right
_dev.flush();
#else
// Between 1.0.0 and 1.0.3 it almost does it - need to compensate
// for the hardware buffer. Delay for 2 bytes worth of transmission.
_dev.flush();
delayMicroseconds((20000000UL/9600)+1);
#endif
#elif defined(__linux__)
_dev.flush();
#endif
#endif
hwDigitalWrite(MY_RS485_DE_PIN, LOW);
#endif
return true;
}
bool transportInit(void)
{
// Reset the state machine
_dev.begin(MY_RS485_BAUD_RATE);
_serialReset();
#if defined(MY_RS485_DE_PIN)
hwPinMode(MY_RS485_DE_PIN, OUTPUT);
hwDigitalWrite(MY_RS485_DE_PIN, LOW);
#endif
return true;
}
void transportSetAddress(const uint8_t address)
{
_nodeId = address;
}
uint8_t transportGetAddress(void)
{
return _nodeId;
}
bool transportAvailable(void)
{
_serialProcess();
return _packet_received;
}
bool transportSanityCheck(void)
{
// not implemented yet
return true;
}
uint8_t transportReceive(void* data)
{
if (_packet_received) {
memcpy(data,_data,_packet_len);
_packet_received = false;
return _packet_len;
} else {
return (0);
}
}
void transportPowerDown(void)
{
// Nothing to shut down here
}
void transportPowerUp(void)
{
}
void transportSleep(void)
{
}
void transportStandBy(void)
{
}
int16_t transportGetSendingRSSI(void)
{
// not implemented
return static_cast<int16_t>(INVALID_RSSI);
}
int16_t transportGetReceivingRSSI(void)
{
// not implemented
return static_cast<int16_t>(INVALID_RSSI);
}
int16_t transportGetSendingSNR(void)
{
// not implemented
return static_cast<int16_t>(INVALID_SNR);
}
int16_t transportGetReceivingSNR(void)
{
// not implemented
return static_cast<int16_t>(INVALID_SNR);
}
int16_t transportGetTxPowerPercent(void)
{
// not implemented
return static_cast<int16_t>(100);
}
int16_t transportGetTxPowerLevel(void)
{
// not implemented
return static_cast<int16_t>(100);
}
bool transportSetTxPowerPercent(const uint8_t powerPercent)
{
// not possbile
(void)powerPercent;
return false;
}