Browse Source

Merge remote-tracking branch 'refs/remotes/origin/development03' into development03

pull/861/head
Frank 2 years ago
parent
commit
4bc5d24b63
  1. 42
      .github/ISSUE_TEMPLATE/report-ahoy.md
  2. 132
      .github/ISSUE_TEMPLATE/report.yaml
  3. 3
      .github/workflows/compile_development.yml
  4. 3
      .github/workflows/compile_release.yml
  5. 32
      Getting_Started.md
  6. 7
      README.md
  7. 45
      ahoy.code-workspace
  8. 11
      scripts/getVersion.py
  9. 45
      src/CHANGES.md
  10. 28
      src/app.cpp
  11. 8
      src/app.h
  12. 3
      src/appInterface.h
  13. 4
      src/config/settings.h
  14. 2
      src/defines.h
  15. 4
      src/platformio.ini
  16. 10
      src/publisher/pubMqtt.h
  17. 65
      src/web/RestApi.h
  18. 6
      src/web/html/includes/header.html
  19. 14
      src/web/html/includes/nav.html
  20. 4
      src/web/html/login.html
  21. 12
      src/web/html/setup.html
  22. 29
      src/web/html/style.css
  23. 161
      src/web/web.h
  24. 13
      src/wifi/ahoywifi.cpp

42
.github/ISSUE_TEMPLATE/report-ahoy.md

@ -22,58 +22,22 @@ Retailer URL: ______
### Antenna:
* [ ] circuit board
* [ ] external antenna
* [ ] external antenna (SMA)
### Power Stabilization:
* [ ] 100uF Electrolytic Capacitor
connected between +3.3V and GND (Pin 1 & 2) of the NRF Module
* [ ] Voltage stabilizing motherboard
### Connection diagram:
* [ ] Image of the your wiring attached
### Connection diagram I used:
| nRF24L01+ Pin | ESP8266 GPIO |
| ------------- | -------------- |
| Pin 1 GND [*] | GND |
| Pin 2 +3.3V | +3.3V |
| Pin 3 CE | GPIO2 CE D4 |
| Pin 4 CSN | GPIO15 CS D8 |
| Pin 5 SCK | GPIO14 SCLK D5 |
| Pin 6 MOSI | GPIO13 MOSI D7 |
| Pin 7 MISO | GPIO12 MISO D6 |
| Pin 8 IRQ | GPIO0 IRQ D3 |
| nRF24L01+ Pin | ESP32 GPIO |
| ------------- | --------------- |
| Pin 1 GND [*] | GND |
| Pin 2 +3.3V | +3.3V |
| Pin 3 CE | GPIO4 CE D4 |
| Pin 4 CSN | GPIO5 CS D5 |
| Pin 5 SCK | GPIO18 SCLK D18 |
| Pin 6 MOSI | GPIO23 MOSI D23 |
| Pin 7 MISO | GPIO19 MISO D19 |
| Pin 8 IRQ | GPIO0 IRQ D0 |
Note: [*] GND Pin 1 has a square mark on the nRF24L01+ module
## Software
* [ ] AhoyDTU
* [ ] OpenDTU
### Version / Git SHA:
Version: _._.__
Github Hash: _______
### Build & Flash Method:
* [ ] AhoyDTU Webinstaller
* [ ] VSCode - Platform IO
* [ ] Arduino
* [ ] ESP Tools
* [ ] Platform IO
### Desktop OS:
* [ ] Linux
* [ ] Windows
* [ ] Mac OS
### Debugging:
* [ ] USB Serial Log (attached)

132
.github/ISSUE_TEMPLATE/report.yaml

@ -1,18 +1,20 @@
name: "AhoyDTU bug"
description: "File a bug report"
title: "[ESP8266/ESP32/RaspberryPi] Problem Description / Beschreibung"
title: "[Bug]"
labels: ["bug", "needs-triage"]
assignees:
- stefan123t
- lumapu
body:
- type: markdown
attributes:
value: |
Bitte die Posting Guide lines lesen, Vorlage kopieren und ausfüllen und in Eurem Support Forum Eintrag posten.
Wir lesen auch gerne Deutsch, bitte fülle die u.a. Fragen aus damit wir Dir bestmöglich helfen können Danke!
Bitte unser FAQ als Hilfestellung prüfen: https://ahoydtu.de/faq
Please read, copy & fill in the template from our Posting Guide lines into your Support Forum post.
We do enjoy the english language, but we need a couple of things to best support you in your goal, please fill in all / most of the details given below. Thanks!
Check our FAQ: https://ahoydtu.de/faq
- type: markdown
attributes:
value: "## Hardware"
@ -35,11 +37,21 @@ body:
placeholder:
validations:
required: false
- type: dropdown
id: assembly-type
attributes:
label: Assembly
description:
options:
- I did the assebly by myself
- the DTU was already assembled
validations:
required: true
- type: dropdown
id: nrf24l01-module
attributes:
label: nRF24L01+ Module
description: |
description: |
What type of nRF24L01+ chip is on your nRF24L01+ module ?
* you verified this is a **nRF24L01+ plus** model capable of the required 256kBit/s mode ?
* **square dot** indicates original Nordic Semicon chip ?
@ -69,43 +81,11 @@ body:
* special **voltage stabilizing board**
* **nothing** (yet)
options:
- ~100uF Elko
- Elko (~100uF)
- board
- nothing
validations:
required: true
- type: textarea
id: connection-diagram
attributes:
label: Connection diagram
description: Tell us which connection diagram you used?
value: |
## Connection diagram I used:
| nRF24L01+ Pin | ESP8266 GPIO |
| ------------- | -------------- |
| Pin 1 GND [*] | GND |
| Pin 2 +3.3V | +3.3V |
| Pin 3 CE | GPIO2 CE D4 |
| Pin 4 CSN | GPIO15 CS D8 |
| Pin 5 SCK | GPIO14 SCLK D5 |
| Pin 6 MOSI | GPIO13 MOSI D7 |
| Pin 7 MISO | GPIO12 MISO D6 |
| Pin 8 IRQ | GPIO0 IRQ D3 |
| nRF24L01+ Pin | ESP32 GPIO |
| ------------- | --------------- |
| Pin 1 GND [*] | GND |
| Pin 2 +3.3V | +3.3V |
| Pin 3 CE | GPIO4 CE D4 |
| Pin 4 CSN | GPIO5 CS D5 |
| Pin 5 SCK | GPIO18 SCLK D18 |
| Pin 6 MOSI | GPIO23 MOSI D23 |
| Pin 7 MISO | GPIO19 MISO D19 |
| Pin 8 IRQ | GPIO0 IRQ D0 |
Note: [*] GND Pin 1 has a square mark on the nRF24L01+ module
validations:
required: true
- type: checkboxes
id: connection-picture
attributes:
@ -123,7 +103,7 @@ body:
attributes:
label: Version
description: What version of our software are you running ?
placeholder: 0.5.17
placeholder: 0.6.0
validations:
required: true
- type: input
@ -131,7 +111,7 @@ body:
attributes:
label: Github Hash
description: Which GitHub hash has the build of our software ?
placeholder: 5402e9b
placeholder: 0000000
validations:
required: true
- type: dropdown
@ -140,19 +120,11 @@ body:
label: Build & Flash Method
description: What software do you use to flash / build & flash our firmware images ?
options:
- AhoyDTU Webinstaller
- VSCode - Platform IO (build & flash)
- ESP Tools (flash)
- Platform IO (build & flash)
validations:
required: true
- type: dropdown
id: desktop-os
attributes:
label: Desktop
description: Which operating system are you using on your desktop to build & flash ?
options:
- Linux
- Mac OS
- Windows
- Arduino IDE
- was already installed
validations:
required: true
- type: textarea
@ -160,49 +132,12 @@ body:
attributes:
label: Setup
description: |
Which settings are configured under http://ahoy-dtu/setup ?
Document any relevant setup values correctly.
Copy and paste the Inverter Section if you have multiple Inverters.
value: |
### Device Host Name
- Device Name: AHOY-DTU
### WiFi
- SSID: YOUR_WIFI_SSID *don't paste here*
- Password: YOUR_WIFI_PWD *don't paste here*
### Inverter
#### Inverter 0
- Address: 1141752123456
- Name: HM-600
- Active Power Limit: 65535
- Active Power Limit Control Type: no powerlimit
- Max Module Power (Wp): 375, 375
- Module Name: link, rech
### General
- Interval [s]: 30
- Max retries per Payload: 5
### NTP Server
- NTP Server / IP: pool.ntp.org
- NTP Port: 123
### MQTT
- Broker / Server IP:
- Port: 1883
- Username (optional):
- Password (optional):
- Topic: inverter
### System Config
#### Pinout (Wemos)
- CS: D8 (GPIO15)
- CE: D4 (GPIO2)
- IRQ: D3 (GPIO0)
#### Radio (NRF24L01+)
- Amplifier Power Level: LOW
#### Serial Console
- print inverter data: [x]
- Serial Debug: [x]
- Interval [s]: 5
- Reboot device after successful save: [x]
- SAVE
Which settings were modified to which values? Check this page on your DTU: http://ahoy-dtu/setup
Do not post private data here (SSID / passwords / serial numbers)!
placeholder: |
Some examples:
- MqTT: only broker was added
- Inverter: set intervall to 5 seconds ..
validations:
required: true
- type: textarea
@ -219,12 +154,9 @@ body:
attributes:
label: Error description
description: Please describe what you expected and what happened instead.
value: |
1) Go to http://ahoy-dtu/setup
2) configure above settings
3) Reboot
4) I did this
5) I expected that
6) and something completely differen happened
placeholder: |
1) I went to https://ahoy-dtu.de/web_install and installed latest release
2) I did some configurations, especially ...
...
validations:
required: true

3
.github/workflows/compile_development.yml

@ -49,6 +49,9 @@ jobs:
- name: Run PlatformIO
run: pio run -d src --environment esp8266-release --environment esp8266-release-prometheus --environment esp8285-release --environment esp32-wroom32-release --environment esp32-wroom32-release-prometheus --environment opendtufusionv1-release
- name: Copy boot_app0.bin
run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusionv1-release/ota.bin
- name: Rename Binary files
id: rename-binary-files
working-directory: src

3
.github/workflows/compile_release.yml

@ -53,6 +53,9 @@ jobs:
- name: Run PlatformIO
run: pio run -d src --environment esp8266-release --environment esp8266-release-prometheus --environment esp8285-release --environment esp32-wroom32-release --environment esp32-wroom32-release-prometheus --environment opendtufusionv1-release
- name: Copy boot_app0.bin
run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusionv1-release/ota.bin
- name: Rename Binary files
id: rename-binary-files
working-directory: src

32
Getting_Started.md

@ -1,13 +1,12 @@
## Overview
This page describes how the module of a Wemos D1 mini and ESP8266 is wired to the radio module and is flashed with the latest Firmware.<br/>
Further information will help you to communicate to the compatible inverters.
On this page, you'll find detailed instructions on how to wire the module of a Wemos D1 mini or ESP32 to the radio module, as well as how to flash it with the latest firmware. This information will enable you to communicate with compatible inverters.
You find the full [User_Manual here](User_Manual.md)
## Compatiblity
For now the following Inverters should work out of the box:
The following inverters are currently supported out of the box:
Hoymiles Inverters
@ -55,12 +54,11 @@ Solenso Inverters:
## Things needed
In order to build your own Ahoy DTU, you will need some things.<br/>
This list is not closing as the Maker Community offers more Boards than we could cover in this Readme.<br/><br/>
If you're interested in building your own AhoyDTU, you'll need a few things to get started. While we've provided a list of recommended boards below, keep in mind that the maker community is constantly developing new and innovative options that we may not have covered in this readme..
We suggest to use a WEMOS D1 mini Board as well as a NRF24L01+ Breakout Board as a bare minimum.<br/>
Any other ESP8266 Board with at least 4MBytes of ROM could work as well, depending on your skills and goals.<br/>
Make sure the NRF24L01+ module has the "+" in its name as we depend on the 250kbps features provided only with the plus-variant.
For optimal performance, we recommend using a Wemos D1 mini or ESP32 along with a NRF24L01+ breakout board as a bare minimum. However, if you have experience working with other ESP boards, any board with at least 4MBytes of ROM may be suitable, depending on your skills.
Just be sure that the NRF24L01+ module you choose includes the "+" in its name, as we rely on the 250kbps features that are only provided by the plus-variant.
| **Parts** | **Price** |
| --- | --- |
@ -70,7 +68,7 @@ Make sure the NRF24L01+ module has the "+" in its name as we depend on the 250kb
| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro |
| **Total costs** | **10,34 Euro** |
To also run our sister project OpenDTU and be upwards compatible for the future we would recommend to spend some more money on an ESP32 board which has two CPU cores and a NRF24L01+ module with external antenna.
If you're interested in using our sister project OpenDTU or you want to future-proof your setup, we recommend investing in an ESP32 board that features two CPU cores. As Radio you can also use a NRF24L01+ module with an external antenna. While this option may cost a bit more, it will provide superior performance and ensure compatibility with upcoming developments.
| **Parts** | **Price** |
| --- | --- |
@ -154,7 +152,7 @@ Example wiring for a 38pin ESP32 module
##### ESP32 GPIO settings
For this wiring, set the 3 individual GPIOs under the /setup URL:
CS, CE, IRQ must be set according to how they are wired up. For the diagram above, set the 3 individual GPIOs under the /setup URL as follows:
```
CS D1 (GPIO5)
@ -162,14 +160,14 @@ CE D2 (GPIO4)
IRQ D0 (GPIO16 - no IRQ!)
```
ATTENTION: From development version 108 onwards, also MISO, MOSI and SCLK
are configurable. Their defaults are correct for 'standard' ESP32 boards
and non-settable for ESP8266 (as this chip cannot move them elsewhere).
If you have an existing install though, you might see '0' in the web GUI.
IMPORTANT: From development version 108/release 0.6.0 onwards, also MISO, MOSI, and SCLK
are configurable. On new installations, their defaults are correct for most ESP32 boards.
These pins cannot be configured for ESP82xx boards, as this chip cannot move them elsewhere.
Set MISO=19, MOSI=23, SCLK=18 in GUI and save for existing installs, this is the old
correct default for most ESP32 boards, for ESP82xx, a simple settings save should suffice.
Reboot afterwards.
If you are upgrading an existing install though, you might see that these pins are set to '0' in the web GUI.
Communication with the NRF module wont work. For upgrading an existing installations, set MISO=19, MOSI=23, SCLK=18 in the settings.
This is the correct default for most ESP32 boards. On ESP82xx, simply saving the settings without changes should suffice.
Save and reboot.
## Flash the Firmware on your Ahoy DTU Hardware

7
README.md

@ -16,7 +16,7 @@ This work is licensed under a
# 🖐 Ahoy!
![Logo](https://github.com/grindylow/ahoy/blob/main/doc/logo1_small.png?raw=true)
**Communicate with Hoymiles inverters via radio**. Get actual values like power, current, daily energy and set parameters like the power limit via web interface or MQTT. In this repository you will find different approaches means Hardware / Software to realize the described functionalities.
This repository offers hardware and software solutions for communicating with Hoymiles inverters via radio. With our system, you can easily obtain real-time values such as power, current, and daily energy. Additionally, you can set parameters like the power limit of your inverter to achieve zero export. You can access these functionalities through our user-friendly web interface, MQTT, or JSON. Whether you're monitoring your solar panel system's performance or fine-tuning its settings, our solutions make it easy to achieve your goals.
Table of approaches:
@ -29,6 +29,7 @@ Table of approaches:
## Getting Started
[Guide how to start with a ESP module](Getting_Started.md)
[ESP Webinstaller (Edge / Chrome Browser only)](https://ahoydtu.de/web_install)
## Our Website
@ -42,9 +43,7 @@ Table of approaches:
- [The root of development](https://www.mikrocontroller.net/topic/525778)
### Development
If you encounter issues use the issues here on github.
Please try to describe your issues as precise as possible and think about if this is a issue with the software here in the repository or other software components.
If you run into any issues, please feel free to use the issue tracker here on Github. When describing your issue, please be as detailed and precise as possible, and take a moment to consider whether the issue is related to our software. This will help us to provide more effective solutions to your problem.
**Contributors are always welcome!**

45
ahoy.code-workspace

@ -0,0 +1,45 @@
{
"folders": [
{
"path": "."
},
{
"path": "src"
}
],
"settings": {
"files.associations": {
"algorithm": "cpp",
"array": "cpp",
"chrono": "cpp",
"deque": "cpp",
"format": "cpp",
"forward_list": "cpp",
"functional": "cpp",
"initializer_list": "cpp",
"iterator": "cpp",
"list": "cpp",
"memory": "cpp",
"queue": "cpp",
"random": "cpp",
"regex": "cpp",
"vector": "cpp",
"xhash": "cpp",
"xlocmon": "cpp",
"xlocnum": "cpp",
"xmemory": "cpp",
"xstring": "cpp",
"xtree": "cpp",
"xutility": "cpp",
"*.tcc": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"string_view": "cpp",
"sstream": "cpp",
"istream": "cpp",
"ostream": "cpp"
},
"editor.formatOnSave": false
}
}

11
scripts/getVersion.py

@ -50,6 +50,7 @@ def readVersion(path, infile):
versionnumber += line[p+13:].rstrip() + "."
os.mkdir(path + "firmware/")
os.mkdir(path + "firmware/s3/")
sha = os.getenv("SHA",default="sha")
versionout = version[:-1] + "_" + sha + "_esp8266.bin"
@ -80,7 +81,7 @@ def readVersion(path, infile):
versionout = version[:-1] + "_" + sha + "_esp32s3.bin"
src = path + ".pio/build/opendtufusionv1-release/firmware.bin"
dst = path + "firmware/" + versionout
dst = path + "firmware/s3/" + versionout
os.rename(src, dst)
# other ESP32 bin files
@ -89,6 +90,14 @@ def readVersion(path, infile):
os.rename(src + "bootloader.bin", dst + "bootloader.bin")
os.rename(src + "partitions.bin", dst + "partitions.bin")
genOtaBin(path + "firmware/")
# other ESP32S3 bin files
src = path + ".pio/build/opendtufusionv1-release/"
dst = path + "firmware/s3/"
os.rename(src + "bootloader.bin", dst + "bootloader.bin")
os.rename(src + "partitions.bin", dst + "partitions.bin")
os.rename(src + "ota.bin", dst + "ota.bin")
os.rename("../scripts/gh-action-dev-build-flash.html", path + "install.html")
print("name=" + versionnumber[:-1] )

45
src/CHANGES.md

@ -1,33 +1,16 @@
Changelog v0.6.0
# Development Changes
## General
* improved night time calculation time to 1 minute after last communication pause #515
* refactored code for better readability
* improved Hoymiles commuinication (retransmits, immediate power limit transmission, timing at all)
* renamed firmware binaries
* add login / logout to menu
* add display support for `SH1106`, `SSD1306`, `Nokia` and `ePaper 1.54"` (ESP32 only)
* add yield total correction - move your yield to a new inverter or correct an already used inverter
* added import / export feature
* added `Prometheus` endpoints
* improved wifi connection and stability (connect to strongest AP)
* addded Hoymiles alarm IDs to log
* improved `System` information page (eg. radio statitistics)
* improved UI (repsonsive design, (optional) dark mode)
* improved system stability (reduced `heap-fragmentation`, don't break settings on failure) #644, #645
* added support for 2nd generation of Hoymiles inverters, MI series
* improved JSON API for more stable WebUI
* added option to disable input display in `/live` (`max-power` has to be set to `0`)
* updated documentation
* improved settings on ESP32 devices while setting SPI pins (for `NRF24` radio)
## 0.6.3 - 2023-04-04
* fix login, password length was not checked #852
* merge PR #854 optimize browser caching, thx @tastendruecker123 #828
* fix WiFi reconnect not working #851
* updated issue templates #822
## MqTT
* added `comm_disabled` #529
* added fixed interval option #542, #523
* improved communication, only required publishes
* improved retained flags
* added `set_power_limit` acknowledge MQTT publish #553
* added feature to reset values on midnight, communication pause or if the inverters are not available
* partially added Hoymiles alarm ID
* improved autodiscover (added total values on multi-inverter setup)
* improved `clientID` a part of the MAC address is added to have an unique name
## 0.6.2 - 2023-04-04
* fix login from multiple clients #819
* fix login screen on small displays
## 0.6.1 - 2023-04-01
* merge LED fix - LED1 shows MqTT state, LED configureable active high/low #839
* only publish new inverter data #826
* potential fix of WiFi hostname during boot up #752

28
src/app.cpp

@ -387,30 +387,40 @@ void app::mqttSubRxCb(JsonObject obj) {
//-----------------------------------------------------------------------------
void app::setupLed(void) {
/** LED connection diagram
* \\
* PIN ---- |<----- 3.3V
*
* */
uint8_t led_off = (mConfig->led.led_high_active != 0) ? LOW : HIGH;
if (mConfig->led.led0 != 0xff) {
pinMode(mConfig->led.led0, OUTPUT);
digitalWrite(mConfig->led.led0, HIGH); // LED off
digitalWrite(mConfig->led.led0, led_off);
}
if (mConfig->led.led1 != 0xff) {
pinMode(mConfig->led.led1, OUTPUT);
digitalWrite(mConfig->led.led1, HIGH); // LED off
digitalWrite(mConfig->led.led1, led_off);
}
}
//-----------------------------------------------------------------------------
void app::updateLed(void) {
uint8_t led_off = (mConfig->led.led_high_active != 0) ? LOW : HIGH;
uint8_t led_on = (mConfig->led.led_high_active != 0) ? HIGH : LOW;
if (mConfig->led.led0 != 0xff) {
Inverter<> *iv = mSys.getInverterByPos(0);
if (NULL != iv) {
if (iv->isProducing(mTimestamp))
digitalWrite(mConfig->led.led0, LOW); // LED on
digitalWrite(mConfig->led.led0, led_on);
else
digitalWrite(mConfig->led.led0, HIGH); // LED off
digitalWrite(mConfig->led.led0, led_off);
}
}
if (mConfig->led.led1 != 0xff) {
if (getMqttIsConnected()) {
digitalWrite(mConfig->led.led1, led_on);
} else {
digitalWrite(mConfig->led.led1, led_off);
}
}
}

8
src/app.h

@ -157,8 +157,8 @@ class app : public IApp, public ah::Scheduler {
return mMqtt.getRxCnt();
}
bool getProtection() {
return mWeb.getProtection();
bool getProtection(AsyncWebServerRequest *request) {
return mWeb.isProtected(request);
}
uint8_t getIrqPin(void) {
@ -213,8 +213,8 @@ class app : public IApp, public ah::Scheduler {
void mqttSubRxCb(JsonObject obj);
void setupLed(void);
void updateLed(void);
void setupLed();
void updateLed();
void tickReboot(void) {
DPRINTLN(DBG_INFO, F("Rebooting..."));

3
src/appInterface.h

@ -8,6 +8,7 @@
#include "defines.h"
#include "hm/hmSystem.h"
#include "ESPAsyncWebServer.h"
// abstract interface to App. Make members of App accessible from child class
// like web or API without forward declaration
@ -47,7 +48,7 @@ class IApp {
virtual uint32_t getMqttRxCnt() = 0;
virtual uint32_t getMqttTxCnt() = 0;
virtual bool getProtection() = 0;
virtual bool getProtection(AsyncWebServerRequest *request) = 0;
};
#endif /*__IAPP_H__*/

4
src/config/settings.h

@ -100,6 +100,7 @@ typedef struct {
typedef struct {
uint8_t led0; // first LED pin
uint8_t led1; // second LED pin
uint8_t led_high_active; // determines if LEDs are high or low active
} cfgLed_t;
typedef struct {
@ -378,6 +379,7 @@ class settings {
mCfg.led.led0 = DEF_PIN_OFF;
mCfg.led.led1 = DEF_PIN_OFF;
mCfg.led.led_high_active = LOW;
memset(&mCfg.inst, 0, sizeof(cfgInst_t));
@ -517,9 +519,11 @@ class settings {
if(set) {
obj[F("0")] = mCfg.led.led0;
obj[F("1")] = mCfg.led.led1;
obj[F("led_high_active")] = mCfg.led.led_high_active;
} else {
mCfg.led.led0 = obj[F("0")];
mCfg.led.led1 = obj[F("1")];
mCfg.led.led_high_active = obj[F("led_high_active")];
}
}

2
src/defines.h

@ -13,7 +13,7 @@
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 6
#define VERSION_PATCH 0
#define VERSION_PATCH 3
//-------------------------------------
typedef struct {

4
src/platformio.ini

@ -138,6 +138,10 @@ monitor_filters =
[env:opendtufusionv1-release]
platform = espressif32
board = esp32-s3-devkitc-1
upload_protocol = esp-builtin
upload_speed = 115200
debug_tool = esp-builtin
debug_speed = 12000
build_flags = -D RELEASE -std=gnu++14
build_unflags = -std=gnu++11
monitor_filters =

10
src/publisher/pubMqtt.h

@ -49,6 +49,7 @@ class PubMqtt {
mTxCnt = 0;
mSubscriptionCb = NULL;
memset(mLastIvState, MQTT_STATUS_NOT_AVAIL_NOT_PROD, MAX_NUM_INVERTERS);
memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * 4);
mLastAnyAvail = false;
}
@ -522,7 +523,13 @@ class PubMqtt {
void sendData(Inverter<> *iv, uint8_t curInfoCmd) {
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
if (iv->getLastTs(rec) > 0) {
uint32_t lastTs = iv->getLastTs(rec);
bool pubData = (lastTs > 0);
if (curInfoCmd == RealTimeRunData_Debug)
pubData &= (lastTs != mIvLastRTRpub[iv->id]);
if (pubData) {
mIvLastRTRpub[iv->id] = lastTs;
for (uint8_t i = 0; i < rec->length; i++) {
bool retained = false;
if (curInfoCmd == RealTimeRunData_Debug) {
@ -653,6 +660,7 @@ class PubMqtt {
subscriptionCb mSubscriptionCb;
bool mLastAnyAvail;
uint8_t mLastIvState[MAX_NUM_INVERTERS];
uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS];
uint16_t mIntervalTimeout;
// last will topic and payload must be available trough lifetime of 'espMqttClient'

65
src/web/RestApi.h

@ -77,19 +77,19 @@ class RestApi {
JsonObject root = response->getRoot();
String path = request->url().substring(5);
if(path == "html/system") getHtmlSystem(root);
else if(path == "html/logout") getHtmlLogout(root);
else if(path == "html/reboot") getHtmlReboot(root);
else if(path == "html/save") getHtmlSave(root);
else if(path == "system") getSysInfo(root);
else if(path == "generic") getGeneric(root);
else if(path == "reboot") getReboot(root);
if(path == "html/system") getHtmlSystem(request, root);
else if(path == "html/logout") getHtmlLogout(request, root);
else if(path == "html/reboot") getHtmlReboot(request, root);
else if(path == "html/save") getHtmlSave(request, root);
else if(path == "system") getSysInfo(request, root);
else if(path == "generic") getGeneric(request, root);
else if(path == "reboot") getReboot(request, root);
else if(path == "statistics") getStatistics(root);
else if(path == "inverter/list") getInverterList(root);
else if(path == "index") getIndex(root);
else if(path == "setup") getSetup(root);
else if(path == "index") getIndex(request, root);
else if(path == "setup") getSetup(request, root);
else if(path == "setup/networks") getNetworks(root);
else if(path == "live") getLive(root);
else if(path == "live") getLive(request, root);
else if(path == "record/info") getRecord(root, InverterDevInform_All);
else if(path == "record/alarm") getRecord(root, AlarmData);
else if(path == "record/config") getRecord(root, SystemConfigPara);
@ -189,10 +189,10 @@ class RestApi {
fp.close();
}
void getGeneric(JsonObject obj) {
void getGeneric(AsyncWebServerRequest *request, JsonObject obj) {
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
obj[F("ts_uptime")] = mApp->getUptime();
obj[F("menu_prot")] = mApp->getProtection();
obj[F("menu_prot")] = mApp->getProtection(request);
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
@ -203,7 +203,7 @@ class RestApi {
#endif
}
void getSysInfo(JsonObject obj) {
void getSysInfo(AsyncWebServerRequest *request, JsonObject obj) {
obj[F("ssid")] = mConfig->sys.stationSsid;
obj[F("device_name")] = mConfig->sys.deviceName;
obj[F("dark_mode")] = (bool)mConfig->sys.darkMode;
@ -218,7 +218,7 @@ class RestApi {
obj[F("heap_free")] = mHeapFree;
obj[F("sketch_total")] = ESP.getFreeSketchSpace();
obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb
getGeneric(obj);
getGeneric(request, obj);
getRadio(obj.createNestedObject(F("radio")));
getStatistics(obj.createNestedObject(F("statistics")));
@ -252,34 +252,34 @@ class RestApi {
obj[F("schMax")] = max;
}
void getHtmlSystem(JsonObject obj) {
getSysInfo(obj.createNestedObject(F("system")));
getGeneric(obj.createNestedObject(F("generic")));
void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) {
getSysInfo(request, obj.createNestedObject(F("system")));
getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
}
void getHtmlLogout(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic")));
void getHtmlLogout(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("refresh")] = 3;
obj[F("refresh_url")] = "/";
obj[F("html")] = F("succesfully logged out");
}
void getHtmlReboot(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic")));
void getHtmlReboot(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("refresh")] = 20;
obj[F("refresh_url")] = "/";
obj[F("html")] = F("rebooting ...");
}
void getHtmlSave(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic")));
void getHtmlSave(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(request, obj.createNestedObject(F("generic")));
obj["pending"] = (bool)mApp->getSavePending();
obj["success"] = (bool)mApp->getLastSaveSucceed();
}
void getReboot(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic")));
void getReboot(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("refresh")] = 10;
obj[F("refresh_url")] = "/";
obj[F("html")] = F("reboot. Autoreload after 10 seconds");
@ -391,6 +391,7 @@ class RestApi {
obj[F("miso")] = mConfig->nrf.pinMiso;
obj[F("led0")] = mConfig->led.led0;
obj[F("led1")] = mConfig->led.led1;
obj[F("led_high_active")] = mConfig->led.led_high_active;
}
void getRadio(JsonObject obj) {
@ -429,8 +430,8 @@ class RestApi {
obj[F("disp_bsy")] = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : mConfig->plugin.display.disp_busy;
}
void getIndex(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic")));
void getIndex(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("ts_now")] = mApp->getTimestamp();
obj[F("ts_sunrise")] = mApp->getSunrise();
obj[F("ts_sunset")] = mApp->getSunset();
@ -478,9 +479,9 @@ class RestApi {
info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds"));
}
void getSetup(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic")));
getSysInfo(obj.createNestedObject(F("system")));
void getSetup(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(request, obj.createNestedObject(F("generic")));
getSysInfo(request, obj.createNestedObject(F("system")));
//getInverterList(obj.createNestedObject(F("inverter")));
getMqtt(obj.createNestedObject(F("mqtt")));
getNtp(obj.createNestedObject(F("ntp")));
@ -496,8 +497,8 @@ class RestApi {
mApp->getAvailNetworks(obj);
}
void getLive(JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic")));
void getLive(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("refresh")] = mConfig->nrf.sendInterval;
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {

6
src/web/html/includes/header.html

@ -1,5 +1,5 @@
<link rel="stylesheet" type="text/css" href="colors.css"/>
<link rel="stylesheet" type="text/css" href="style.css"/>
<link rel="stylesheet" type="text/css" href="style.css?v={#VERSION}"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<script type="text/javascript" src="api.js"></script>
<script type="text/javascript" src="api.js?v={#VERSION}"></script>
<link rel="stylesheet" type="text/css" href="colors.css?v={#VERSION}"/>

14
src/web/html/includes/nav.html

@ -1,21 +1,21 @@
<div class="topnav">
<a href="/" class="title">AhoyDTU</a>
<a href="/?v={#VERSION}" class="title">AhoyDTU</a>
<a href="javascript:void(0);" class="icon" onclick="topnav()">
<span></span>
<span></span>
<span></span>
</a>
<div id="topnav" class="mobile">
<a id="nav3" class="hide" href="/live">Live</a>
<a id="nav4" class="hide" href="/serial">Serial / Control</a>
<a id="nav5" class="hide" href="/setup">Settings</a>
<a id="nav3" class="hide" href="/live?v={#VERSION}">Live</a>
<a id="nav4" class="hide" href="/serial?v={#VERSION}">Serial / Control</a>
<a id="nav5" class="hide" href="/setup?v={#VERSION}">Settings</a>
<span class="seperator"></span>
<a id="nav6" class="hide" href="/update">Update</a>
<a id="nav7" class="hide" href="/system">System</a>
<a id="nav6" class="hide" href="/update?v={#VERSION}">Update</a>
<a id="nav7" class="hide" href="/system?v={#VERSION}">System</a>
<span class="seperator"></span>
<a id="nav8" href="/api" target="_blank">REST API</a>
<a id="nav9" href="https://ahoydtu.de" target="_blank">Documentation</a>
<a id="nav10" href="/about">About</a>
<a id="nav10" href="/about?v={#VERSION}">About</a>
<span class="seperator"></span>
<a id="nav0" class="hide" href="/login">Login</a>
<a id="nav1" class="hide" href="/logout">Logout</a>

4
src/web/html/login.html

@ -11,8 +11,8 @@
<form action="/login" method="post">
<div class="row"><h2>AhoyDTU</h2></div>
<div class="row">
<div class="col-8"><input type="password" name="pwd" autofocus></div>
<div class="col-4"><input type="submit" name="login" value="login" class="btn"></div>
<div class="col-12 col-sm-8 mb-3"><input type="password" name="pwd" autofocus></div>
<div class="col-12 col-sm-4"><input type="submit" name="login" value="login" class="btn"></div>
</div>
</form>
</div>

12
src/web/html/setup.html

@ -411,6 +411,10 @@
[47, "GPIO47"],
[48, "GPIO48"],
];
var led_high_active = [
[0, "low active"],
[1, "high active"],
];
const re = /11[2,4,6]1.*/;
@ -670,6 +674,14 @@
])
);
}
e.append(
ml("div", { class: "row mb-3" }, [
ml("div", { class: "col-12 col-sm-3 my-2" }, "LED polarity"),
ml("div", { class: "col-12 col-sm-9" },
sel('pinLedHighActive', led_high_active, obj['led_high_active'])
)
])
);
}
function parseRadio(obj) {

29
src/web/html/style.css

@ -546,6 +546,18 @@ div.hr {
width: 100%;
}
#login {
width: 450px;
height: 200px;
border: 1px solid #ccc;
background-color: var(--input-bg);
position: absolute;
top: 50%;
left: 50%;
margin-top: -160px;
margin-left: -225px;
}
@media(max-width: 500px) {
div.ch .unit, div.ch-iv .unit {
font-size: 18px;
@ -559,6 +571,11 @@ div.hr {
.subgrp {
width: 180px;
}
#login {
margin-left: -150px;
width: 300px;
}
}
#serial {
@ -578,18 +595,6 @@ div.hr {
margin-top: 15px;
}
#login {
width: 450px;
height: 200px;
border: 1px solid #ccc;
background-color: var(--input-bg);
position: absolute;
top: 50%;
left: 50%;
margin-top: -160px;
margin-left: -225px;
}
.head {
background-color: var(--primary);

161
src/web/web.h

@ -34,7 +34,7 @@
#define WEB_SERIAL_BUF_SIZE 2048
const char *const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1"};
const char *const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLedHighActive"};
template <class HMSYSTEM>
class Web {
@ -126,8 +126,21 @@ class Web {
mProtected = protect;
}
bool getProtection() {
return mProtected;
bool isProtected(AsyncWebServerRequest *request) {
bool prot;
prot = mProtected;
if(!prot) {
if(strlen(mConfig->sys.adminPwd) > 0) {
uint8_t ip[4];
ah::ip2Arr(ip, request->client()->remoteIP().toString().c_str());
for(uint8_t i = 0; i < 4; i++) {
if(mLoginIp[i] != ip[i])
prot = true;
}
}
}
return prot;
}
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
@ -216,7 +229,7 @@ class Web {
}
private:
void checkRedirect(AsyncWebServerRequest *request) {
inline void checkRedirect(AsyncWebServerRequest *request) {
if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX)
request->redirect(F("/index"));
else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE)
@ -229,21 +242,29 @@ class Web {
request->redirect(F("/login"));
}
void onUpdate(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onUpdate"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) {
if (mProtected) {
checkRedirect(request);
return;
}
void checkProtection(AsyncWebServerRequest *request) {
if(isProtected(request)) {
checkRedirect(request);
return;
}
}
void getPage(AsyncWebServerRequest *request, uint8_t mask, const uint8_t *zippedHtml, uint32_t len) {
if (CHECK_MASK(mConfig->sys.protectionMask, mask))
checkProtection(request);
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), update_html, update_html_len);
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), zippedHtml, len);
response->addHeader(F("Content-Encoding"), "gzip");
response->addHeader(F("content-type"), "text/html; charset=UTF-8");
if(request->hasParam("v"))
response->addHeader(F("Cache-Control"), F("max-age=604800"));
request->send(response);
}
void onUpdate(AsyncWebServerRequest *request) {
getPage(request, PROT_MASK_UPDATE, update_html, update_html_len);
}
void showUpdate(AsyncWebServerRequest *request) {
bool reboot = (!Update.hasError());
@ -288,18 +309,7 @@ class Web {
}
void onIndex(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onIndex"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
if (mProtected) {
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), index_html, index_html_len);
response->addHeader(F("Content-Encoding"), "gzip");
request->send(response);
getPage(request, PROT_MASK_INDEX, index_html, index_html_len);
}
void onLogin(AsyncWebServerRequest *request) {
@ -308,6 +318,7 @@ class Web {
if (request->args() > 0) {
if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
mProtected = false;
ah::ip2Arr(mLoginIp, request->client()->remoteIP().toString().c_str());
request->redirect("/");
}
}
@ -320,10 +331,7 @@ class Web {
void onLogout(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLogout"));
if (mProtected) {
checkRedirect(request);
return;
}
checkProtection(request);
mProtected = true;
@ -340,6 +348,9 @@ class Web {
else
response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len);
response->addHeader(F("Content-Encoding"), "gzip");
if(request->hasParam("v")) {
response->addHeader(F("Cache-Control"), F("max-age=604800"));
}
request->send(response);
}
@ -348,6 +359,9 @@ class Web {
mLogoutTimeout = LOGOUT_TIMEOUT;
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len);
response->addHeader(F("Content-Encoding"), "gzip");
if(request->hasParam("v")) {
response->addHeader(F("Cache-Control"), F("max-age=604800"));
}
request->send(response);
}
@ -356,6 +370,8 @@ class Web {
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), api_js, api_js_len);
response->addHeader(F("Content-Encoding"), "gzip");
if(request->hasParam("v"))
response->addHeader(F("Cache-Control"), F("max-age=604800"));
request->send(response);
}
@ -367,10 +383,8 @@ class Web {
}
void showNotFound(AsyncWebServerRequest *request) {
if (mProtected)
checkRedirect(request);
else
request->redirect("/setup");
checkProtection(request);
request->redirect("/setup");
}
void onReboot(AsyncWebServerRequest *request) {
@ -381,10 +395,7 @@ class Web {
}
void showErase(AsyncWebServerRequest *request) {
if (mProtected) {
checkRedirect(request);
return;
}
checkProtection(request);
DPRINTLN(DBG_VERBOSE, F("showErase"));
mApp->eraseSettings(false);
@ -392,10 +403,7 @@ class Web {
}
void showFactoryRst(AsyncWebServerRequest *request) {
if (mProtected) {
checkRedirect(request);
return;
}
checkProtection(request);
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
String content = "";
@ -422,27 +430,13 @@ class Web {
}
void onSetup(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSetup"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
if (mProtected) {
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), setup_html, setup_html_len);
response->addHeader(F("Content-Encoding"), "gzip");
request->send(response);
getPage(request, PROT_MASK_SETUP, setup_html, setup_html_len);
}
void showSave(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("showSave"));
if (mProtected) {
checkRedirect(request);
return;
}
checkProtection(request);
if (request->args() == 0)
return;
@ -532,6 +526,7 @@ class Web {
case 5: mConfig->nrf.pinMiso = ((pin != 0xff) ? pin : DEF_MISO_PIN); break;
case 6: mConfig->led.led0 = pin; break;
case 7: mConfig->led.led1 = pin; break;
case 8: mConfig->led.led_high_active = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense
}
}
@ -602,33 +597,16 @@ class Web {
}
void onLive(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLive"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
if (mProtected) {
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), visualization_html, visualization_html_len);
response->addHeader(F("Content-Encoding"), "gzip");
response->addHeader(F("content-type"), "text/html; charset=UTF-8");
request->send(response);
getPage(request, PROT_MASK_LIVE, visualization_html, visualization_html_len);
}
void onAbout(AsyncWebServerRequest *request) {
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
if (mProtected) {
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), about_html, about_html_len);
response->addHeader(F("Content-Encoding"), "gzip");
response->addHeader(F("content-type"), "text/html; charset=UTF-8");
if(request->hasParam("v")) {
response->addHeader(F("Cache-Control"), F("max-age=604800"));
}
request->send(response);
}
@ -640,33 +618,11 @@ class Web {
}
void onSerial(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSerial"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
if (mProtected) {
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), serial_html, serial_html_len);
response->addHeader(F("Content-Encoding"), "gzip");
request->send(response);
getPage(request, PROT_MASK_SERIAL, serial_html, serial_html_len);
}
void onSystem(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSystem"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
if (mProtected) {
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
response->addHeader(F("Content-Encoding"), "gzip");
request->send(response);
getPage(request, PROT_MASK_SYSTEM, system_html, system_html_len);
}
@ -839,6 +795,7 @@ class Web {
AsyncEventSource mEvts;
bool mProtected;
uint32_t mLogoutTimeout;
uint8_t mLoginIp[4];
IApp *mApp;
HMSYSTEM *mSys;

13
src/wifi/ahoywifi.cpp

@ -1,6 +1,6 @@
//-----------------------------------------------------------------------------
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#if defined(ESP32) && defined(F)
@ -25,6 +25,7 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) {
mStaConn = DISCONNECTED;
mCnt = 0;
mScanActive = false;
mScanCnt = 0;
#if defined(ESP8266)
wifiConnectHandler = WiFi.onStationModeConnected(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1));
@ -109,7 +110,7 @@ void ahoywifi::tickWifiLoop() {
DBGPRINTLN(F(" seconds"));
if(mScanActive) {
getBSSIDs();
if(!mScanActive) // scan completed
if((!mScanActive) && (!mBSSIDList.empty())) // scan completed
if ((mCnt % timeout) < timeout - 2)
mCnt = timeout - 2;
}
@ -160,6 +161,9 @@ void ahoywifi::setupAp(void) {
DBGPRINTLN(mApIp.toString());
DBGPRINTLN(F("---------\n"));
if(String(mConfig->sys.deviceName) != "")
WiFi.hostname(mConfig->sys.deviceName);
WiFi.mode(WIFI_AP_STA);
WiFi.softAPConfig(mApIp, mApIp, IPAddress(255, 255, 255, 0));
WiFi.softAP(WIFI_AP_SSID, WIFI_AP_PWD);
@ -294,8 +298,7 @@ void ahoywifi::getAvailNetworks(JsonObject obj) {
void ahoywifi::getBSSIDs() {
int n = WiFi.scanComplete();
if (n < 0) {
mScanCnt++;
if (mScanCnt < 20)
if (++mScanCnt < 20)
return;
}
if(n > 0) {

Loading…
Cancel
Save