You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

301 lines
9.4 KiB

//-----------------------------------------------------------------------------
// 2024 Ahoy, https://ahoydtu.de
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#ifndef __AHOY_NETWORK_H__
#define __AHOY_NETWORK_H__
#include "AhoyNetworkHelper.h"
#include "../config/settings.h"
#include "../utils/helper.h"
#include "AhoyWifiAp.h"
#include "AsyncJson.h"
#include <lwip/dns.h>
#define NTP_PACKET_SIZE 48
class AhoyNetwork {
public:
typedef std::function<void(bool)> OnNetworkCB;
typedef std::function<void(uint32_t utcTimestamp)> OnTimeCB;
public:
void setup(settings_t *config, OnNetworkCB onNetworkCB, OnTimeCB onTimeCB) {
mConfig = config;
mOnNetworkCB = onNetworkCB;
mOnTimeCB = onTimeCB;
mNtpIp = IPADDR_NONE;
if('\0' == mConfig->sys.deviceName[0])
snprintf(mConfig->sys.deviceName, DEVNAME_LEN, "%s", DEF_DEVICE_NAME);
mAp.setup(&mConfig->sys);
#if defined(ESP32)
WiFi.onEvent([this](WiFiEvent_t event, arduino_event_info_t info) -> void {
OnEvent(event);
});
#else
wifiConnectHandler = WiFi.onStationModeConnected(
[this](const WiFiEventStationModeConnected& event) -> void {
OnEvent((WiFiEvent_t)SYSTEM_EVENT_STA_CONNECTED);
});
wifiGotIPHandler = WiFi.onStationModeGotIP(
[this](const WiFiEventStationModeGotIP& event) -> void {
OnEvent((WiFiEvent_t)SYSTEM_EVENT_STA_GOT_IP);
});
wifiDisconnectHandler = WiFi.onStationModeDisconnected(
[this](const WiFiEventStationModeDisconnected& event) -> void {
OnEvent((WiFiEvent_t)SYSTEM_EVENT_STA_DISCONNECTED);
});
#endif
}
virtual void tickNetworkLoop() {
if(mDnsCallbackReady) {
mDnsCallbackReady = false;
startNtpUpdate();
}
if(mNtpTimeoutSec) {
mNtpTimeoutSec--;
if(!mNtpTimeoutSec)
mOnTimeCB(0); // timeout
}
}
bool isConnected() const {
return ((mStatus == NetworkState::CONNECTED) || (mStatus == NetworkState::GOT_IP));
}
static void dnsCallback(const char *name, const ip_addr_t *ipaddr, void *pClass) {
AhoyNetwork *obj = static_cast<AhoyNetwork*>(pClass);
if (ipaddr) {
#if defined(ESP32)
obj->mNtpIp = ipaddr->u_addr.ip4.addr;
#else
obj->mNtpIp = ipaddr->addr;
#endif
}
obj->mDnsCallbackReady = true;
}
void updateNtpTime() {
if(mNtpIp != IPADDR_NONE) {
startNtpUpdate();
return;
}
mNtpTimeoutSec = 30;
ip_addr_t ipaddr;
mNtpIp = WiFi.gatewayIP();
// dns_gethostbyname runs asynchronous and sets the member mNtpIp which is then checked on
// next call of updateNtpTime
err_t err = dns_gethostbyname(mConfig->ntp.addr, &ipaddr, dnsCallback, this);
if (err == ERR_OK) {
#if defined(ESP32)
mNtpIp = ipaddr.u_addr.ip4.addr;
#else
mNtpIp = ipaddr.addr;
#endif
startNtpUpdate();
}
}
protected:
void startNtpUpdate() {
DPRINTLN(DBG_INFO, F("get time from: ") + mNtpIp.toString());
if (!mUdp.connected()) {
if (!mUdp.connect(mNtpIp, mConfig->ntp.port)) {
mOnTimeCB(0);
return;
}
}
mUdp.onPacket([this](AsyncUDPPacket packet) {
this->handleNTPPacket(packet);
});
sendNTPpacket();
// reset to start with DNS lookup next time again
mNtpIp = IPADDR_NONE;
}
public:
virtual void begin() = 0;
virtual String getIp(void) = 0;
virtual String getMac(void) = 0;
virtual bool getWasInCh12to14() {
return false;
}
virtual bool isWiredConnection() {
return false;
}
bool isApActive() {
return mAp.isEnabled();
}
bool getAvailNetworks(JsonObject obj, IApp *app) {
if(!mScanActive) {
app->addOnce([this]() {scan();}, 1, "scan");
return false;
}
int n = WiFi.scanComplete();
if (WIFI_SCAN_RUNNING == n)
return false;
if(n > 0) {
JsonArray nets = obj.createNestedArray(F("networks"));
int sort[n];
sortRSSI(&sort[0], n);
for (int i = 0; i < n; ++i) {
nets[i][F("ssid")] = WiFi.SSID(sort[i]);
nets[i][F("rssi")] = WiFi.RSSI(sort[i]);
}
}
mScanActive = false;
WiFi.scanDelete();
return true;
}
void scan(void) {
mScanActive = true;
if(mWifiConnecting) {
mWifiConnecting = false;
WiFi.disconnect();
}
WiFi.scanNetworks(true, true);
}
protected:
virtual void setStaticIp() = 0;
void setupIp(std::function<bool(IPAddress ip, IPAddress gateway, IPAddress mask, IPAddress dns1, IPAddress dns2)> cb) {
if(mConfig->sys.ip.ip[0] != 0) {
IPAddress ip(mConfig->sys.ip.ip);
IPAddress mask(mConfig->sys.ip.mask);
IPAddress dns1(mConfig->sys.ip.dns1);
IPAddress dns2(mConfig->sys.ip.dns2);
IPAddress gateway(mConfig->sys.ip.gateway);
if(cb(ip, gateway, mask, dns1, dns2))
DPRINTLN(DBG_ERROR, F("failed to set static IP!"));
}
}
virtual void OnEvent(WiFiEvent_t event) {
switch(event) {
case SYSTEM_EVENT_STA_CONNECTED:
[[fallthrough]];
case ARDUINO_EVENT_ETH_CONNECTED:
if(NetworkState::CONNECTED != mStatus) {
mStatus = NetworkState::CONNECTED;
DPRINTLN(DBG_INFO, F("Network connected"));
}
break;
case SYSTEM_EVENT_STA_GOT_IP:
[[fallthrough]];
case ARDUINO_EVENT_ETH_GOT_IP:
mStatus = NetworkState::GOT_IP;
break;
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
[[fallthrough]];
case ARDUINO_EVENT_WIFI_STA_STOP:
[[fallthrough]];
case SYSTEM_EVENT_STA_DISCONNECTED:
[[fallthrough]];
case ARDUINO_EVENT_ETH_STOP:
[[fallthrough]];
case ARDUINO_EVENT_ETH_DISCONNECTED:
mStatus = NetworkState::DISCONNECTED;
break;
default:
break;
}
}
void sortRSSI(int *sort, int n) {
for (int i = 0; i < n; i++)
sort[i] = i;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
if (WiFi.RSSI(sort[j]) > WiFi.RSSI(sort[i]))
std::swap(sort[i], sort[j]);
}
protected:
void sendNTPpacket(void) {
uint8_t buf[NTP_PACKET_SIZE];
memset(buf, 0, NTP_PACKET_SIZE);
buf[0] = 0b11100011; // LI, Version, Mode
buf[1] = 0; // Stratum
buf[2] = 6; // Max Interval between messages in seconds
buf[3] = 0xEC; // Clock Precision
mUdp.write(buf, NTP_PACKET_SIZE);
}
void handleNTPPacket(AsyncUDPPacket packet) {
char buf[80];
memcpy(buf, packet.data(), sizeof(buf));
unsigned long highWord = word(buf[40], buf[41]);
unsigned long lowWord = word(buf[42], buf[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
mUdp.close();
mNtpTimeoutSec = 0; // clear timeout
mOnTimeCB(secsSince1900 - 2208988800UL);
}
protected:
enum class NetworkState : uint8_t {
DISCONNECTED,
CONNECTED,
GOT_IP,
SCAN_READY, // ESP8266
CONNECTING // ESP8266
};
public:
bool mDnsCallbackReady = false;
protected:
settings_t *mConfig = nullptr;
bool mConnected = false;
bool mScanActive = false;
bool mWifiConnecting = false;
uint8_t mNtpTimeoutSec = 0;
OnNetworkCB mOnNetworkCB;
OnTimeCB mOnTimeCB;
NetworkState mStatus = NetworkState::DISCONNECTED;
AhoyWifiAp mAp;
DNSServer mDns;
IPAddress mNtpIp;
AsyncUDP mUdp; // for time server
#if defined(ESP8266)
WiFiEventHandler wifiConnectHandler, wifiDisconnectHandler, wifiGotIPHandler;
#endif
};
#endif /*__AHOY_NETWORK_H__*/