If MY_GATEWAY_CLIENT_MODE or MY_CONTROLLER_URL_ADDRESS are present, set MY_GATEWAY_CLIENT_MODE. MY_USE_UDP requires that MY_GATEWAY_CLIENT_MODE is set. Add some ifdefs to filter some variables not needed in client mode. Using global variables to keep the connection to controller open therefore, no need to reconnect every time we need to send/receive something. Add the remaining code to handle incoming messages from controllers.
450 lines
13 KiB
C++
450 lines
13 KiB
C++
/*
|
|
* 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 Tomas Hozza <thozza@gmail.com>
|
|
* Copyright (C) 2015 Tomas Hozza
|
|
* 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 "MyGatewayTransport.h"
|
|
|
|
// global variables
|
|
extern MyMessage _msgTmp;
|
|
|
|
|
|
#if defined(MY_CONTROLLER_IP_ADDRESS)
|
|
IPAddress _ethernetControllerIP(MY_CONTROLLER_IP_ADDRESS);
|
|
#endif
|
|
|
|
#if defined(MY_IP_ADDRESS)
|
|
IPAddress _ethernetGatewayIP(MY_IP_ADDRESS);
|
|
#endif
|
|
byte _ethernetGatewayMAC[] = { MY_MAC_ADDRESS };
|
|
uint16_t _ethernetGatewayPort = MY_PORT;
|
|
MyMessage _ethernetMsg;
|
|
|
|
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
|
|
|
|
typedef struct
|
|
{
|
|
char string[MY_GATEWAY_MAX_RECEIVE_LENGTH];
|
|
uint8_t idx;
|
|
} inputBuffer;
|
|
|
|
#if defined(MY_GATEWAY_ESP8266)
|
|
// Some re-defines to make code more readable below
|
|
#define EthernetServer WiFiServer
|
|
#define EthernetClient WiFiClient
|
|
#define EthernetUDP WiFiUDP
|
|
|
|
#if defined(MY_IP_ADDRESS)
|
|
IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS);
|
|
IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS);
|
|
#endif
|
|
#if !defined(MY_GATEWAY_CLIENT_MODE)
|
|
static bool clientsConnected[MY_GATEWAY_MAX_CLIENTS];
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(MY_USE_UDP)
|
|
EthernetUDP _ethernetServer;
|
|
#elif !defined(MY_GATEWAY_CLIENT_MODE)
|
|
EthernetServer _ethernetServer(_ethernetGatewayPort);
|
|
#endif
|
|
|
|
|
|
#if defined(MY_GATEWAY_ESP8266) && !defined(MY_GATEWAY_CLIENT_MODE)
|
|
static EthernetClient clients[MY_GATEWAY_MAX_CLIENTS];
|
|
static inputBuffer inputString[MY_GATEWAY_MAX_CLIENTS];
|
|
#else
|
|
static EthernetClient client = EthernetClient();
|
|
static inputBuffer inputString;
|
|
#endif
|
|
|
|
|
|
#ifndef MY_IP_ADDRESS
|
|
void gatewayTransportRenewIP();
|
|
#endif
|
|
|
|
// On W5100 boards with SPI_EN exposed we can use the real SPI bus together with radio
|
|
// (if we enable it during usage)
|
|
#ifdef MY_W5100_SPI_EN
|
|
void _w5100_spi_en(bool enable)
|
|
{
|
|
if (enable)
|
|
{
|
|
// Pull up pin
|
|
pinMode(MY_W5100_SPI_EN, INPUT);
|
|
digitalWrite(MY_W5100_SPI_EN, HIGH);
|
|
}
|
|
else
|
|
{
|
|
// Ground pin
|
|
pinMode(MY_W5100_SPI_EN, OUTPUT);
|
|
digitalWrite(MY_W5100_SPI_EN, LOW);
|
|
}
|
|
}
|
|
#else
|
|
#define _w5100_spi_en(x)
|
|
#endif
|
|
|
|
|
|
|
|
bool gatewayTransportInit() {
|
|
_w5100_spi_en(true);
|
|
#if defined(MY_GATEWAY_ESP8266)
|
|
#if defined(MY_ESP8266_SSID)
|
|
// Turn off access point
|
|
WiFi.mode (WIFI_STA);
|
|
#if defined(MY_ESP8266_HOSTNAME)
|
|
WiFi.hostname(MY_ESP8266_HOSTNAME);
|
|
#endif
|
|
#ifdef MY_IP_ADDRESS
|
|
WiFi.config(_ethernetGatewayIP, _gatewayIp, _subnetIp);
|
|
#endif
|
|
(void)WiFi.begin(MY_ESP8266_SSID, MY_ESP8266_PASSWORD);
|
|
while (WiFi.status() != WL_CONNECTED)
|
|
{
|
|
delay(500);
|
|
MY_SERIALDEVICE.print(F("."));
|
|
yield();
|
|
}
|
|
MY_SERIALDEVICE.print(F("IP: "));
|
|
MY_SERIALDEVICE.println(WiFi.localIP());
|
|
#endif
|
|
|
|
#else
|
|
#ifdef MY_IP_ADDRESS
|
|
Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP);
|
|
#else
|
|
// Get IP address from DHCP
|
|
if (!Ethernet.begin(_ethernetGatewayMAC)) {
|
|
MY_SERIALDEVICE.print(F("DHCP FAILURE..."));
|
|
_w5100_spi_en(false);
|
|
return false;
|
|
}
|
|
#endif
|
|
MY_SERIALDEVICE.print(F("IP: "));
|
|
MY_SERIALDEVICE.println(Ethernet.localIP());
|
|
// give the Ethernet interface a second to initialize
|
|
delay(1000);
|
|
#endif /* MY_GATEWAY_ESP8266 */
|
|
|
|
#ifdef MY_USE_UDP
|
|
_ethernetServer.begin(_ethernetGatewayPort);
|
|
#elif defined(MY_GATEWAY_CLIENT_MODE)
|
|
#if defined(MY_CONTROLLER_URL_ADDRESS)
|
|
if (!client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
|
|
#else
|
|
if (!client.connect(_ethernetControllerIP, MY_PORT)) {
|
|
#endif
|
|
client.stop();
|
|
debug(PSTR("Eth: Failed to connect\n"));
|
|
} else {
|
|
debug(PSTR("Eth: connect\n"));
|
|
_w5100_spi_en(false);
|
|
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
|
|
_w5100_spi_en(true);
|
|
presentNode();
|
|
}
|
|
#else
|
|
// we have to use pointers due to the constructor of EthernetServer
|
|
_ethernetServer.begin();
|
|
#endif
|
|
_w5100_spi_en(false);
|
|
return true;
|
|
}
|
|
|
|
bool gatewayTransportSend(MyMessage &message)
|
|
{
|
|
bool ret = true;
|
|
char *_ethernetMsg = protocolFormat(message);
|
|
|
|
setIndication(INDICATION_GW_TX);
|
|
|
|
_w5100_spi_en(true);
|
|
#if defined(MY_GATEWAY_CLIENT_MODE)
|
|
#if defined(MY_USE_UDP)
|
|
#if defined(MY_CONTROLLER_URL_ADDRESS)
|
|
_ethernetServer.beginPacket(MY_CONTROLLER_URL_ADDRESS, MY_PORT);
|
|
#else
|
|
_ethernetServer.beginPacket(_ethernetControllerIP, MY_PORT);
|
|
#endif
|
|
_ethernetServer.write(_ethernetMsg, strlen(_ethernetMsg));
|
|
// returns 1 if the packet was sent successfully
|
|
ret = _ethernetServer.endPacket();
|
|
#else
|
|
if (!client.connected()) {
|
|
client.stop();
|
|
#if defined(MY_CONTROLLER_URL_ADDRESS)
|
|
if (!client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
|
|
#else
|
|
if (!client.connect(_ethernetControllerIP, MY_PORT)) {
|
|
#endif
|
|
// connecting to the server failed!
|
|
debug(PSTR("Eth: Failed to connect\n"));
|
|
ret = false;
|
|
} else {
|
|
debug(PSTR("Eth: connect\n"));
|
|
_w5100_spi_en(false);
|
|
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
|
|
_w5100_spi_en(true);
|
|
presentNode();
|
|
}
|
|
} else {
|
|
ret = client.write(_ethernetMsg, strlen(_ethernetMsg)) > 0;
|
|
}
|
|
#endif
|
|
#else
|
|
// Send message to connected clients
|
|
#if defined(MY_GATEWAY_ESP8266)
|
|
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++)
|
|
{
|
|
if (clients[i] && clients[i].connected())
|
|
{
|
|
clients[i].write((uint8_t*)_ethernetMsg, strlen(_ethernetMsg));
|
|
}
|
|
}
|
|
#else
|
|
ret = _ethernetServer.write(_ethernetMsg) > 0;
|
|
#endif
|
|
#endif /* MY_GATEWAY_CLIENT_MODE */
|
|
_w5100_spi_en(false);
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
#if defined(MY_GATEWAY_ESP8266) && !defined(MY_GATEWAY_CLIENT_MODE)
|
|
bool _readFromClient(uint8_t i) {
|
|
while (clients[i].connected() && clients[i].available()) {
|
|
char inChar = clients[i].read();
|
|
if (inputString[i].idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
|
|
// if newline then command is complete
|
|
if (inChar == '\n' || inChar == '\r') {
|
|
// Add string terminator and prepare for the next message
|
|
inputString[i].string[inputString[i].idx] = 0;
|
|
debug(PSTR("Client %d: %s\n"), i, inputString[i].string);
|
|
inputString[i].idx = 0;
|
|
if (protocolParse(_ethernetMsg, inputString[i].string)) {
|
|
return true;
|
|
}
|
|
|
|
} else {
|
|
// add it to the inputString:
|
|
inputString[i].string[inputString[i].idx++] = inChar;
|
|
}
|
|
} else {
|
|
// Incoming message too long. Throw away
|
|
debug(PSTR("Client %d: Message too long\n"), i);
|
|
inputString[i].idx = 0;
|
|
// Finished with this client's message. Next loop() we'll see if there's more to read.
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#else
|
|
bool _readFromClient() {
|
|
while (client.connected() && client.available()) {
|
|
char inChar = client.read();
|
|
if (inputString.idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) {
|
|
// if newline then command is complete
|
|
if (inChar == '\n' || inChar == '\r') {
|
|
// Add string terminator and prepare for the next message
|
|
inputString.string[inputString.idx] = 0;
|
|
debug(PSTR("Eth: %s\n"), inputString.string);
|
|
inputString.idx = 0;
|
|
if (protocolParse(_ethernetMsg, inputString.string)) {
|
|
return true;
|
|
}
|
|
|
|
} else {
|
|
// add it to the inputString:
|
|
inputString.string[inputString.idx++] = inChar;
|
|
}
|
|
} else {
|
|
// Incoming message too long. Throw away
|
|
debug(PSTR("Eth: Message too long\n"));
|
|
inputString.idx = 0;
|
|
// Finished with this client's message. Next loop() we'll see if there's more to read.
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
|
|
bool gatewayTransportAvailable()
|
|
{
|
|
_w5100_spi_en(true);
|
|
#if !defined(MY_IP_ADDRESS) && defined(MY_GATEWAY_W5100)
|
|
// renew IP address using DHCP
|
|
gatewayTransportRenewIP();
|
|
#endif
|
|
|
|
#ifdef MY_USE_UDP
|
|
|
|
int packet_size = _ethernetServer.parsePacket();
|
|
|
|
if (packet_size) {
|
|
//debug(PSTR("UDP packet available. Size:%d\n"), packet_size);
|
|
#if defined(MY_GATEWAY_ESP8266)
|
|
_ethernetServer.read(inputString[0].string, MY_GATEWAY_MAX_RECEIVE_LENGTH);
|
|
inputString[0].string[packet_size] = 0;
|
|
debug(PSTR("UDP packet received: %s\n"), inputString[0].string);
|
|
const bool ok = protocolParse(_ethernetMsg, inputString[0].string);
|
|
#else
|
|
_ethernetServer.read(inputString.string, MY_GATEWAY_MAX_RECEIVE_LENGTH);
|
|
inputString.string[packet_size] = 0;
|
|
debug(PSTR("UDP packet received: %s\n"), inputString.string);
|
|
_w5100_spi_en(false);
|
|
const bool ok = protocolParse(_ethernetMsg, inputString.string);
|
|
#endif
|
|
if (ok)
|
|
{
|
|
setIndication(INDICATION_GW_RX);
|
|
}
|
|
return ok;
|
|
}
|
|
#elif defined(MY_GATEWAY_CLIENT_MODE)
|
|
if (!client.connected()) {
|
|
client.stop();
|
|
#if defined(MY_CONTROLLER_URL_ADDRESS)
|
|
if (!client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) {
|
|
#else
|
|
if (!client.connect(_ethernetControllerIP, MY_PORT)) {
|
|
#endif
|
|
debug(PSTR("Eth: Failed to connect\n"));
|
|
_w5100_spi_en(false);
|
|
return false;
|
|
} else {
|
|
debug(PSTR("Eth: connect\n"));
|
|
_w5100_spi_en(false);
|
|
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
|
|
_w5100_spi_en(true);
|
|
presentNode();
|
|
}
|
|
}
|
|
if (_readFromClient()) {
|
|
setIndication(INDICATION_GW_RX);
|
|
_w5100_spi_en(false);
|
|
return true;
|
|
}
|
|
#else
|
|
#if defined(MY_GATEWAY_ESP8266)
|
|
// ESP8266: Go over list of clients and stop any that are no longer connected.
|
|
// If the server has a new client connection it will be assigned to a free slot.
|
|
bool allSlotsOccupied = true;
|
|
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) {
|
|
if (!clients[i].connected()) {
|
|
if (clientsConnected[i]) {
|
|
debug(PSTR("Client %d disconnected\n"), i);
|
|
clients[i].stop();
|
|
}
|
|
//check if there are any new clients
|
|
if (_ethernetServer.hasClient()) {
|
|
clients[i] = _ethernetServer.available();
|
|
inputString[i].idx = 0;
|
|
debug(PSTR("Client %d connected\n"), i);
|
|
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
|
|
// Send presentation of locally attached sensors (and node if applicable)
|
|
presentNode();
|
|
}
|
|
}
|
|
bool connected = clients[i].connected();
|
|
clientsConnected[i] = connected;
|
|
allSlotsOccupied &= connected;
|
|
}
|
|
if (allSlotsOccupied && _ethernetServer.hasClient()) {
|
|
//no free/disconnected spot so reject
|
|
debug(PSTR("No free slot available\n"));
|
|
EthernetClient c = _ethernetServer.available();
|
|
c.stop();
|
|
}
|
|
// Loop over clients connect and read available data
|
|
for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) {
|
|
if (_readFromClient(i)) {
|
|
setIndication(INDICATION_GW_RX);
|
|
_w5100_spi_en(false);
|
|
return true;
|
|
}
|
|
}
|
|
#else
|
|
// W5100/ENC module does not have hasClient-method. We can only serve one client at the time.
|
|
EthernetClient newclient = _ethernetServer.available();
|
|
// if a new client connects make sure to dispose any previous existing sockets
|
|
if (newclient) {
|
|
if (client != newclient) {
|
|
client.stop();
|
|
client = newclient;
|
|
debug(PSTR("Eth: connect\n"));
|
|
_w5100_spi_en(false);
|
|
gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete."));
|
|
_w5100_spi_en(true);
|
|
presentNode();
|
|
}
|
|
}
|
|
if (client) {
|
|
if (!client.connected()) {
|
|
debug(PSTR("Eth: disconnect\n"));
|
|
client.stop();
|
|
} else {
|
|
if (_readFromClient()) {
|
|
setIndication(INDICATION_GW_RX);
|
|
_w5100_spi_en(false);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
#endif /* MY_GATEWAY_ESP8266 */
|
|
#endif
|
|
_w5100_spi_en(false);
|
|
return false;
|
|
}
|
|
|
|
MyMessage& gatewayTransportReceive()
|
|
{
|
|
// Return the last parsed message
|
|
return _ethernetMsg;
|
|
}
|
|
|
|
|
|
#if !defined(MY_IP_ADDRESS) && !defined(MY_GATEWAY_ESP8266)
|
|
void gatewayTransportRenewIP()
|
|
{
|
|
/* renew/rebind IP address
|
|
0 - nothing happened
|
|
1 - renew failed
|
|
2 - renew success
|
|
3 - rebinf failed
|
|
4 - rebind success
|
|
*/
|
|
static unsigned long next_time = hwMillis() + MY_IP_RENEWAL_INTERVAL;
|
|
unsigned long now = hwMillis();
|
|
|
|
// http://playground.arduino.cc/Code/TimingRollover
|
|
if ((long)(now - next_time) < 0)
|
|
return;
|
|
if (Ethernet.maintain() & ~(0x06)) {
|
|
debug(PSTR("IP was not renewed correctly\n"));
|
|
/* Error occured -> IP was not renewed */
|
|
return;
|
|
}
|
|
_w5100_spi_en(false);
|
|
next_time = now + MY_IP_RENEWAL_INTERVAL;
|
|
}
|
|
#endif
|