1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-11-30 03:26:13 +01:00
CommandStation-EX/WifiInterface.cpp
Fred 4861e592c7
Nano every2 (#129)
* Start adding back unowifi stuffz

* Uno Wifi compiling

* Fixes for compile arduino unowifi r2

* FlasString and Timers for Uno Wifi

ALL these changes should be portable back to master

* Remove extra timer that was already added

* Changed to EveryTimerB

* Add everytimerb.h

* Cleanup

* Linear address <a> cmd

* Allow lower case keywords

* Add the F define to be on safe side if it is not present in the library core code

* Clean simple Timer interface

Removes overkill files, puts all timer in a single small file. (DCCTimer)

* Timer port

* Timer working

And slow wave command removed

* Correcting non-portables merged from master

* Wave-state machine ( part 11)

* Microtuning waveform

Significant reduction in code paths and call overheads

* Current check cleanup

* Fix no-loco id

Has to handle -1 correctly

* fix wrong format letter

* redo flow through wifisetup again

* version++

* bugfixes wifi setup

* Retry harder for AP mode

* Remove unued if

* DIO2 replacement

Currently for writing signal pins during waveform.

* Drop analogReadFast (see DCCTimer)

AnalogRead speed set in DCCTimer for ease of porting.
Code tidy and diagnostics in MotorDriver

* UNTESTED fast power,brake,fault pins

* Distunguish between in/out of FASTPIN

* minor performance tweaks

* Config comments and example use

* Config/example loading

* IP/PORT on LCD

* Ethernet simulated mac

Plus fixed listening port

* Github SHA

* Committing a SHA

* Fix for nano compile

* Comments and a reliability fix.

* UnoRev2 protection

* PWM pin implementation

* fix wifi setup issue

* Reinstate IP_PORT

* Wifi channel and code cleaninga

* Reduce duplicated F() macros

Compiler isn't as clever as one might expect

* Committing a SHA

* Update config.example.h

Add comment to wifi channel section

* Committing a SHA

* Handle shields with common fault pins (Pololu)

* Committing a SHA

* remove warning

* Committing a SHA

* only do the sha generation on master

* yaml syntax

* Fast SSD1306 OLED driver

Incorporate code from SSD1306Ascii library to speed up OLED screen updates, reduce memory requirements and eliminate some library dependences.

* Fix auto-configure from cold boot.

Add call to Wire.begin().

* Update comment for OLED_DRIVER define.

* Update MotorDrivers.h

Add a motor board definition for using the IBT_2 board for a high current to the main track and keep the Arduino Motor Shield for operating the programming track.

* Committing a SHA

* Fix missing F  in motor drivers

* JOIN relay pin

* Swap Join Relay high/low

* Hide WIFI_CONNECT_TIMEOUT

This is not what the config suggests it is...  The timeout is in the ES and defaults to 15 seconds. Abandoning it early leads to confused setup.

* Enhance OLED/LCD speed

Write one character or position command per loop entry so as not to hold up the loop.  Add support for SH1106 OLED as 132x64 size option.

* Enhance OLED/LCD speed

* Delete comment about OLED on UNO.

* Trim unwanted code

* Handle display types correctly

* Update comments

* Speed up OLED writes

Add new flushDisplay() to end any in-progress I2C transaction.  Previously, an redundant command was sent that ended the in-progress transaction but also sent another complete, but unnecessary, transaction.

* Comments and copyright update

* Reduce RAM and flash requirement a few more bytes.

* Move statics into LCDDisplay class, and reduce RAM.

Some state variables were static in LCDDisplay.write().  Moved to class members.  Also, types of data items like row, column & character position changed to int8_t to save a few bytes of RAM.

* Type lcdCols and lcdRows to unsigned.

Since lcdCols is normally 128, it needs to be uint8_t, not int8_t.

* remove timeout from user config

* faultpin is common only if it exists ; make code prettier

* Rationalisation of SSD1306 driver

Merge SSD1306AsciiWire.cpp into SSD1306Ascii.cpp and rename SSD1306AsciiWire.h as SSD1306Ascii.h.
Merge allFonts.h into System5x7.h and rename as SSD1306font.h.
Move all SSD1306 files into root folder to facilitate compilation in Arduino IDE.

* Fix some font attributes as const.

* Remove unused initialisation sequences for tiny oled screens

* Add m_ to variables

* Bump up I2C speed

Speed was 100kHz (default).  Max for OLEDis 400kHz.

* Revert "Bump up I2C speed"

This reverts commit 1c1168f433.

* Bump up I2C speed

Speed was 100kHz (default). Max for OLEDis 400kHz.

* Drop duplicate DIAG

* ignore mySetup.h files

* Restore uno to default_envs

Restore uno (previously commented out) to default_envs.

* Update objdump.bat

Allows other editors as Notepad is very slow on large files

* Prog Track overload during cv read

* Faster LCD Driver

Extract LCD driver from library;
Trim unused functionality;
Reduce I2C communications to minimum;
Speed up I2C clock to 400kHz.

* Update config.example.h

Add IBT_2_WITH_ARDUINO to example config

* Update config.example.h

* Screen enhancements (#126)

* Add I2CManager to coordinate I2C shared parameters.

* Add use of I2CManager, and experimental scrolling strategies.

New scrolling capability by defining SCROLLMODE in Config.h to 0 (original), 1 (by page) or 2 (by line).  If not defined, defaults to 0.

* Scrolling updates

New scrolling capability by defining SCROLLMODE in Config.h to 0 (original), 1 (by page) or 2 (by line). If not defined, defaults to 0.
Reformat.

* Add I2CManager calls. Remove unnecessary delays.

* Add I2CManager calls, remove unnecessary I2C delays.

* SSD1306: Move methods from .h to .cpp and reformat.

* Fix compiler warning in LiquidCrystal_I2C

* Allow forcing of I2C clock speed.

New method forceClock allows the I2C speed to be overridden.  For example, if the I2C bus is long then the speed can be forced lower.  It can also be forced higher to gain performance if devices are capable.

* Make Config.h conditionally included.

Allow for non-existence of Config.h.

* Correct scrolling and allow longer messages

Correct the handling of scrolling in scrollmode 1 to avoid a blank page being displayed.  Also, allow MAX_MSG_SIZE to be optionally configured to override maximum message length on screens.

* compiler warning on uno

Co-authored-by: dexslab <dex35803@gmail.com>
Co-authored-by: Asbelos <asbelos@btinternet.com>
Co-authored-by: Harald Barth <haba@kth.se>
Co-authored-by: Neil McKechnie <neilmck999@gmail.com>
Co-authored-by: Neil McKechnie <75813993+Neil-McK@users.noreply.github.com>
2021-03-07 15:58:35 -05:00

374 lines
13 KiB
C++

/*
© 2020, Chris Harlow. All rights reserved.
© 2020, Harald Barth.
This file is part of CommandStation-EX
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture
#include "WifiInterface.h" /* config.h included there */
#include <avr/pgmspace.h>
#include "DIAG.h"
#include "StringFormatter.h"
#include "WifiInboundHandler.h"
const unsigned long LOOP_TIMEOUT = 2000;
bool WifiInterface::connected = false;
Stream * WifiInterface::wifiStream;
#ifndef WIFI_CONNECT_TIMEOUT
// Tested how long it takes to FAIL an unknown SSID on firmware 1.7.4.
// The ES should fail a connect in 15 seconds, we don't want to fail BEFORE that
// or ot will cause issues with the following commands.
#define WIFI_CONNECT_TIMEOUT 16000
#endif
////////////////////////////////////////////////////////////////////////////////
//
// Figure out number of serial ports depending on hardware
//
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
#define NUM_SERIAL 0
#endif
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560))
#define NUM_SERIAL 3
#endif
#ifndef NUM_SERIAL
#define NUM_SERIAL 1
#endif
bool WifiInterface::setup(long serial_link_speed,
const FSH *wifiESSID,
const FSH *wifiPassword,
const FSH *hostname,
const int port,
const byte channel) {
wifiSerialState wifiUp = WIFI_NOAT;
#if NUM_SERIAL == 0
// no warning about unused parameters.
(void) serial_link_speed;
(void) wifiESSID;
(void) wifiPassword;
(void) hostname;
(void) port;
(void) channel;
#endif
#if NUM_SERIAL > 0
Serial1.begin(serial_link_speed);
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
#endif
// Other serials are tried, depending on hardware.
#if NUM_SERIAL > 1
if (wifiUp == WIFI_NOAT)
{
Serial2.begin(serial_link_speed);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
#if NUM_SERIAL > 2
if (wifiUp == WIFI_NOAT)
{
Serial3.begin(serial_link_speed);
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
if (wifiUp == WIFI_NOAT) // here and still not AT commands found
return false;
DCCEXParser::setAtCommandCallback(ATCommand);
// CAUTION... ONLY CALL THIS ONCE
WifiInboundHandler::setup(wifiStream);
if (wifiUp == WIFI_CONNECTED)
connected = true;
else
connected = false;
return connected;
}
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
const FSH* hostname, int port, byte channel) {
wifiSerialState wifiState;
static uint8_t ntry = 0;
ntry++;
wifiStream = &setupStream;
DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry);
wifiState = setup2( SSid, password, hostname, port, channel);
if (wifiState == WIFI_NOAT) {
DIAG(F("\n++ Wifi Setup NO AT ++\n"));
return wifiState;
}
if (wifiState == WIFI_CONNECTED) {
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
checkForOK(200, true);
}
DIAG(F("\n++ Wifi Setup %S ++\n"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED"));
return wifiState;
}
#ifdef DONT_TOUCH_WIFI_CONF
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
const FSH* hostname, int port, byte channel) {
bool ipOK = false;
bool oldCmd = false;
char macAddress[17]; // mac address extraction
// First check... Restarting the Arduino does not restart the ES.
// There may alrerady be a connection with data in the pipeline.
// If there is, just shortcut the setup and continue to read the data as normal.
if (checkForOK(200,F("+IPD"), true)) {
DIAG(F("\nPreconfigured Wifi already running with data waiting\n"));
return WIFI_CONNECTED;
}
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
if(!checkForOK(200, true))
return WIFI_NOAT; // No AT compatible WiFi module here
StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening
checkForOK(2000, true); // Makes this visible on the console
// Display the AT version information
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
checkForOK(2000, true, false); // Makes this visible on the console
#ifdef DONT_TOUCH_WIFI_CONF
DIAG(F("\nDONT_TOUCH_WIFI_CONF was set: Using existing config\n"));
#else
StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client
checkForOK(1000, true); // Not always OK, sometimes "no change"
// Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME
StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n"));
if (checkForOK(2000, true)) {
oldCmd=true;
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
}
const char *yourNetwork = "Your network ";
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) {
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
// If the source code looks unconfigured, check if the
// ESP8266 is preconfigured in station mode.
// We check the first 13 chars of the SSid and the password
// give a preconfigured ES8266 a chance to connect to a router
// typical connect time approx 7 seconds
delay(8000);
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
if (checkForOK(5000, F("+CIFSR:STAIP"), true,false))
if (!checkForOK(1000, F("0.0.0.0"), true,false))
ipOK = true;
}
} else {
// SSID was configured, so we assume station (client) mode.
if (oldCmd) {
// AT command early version supports CWJAP/CWSAP
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
} else {
// later version supports CWJAP_CUR
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
checkForOK(2000, true); // dont care if not supported
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
}
if (ipOK) {
// But we really only have the ESSID and password correct
// Let's check for IP (via DHCP)
ipOK = false;
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
if (checkForOK(5000, F("+CIFSR:STAIP"), true,false))
if (!checkForOK(1000, F("0.0.0.0"), true,false))
ipOK = true;
}
}
if (!ipOK) {
// If we have not managed to get this going in station mode, go for AP mode
// StringFormatter::send(wifiStream, F("AT+RST\r\n"));
// checkForOK(1000, true); // Not always OK, sometimes "no change"
int i=0;
do {
// configure as AccessPoint. Try really hard as this is the
// last way out to get any Wifi connectivity.
StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n"));
} while (!checkForOK(1000+i*500, true) && i++<10);
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
// Figure out MAC addr
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // not TOMATO
// looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7"
if (checkForOK(5000, F("+CIFSR:APMAC,\""), true,false)) {
// Copy 17 byte mac address
for (int i=0; i<17;i++) {
while(!wifiStream->available());
macAddress[i]=wifiStream->read();
StringFormatter::printEscape(macAddress[i]);
}
} else {
memset(macAddress,'f',sizeof(macAddress));
}
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
checkForOK(1000, true, false); // suck up remainder of AT+CIFSR
i=0;
do {
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
// unconfigured
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
oldCmd ? "" : "_CUR", macTail, macTail, channel);
} else {
// password configured by user
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
macTail, password, channel);
}
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
if (i >= 2)
DIAG(F("\nWarning: Setting AP SSID and password failed\n")); // but issue warning
if (!oldCmd) {
StringFormatter::send(wifiStream, F("AT+CIPRECVMODE=0\r\n"), port); // make sure transfer mode is correct
checkForOK(2000, true);
}
}
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
checkForOK(1000, true); // ignore result in case it already was off
StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
#endif //DONT_TOUCH_WIFI_CONF
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
if (!checkForOK(1000, F("IP,\"") , true, false)) return WIFI_DISCONNECTED;
// Copy the IP address
{
const byte MAX_IP_LENGTH=15;
char ipString[MAX_IP_LENGTH+1];
ipString[MAX_IP_LENGTH]='\0'; // protection against missing " character on end.
for(byte ipLen=0;ipLen<MAX_IP_LENGTH;ipLen++) {
while(!wifiStream->available());
int ipChar=wifiStream->read();
StringFormatter::printEscape(ipChar);
if (ipChar=='"') {
ipString[ipLen]='\0';
break;
}
ipString[ipLen]=ipChar;
}
LCD(4,F("%s"),ipString); // There is not enough room on some LCDs to put a title to this
}
// suck up anything after the IP.
if (!checkForOK(1000, true, false)) return WIFI_DISCONNECTED;
LCD(5,F("PORT=%d\n"),port);
return WIFI_CONNECTED;
}
#ifdef DONT_TOUCH_WIFI_CONF
#pragma GCC diagnostic pop
#endif
// This function is used to allow users to enter <+ commands> through the DCCEXParser
// Once the user has made whatever changes to the AT commands, a <+X> command can be used
// to force on the connectd flag so that the loop will start picking up wifi traffic.
// If the settings are corrupted <+RST> will clear this and then you must restart the arduino.
void WifiInterface::ATCommand(const byte * command) {
command++;
if (*command=='X') {
connected = true;
DIAG(F("\n++++++ Wifi Connction forced on ++++++++\n"));
}
else {
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
checkForOK(10000, true);
}
}
bool WifiInterface::checkForOK( const unsigned int timeout, bool echo, bool escapeEcho) {
return checkForOK(timeout,F("\r\nOK\r\n"),echo,escapeEcho);
}
bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor, bool echo, bool escapeEcho) {
unsigned long startTime = millis();
char *locator = (char *)waitfor;
DIAG(F("\nWifi Check: [%E]"), waitfor);
while ( millis() - startTime < timeout) {
while (wifiStream->available()) {
int ch = wifiStream->read();
if (echo) {
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
else DIAG(F("%c"), ch);
}
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
if (ch == GETFLASH(locator)) {
locator++;
if (!GETFLASH(locator)) {
DIAG(F("\nFound in %dms"), millis() - startTime);
return true;
}
}
}
}
DIAG(F("\nTIMEOUT after %dms\n"), timeout);
return false;
}
void WifiInterface::loop() {
if (connected) {
WifiInboundHandler::loop();
}
}
#endif