mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-10 15:36:38 +02:00
added ESP8266 code
This commit is contained in:
parent
8030acd283
commit
5f927ad8c5
19 changed files with 1641 additions and 0 deletions
158
tools/esp8266/CircularBuffer.h
Normal file
158
tools/esp8266/CircularBuffer.h
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
CircularBuffer - An Arduino circular buffering library for arbitrary types.
|
||||
|
||||
Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef CircularBuffer_h
|
||||
#define CircularBuffer_h
|
||||
|
||||
#ifdef ESP8266
|
||||
#define DISABLE_IRQ noInterrupts()
|
||||
#define RESTORE_IRQ interrupts()
|
||||
#else
|
||||
#define DISABLE_IRQ \
|
||||
uint8_t sreg = SREG; \
|
||||
cli();
|
||||
|
||||
#define RESTORE_IRQ \
|
||||
SREG = sreg;
|
||||
#endif
|
||||
|
||||
template <class T> class CircularBuffer
|
||||
{
|
||||
public:
|
||||
/** Constructor
|
||||
* @param buffer Preallocated buffer of at least size records.
|
||||
* @param size Number of records available in the buffer.
|
||||
*/
|
||||
CircularBuffer(T* buffer, const uint8_t size )
|
||||
: m_size(size), m_buff(buffer)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
/** Clear all entries in the circular buffer. */
|
||||
void clear(void)
|
||||
{
|
||||
m_front = 0;
|
||||
m_fill = 0;
|
||||
}
|
||||
|
||||
/** Test if the circular buffer is empty */
|
||||
inline bool empty(void) const
|
||||
{
|
||||
return !m_fill;
|
||||
}
|
||||
|
||||
/** Return the number of records stored in the buffer */
|
||||
inline uint8_t available(void) const
|
||||
{
|
||||
return m_fill;
|
||||
}
|
||||
|
||||
/** Test if the circular buffer is full */
|
||||
inline bool full(void) const
|
||||
{
|
||||
return m_fill == m_size;
|
||||
}
|
||||
|
||||
/** Aquire record on front of the buffer, for writing.
|
||||
* After filling the record, it has to be pushed to actually
|
||||
* add it to the buffer.
|
||||
* @return Pointer to record, or NULL when buffer is full.
|
||||
*/
|
||||
T* getFront(void) const
|
||||
{
|
||||
DISABLE_IRQ;
|
||||
T* f = NULL;
|
||||
if (!full())
|
||||
f = get(m_front);
|
||||
RESTORE_IRQ;
|
||||
return f;
|
||||
}
|
||||
|
||||
/** Push record to front of the buffer
|
||||
* @param record Record to push. If record was aquired previously (using getFront) its
|
||||
* data will not be copied as it is already present in the buffer.
|
||||
* @return True, when record was pushed successfully.
|
||||
*/
|
||||
bool pushFront(T* record)
|
||||
{
|
||||
bool ok = false;
|
||||
DISABLE_IRQ;
|
||||
if (!full())
|
||||
{
|
||||
T* f = get(m_front);
|
||||
if (f != record)
|
||||
*f = *record;
|
||||
m_front = (m_front+1) % m_size;
|
||||
m_fill++;
|
||||
ok = true;
|
||||
}
|
||||
RESTORE_IRQ;
|
||||
return ok;
|
||||
}
|
||||
|
||||
/** Aquire record on back of the buffer, for reading.
|
||||
* After reading the record, it has to be pop'ed to actually
|
||||
* remove it from the buffer.
|
||||
* @return Pointer to record, or NULL when buffer is empty.
|
||||
*/
|
||||
T* getBack(void) const
|
||||
{
|
||||
T* b = NULL;
|
||||
DISABLE_IRQ;
|
||||
if (!empty())
|
||||
b = get(back());
|
||||
RESTORE_IRQ;
|
||||
return b;
|
||||
}
|
||||
|
||||
/** Remove record from back of the buffer.
|
||||
* @return True, when record was pop'ed successfully.
|
||||
*/
|
||||
bool popBack(void)
|
||||
{
|
||||
bool ok = false;
|
||||
DISABLE_IRQ;
|
||||
if (!empty())
|
||||
{
|
||||
m_fill--;
|
||||
ok = true;
|
||||
}
|
||||
RESTORE_IRQ;
|
||||
return ok;
|
||||
}
|
||||
|
||||
protected:
|
||||
inline T * get(const uint8_t idx) const
|
||||
{
|
||||
return &(m_buff[idx]);
|
||||
}
|
||||
inline uint8_t back(void) const
|
||||
{
|
||||
return (m_front - m_fill + m_size) % m_size;
|
||||
}
|
||||
|
||||
const uint8_t m_size; // Total number of records that can be stored in the buffer.
|
||||
T* const m_buff; // Ptr to buffer holding all records.
|
||||
volatile uint8_t m_front; // Index of front element (not pushed yet).
|
||||
volatile uint8_t m_fill; // Amount of records currently pushed.
|
||||
};
|
||||
|
||||
#endif // CircularBuffer_h
|
45
tools/esp8266/README.md
Normal file
45
tools/esp8266/README.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
## OVERVIEW
|
||||
|
||||
This code was tested on a ESP8266 - ESP-07 module. Many parts of the code are based on 'Hubi's code, which can be found here: <https://www.mikrocontroller.net/topic/525778?page=3#7033371>
|
||||
|
||||
The NRF24L01+ radio module is connected to the standard SPI pins. Additional there are 3 pins, which can be set individual:
|
||||
|
||||
- IRQ - Pin 4
|
||||
- CE - Pin 5
|
||||
- CS - Pin 15
|
||||
|
||||
|
||||
## Compile
|
||||
|
||||
This code can be compiled using Arduino. The settings were:
|
||||
|
||||
- Board: Generic ESP8266 Module
|
||||
- Flash-Size: 1MB (FS: none, OTA: 502kB)
|
||||
|
||||
|
||||
## Flash ESP with firmware
|
||||
|
||||
1. flash the ESP with the compiled firmware using the UART pins or any preinstalled firmware with OTA capabilities
|
||||
2. repower the ESP
|
||||
3. the ESP will start as access point (AP) if there is no network config stored in its eeprom
|
||||
4. connect to the AP, you will be forwarded to the setup page
|
||||
5. configure your WiFi settings, save, repower
|
||||
6. check your router for the IP address of the module
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Connect the ESP to power and to your serial console. The webinterface is currently only used for OTA and config.
|
||||
The serial console will print all information which is send and received.
|
||||
|
||||
|
||||
## Known Issues
|
||||
|
||||
- only command 0x81 is received
|
||||
|
||||
|
||||
## USED LIBRARIES
|
||||
|
||||
- `Time`
|
||||
- `RF24`
|
||||
|
25
tools/esp8266/ahoy-esp.ino
Normal file
25
tools/esp8266/ahoy-esp.ino
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "app.h"
|
||||
|
||||
app myApp;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void setup() {
|
||||
pinMode(RF24_IRQ_PIN, INPUT_PULLUP);
|
||||
attachInterrupt(digitalPinToInterrupt(RF24_IRQ_PIN), handleIntr, FALLING);
|
||||
|
||||
// AP name, password, timeout
|
||||
myApp.setup("ESP AHOY", "esp_8266", 15);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void loop() {
|
||||
myApp.loop();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
ICACHE_RAM_ATTR void handleIntr(void) {
|
||||
myApp.handleIntr();
|
||||
}
|
||||
|
265
tools/esp8266/app.cpp
Normal file
265
tools/esp8266/app.cpp
Normal file
|
@ -0,0 +1,265 @@
|
|||
#include "app.h"
|
||||
|
||||
#include "html/h/index_html.h"
|
||||
extern String setup_html;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
app::app() : Main() {
|
||||
uint8_t wrAddr[6];
|
||||
mRadio = new RF24(RF24_CE_PIN, RF24_CS_PIN);
|
||||
|
||||
mRadio->begin();
|
||||
mRadio->setAutoAck(false);
|
||||
mRadio->setRetries(0, 0);
|
||||
|
||||
mHoymiles = new hoymiles();
|
||||
mEep->read(ADDR_HOY_ADDR, mHoymiles->mAddrBytes, HOY_ADDR_LEN);
|
||||
mHoymiles->serial2RadioId();
|
||||
|
||||
mBufCtrl = new CircularBuffer(mBuffer, PACKET_BUFFER_SIZE);
|
||||
|
||||
mSendCnt = 0;
|
||||
mSendTicker = new Ticker();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
app::~app(void) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::setup(const char *ssid, const char *pwd, uint32_t timeout) {
|
||||
Main::setup(ssid, pwd, timeout);
|
||||
|
||||
mWeb->on("/", std::bind(&app::showIndex, this));
|
||||
mWeb->on("/setup", std::bind(&app::showSetup, this));
|
||||
mWeb->on("/save ", std::bind(&app::showSave, this));
|
||||
|
||||
initRadio();
|
||||
|
||||
mSendTicker->attach_ms(1000, std::bind(&app::sendTicker, this));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::loop(void) {
|
||||
Main::loop();
|
||||
|
||||
if(!mBufCtrl->empty()) {
|
||||
uint8_t len, rptCnt;
|
||||
NRF24_packet_t *p = mBufCtrl->getBack();
|
||||
|
||||
//mHoymiles->dumpBuf("RAW", p->packet, PACKET_BUFFER_SIZE);
|
||||
|
||||
if(mHoymiles->checkCrc(p->packet, &len, &rptCnt)) {
|
||||
// process buffer only on first occurrence
|
||||
if((0 != len) && (0 == rptCnt)) {
|
||||
mHoymiles->dumpBuf("Payload", p->packet, len);
|
||||
// @TODO: do analysis here
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(p->packetsLost != 0) {
|
||||
Serial.println("Lost packets: " + String(p->packetsLost));
|
||||
}
|
||||
}
|
||||
mBufCtrl->popBack();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::handleIntr(void) {
|
||||
uint8_t lostCnt = 0, pipe, len;
|
||||
NRF24_packet_t *p;
|
||||
|
||||
DISABLE_IRQ;
|
||||
|
||||
while(mRadio->available(&pipe)) {
|
||||
if(!mBufCtrl->full()) {
|
||||
p = mBufCtrl->getFront();
|
||||
memset(p->packet, 0xcc, MAX_RF_PAYLOAD_SIZE);
|
||||
p->timestamp = micros(); // Micros does not increase in interrupt, but it can be used.
|
||||
p->packetsLost = lostCnt;
|
||||
len = mRadio->getPayloadSize();
|
||||
if(len > MAX_RF_PAYLOAD_SIZE)
|
||||
len = MAX_RF_PAYLOAD_SIZE;
|
||||
|
||||
mRadio->read(p->packet, len);
|
||||
mBufCtrl->pushFront(p);
|
||||
lostCnt = 0;
|
||||
}
|
||||
else {
|
||||
bool tx_ok, tx_fail, rx_ready;
|
||||
if(lostCnt < 255)
|
||||
lostCnt++;
|
||||
mRadio->whatHappened(tx_ok, tx_fail, rx_ready); // reset interrupt status
|
||||
mRadio->flush_rx(); // drop the packet
|
||||
}
|
||||
}
|
||||
|
||||
RESTORE_IRQ;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::initRadio(void) {
|
||||
mRadio->setChannel(DEFAULT_RECV_CHANNEL);
|
||||
mRadio->setDataRate(RF24_250KBPS);
|
||||
mRadio->disableCRC();
|
||||
mRadio->setAutoAck(false);
|
||||
mRadio->setPayloadSize(MAX_RF_PAYLOAD_SIZE);
|
||||
mRadio->setAddressWidth(5);
|
||||
mRadio->openReadingPipe(1, DTU_RADIO_ID);
|
||||
|
||||
// enable only receiving interrupts
|
||||
mRadio->maskIRQ(true, true, false);
|
||||
|
||||
// Use lo PA level, as a higher level will disturb CH340 serial usb adapter
|
||||
mRadio->setPALevel(RF24_PA_MAX);
|
||||
mRadio->startListening();
|
||||
|
||||
Serial.println("Radio Config:");
|
||||
mRadio->printPrettyDetails();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::sendPacket(uint8_t buf[], uint8_t len) {
|
||||
DISABLE_IRQ;
|
||||
|
||||
mRadio->stopListening();
|
||||
|
||||
#ifdef CHANNEL_HOP
|
||||
mRadio->setChannel(mHoymiles->getNxtChannel());
|
||||
#else
|
||||
mRadio->setChannel(mHoymiles->getDefaultChannel());
|
||||
#endif
|
||||
|
||||
mRadio->openWritingPipe(mHoymiles->mRadioId);
|
||||
mRadio->setCRCLength(RF24_CRC_16);
|
||||
mRadio->enableDynamicPayloads();
|
||||
mRadio->setAutoAck(true);
|
||||
mRadio->setRetries(3, 15);
|
||||
|
||||
mRadio->write(buf, len);
|
||||
|
||||
// Try to avoid zero payload acks (has no effect)
|
||||
mRadio->openWritingPipe(DUMMY_RADIO_ID);
|
||||
|
||||
mRadio->setAutoAck(false);
|
||||
mRadio->setRetries(0, 0);
|
||||
mRadio->disableDynamicPayloads();
|
||||
mRadio->setCRCLength(RF24_CRC_DISABLED);
|
||||
|
||||
mRadio->setChannel(DEFAULT_RECV_CHANNEL);
|
||||
mRadio->startListening();
|
||||
|
||||
RESTORE_IRQ;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::sendTicker(void) {
|
||||
uint8_t size = 0;
|
||||
if((mSendCnt % 6) == 0)
|
||||
size = mHoymiles->getTimePacket(mSendBuf, mTimestamp);
|
||||
else if((mSendCnt % 6) == 5)
|
||||
size = mHoymiles->getCmdPacket(mSendBuf, 0x15, 0x81);
|
||||
else if((mSendCnt % 6) == 4)
|
||||
size = mHoymiles->getCmdPacket(mSendBuf, 0x15, 0x80);
|
||||
else if((mSendCnt % 6) == 3)
|
||||
size = mHoymiles->getCmdPacket(mSendBuf, 0x15, 0x83);
|
||||
else if((mSendCnt % 6) == 2)
|
||||
size = mHoymiles->getCmdPacket(mSendBuf, 0x15, 0x82);
|
||||
else if((mSendCnt % 6) == 1)
|
||||
size = mHoymiles->getCmdPacket(mSendBuf, 0x15, 0x84);
|
||||
|
||||
Serial.println("sent packet: #" + String(mSendCnt));
|
||||
dumpBuf(mSendBuf, size);
|
||||
sendPacket(mSendBuf, size);
|
||||
|
||||
mSendCnt++;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::showIndex(void) {
|
||||
String html = index_html;
|
||||
html.replace("{DEVICE}", mDeviceName);
|
||||
html.replace("{VERSION}", mVersion);
|
||||
mWeb->send(200, "text/html", html);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::showSetup(void) {
|
||||
// overrides same method in main.cpp
|
||||
|
||||
String html = setup_html;
|
||||
html.replace("{SSID}", mStationSsid);
|
||||
// PWD will be left at the default value (for protection)
|
||||
// -> the PWD will only be changed if it does not match the placeholder "{PWD}"
|
||||
|
||||
char addr[20] = {0};
|
||||
sprintf(addr, "%02X:%02X:%02X:%02X:%02X:%02X", mHoymiles->mAddrBytes[0], mHoymiles->mAddrBytes[1], mHoymiles->mAddrBytes[2], mHoymiles->mAddrBytes[3], mHoymiles->mAddrBytes[4], mHoymiles->mAddrBytes[5]);
|
||||
html.replace("{HOY_ADDR}", String(addr));
|
||||
|
||||
html.replace("{DEVICE}", String(mDeviceName));
|
||||
html.replace("{VERSION}", String(mVersion));
|
||||
|
||||
mWeb->send(200, "text/html", html);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::showSave(void) {
|
||||
saveValues(true);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::saveValues(bool webSend = true) {
|
||||
Main::saveValues(false); // general configuration
|
||||
|
||||
if(mWeb->args() > 0) {
|
||||
char *p;
|
||||
char addr[20] = {0};
|
||||
uint8_t i = 0;
|
||||
|
||||
memset(mHoymiles->mAddrBytes, 0, 6);
|
||||
mWeb->arg("hoy_addr").toCharArray(addr, 20);
|
||||
|
||||
p = strtok(addr, ":");
|
||||
while(NULL != p) {
|
||||
mHoymiles->mAddrBytes[i++] = strtol(p, NULL, 16);
|
||||
p = strtok(NULL, ":");
|
||||
}
|
||||
|
||||
mEep->write(ADDR_HOY_ADDR, mHoymiles->mAddrBytes, HOY_ADDR_LEN);
|
||||
|
||||
if((mWeb->arg("reboot") == "on"))
|
||||
showReboot();
|
||||
else {
|
||||
mWeb->send(200, "text/html", "<!doctype html><html><head><title>Setup saved</title><meta http-equiv=\"refresh\" content=\"0; URL=/setup\"></head><body>"
|
||||
"<p>saved</p></body></html>");
|
||||
}
|
||||
}
|
||||
else {
|
||||
mWeb->send(200, "text/html", "<!doctype html><html><head><title>Error</title><meta http-equiv=\"refresh\" content=\"3; URL=/setup\"></head><body>"
|
||||
"<p>Error while saving</p></body></html>");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::dumpBuf(uint8_t buf[], uint8_t len) {
|
||||
for(uint8_t i = 0; i < len; i ++) {
|
||||
if((i % 8 == 0) && (i != 0))
|
||||
Serial.println();
|
||||
Serial.print(String(buf[i], HEX) + " ");
|
||||
}
|
||||
Serial.println();
|
||||
}
|
50
tools/esp8266/app.h
Normal file
50
tools/esp8266/app.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
#ifndef __APP_H__
|
||||
#define __APP_H__
|
||||
|
||||
#include <RF24.h>
|
||||
#include <RF24_config.h>
|
||||
|
||||
#include "defines.h"
|
||||
#include "main.h"
|
||||
|
||||
#include "CircularBuffer.h"
|
||||
#include "hoymiles.h"
|
||||
|
||||
|
||||
class app : public Main {
|
||||
public:
|
||||
app();
|
||||
~app();
|
||||
|
||||
void setup(const char *ssid, const char *pwd, uint32_t timeout);
|
||||
void loop(void);
|
||||
void handleIntr(void);
|
||||
|
||||
private:
|
||||
void initRadio(void);
|
||||
void sendPacket(uint8_t data[], uint8_t length);
|
||||
|
||||
void sendTicker(void);
|
||||
|
||||
void showIndex(void);
|
||||
void showSetup(void);
|
||||
void showSave(void);
|
||||
|
||||
void saveValues(bool webSend);
|
||||
void dumpBuf(uint8_t buf[], uint8_t len);
|
||||
|
||||
uint8_t mState;
|
||||
bool mKeyPressed;
|
||||
|
||||
RF24 *mRadio;
|
||||
hoymiles *mHoymiles;
|
||||
CircularBuffer<NRF24_packet_t> *mBufCtrl;
|
||||
NRF24_packet_t mBuffer[PACKET_BUFFER_SIZE];
|
||||
|
||||
|
||||
Ticker *mSendTicker;
|
||||
uint32_t mSendCnt;
|
||||
uint8_t mSendBuf[MAX_RF_PAYLOAD_SIZE];
|
||||
};
|
||||
|
||||
#endif /*__APP_H__*/
|
37
tools/esp8266/defines.h
Normal file
37
tools/esp8266/defines.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef __DEFINES_H__
|
||||
#define __DEFINES_H__
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
// PINOUT
|
||||
//-------------------------------------
|
||||
#define RF24_IRQ_PIN 4
|
||||
#define RF24_CE_PIN 5
|
||||
#define RF24_CS_PIN 15
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
// VERSION
|
||||
//-------------------------------------
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 1
|
||||
#define VERSION_PATCH 7
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
// EEPROM
|
||||
//-------------------------------------
|
||||
#define SSID_LEN 32
|
||||
#define PWD_LEN 64
|
||||
#define DEVNAME_LEN 32
|
||||
|
||||
#define HOY_ADDR_LEN 6
|
||||
|
||||
|
||||
#define ADDR_SSID 0 // start address
|
||||
#define ADDR_PWD ADDR_SSID + SSID_LEN
|
||||
#define ADDR_DEVNAME ADDR_PWD + PWD_LEN
|
||||
|
||||
#define ADDR_HOY_ADDR ADDR_DEVNAME + DEVNAME_LEN
|
||||
|
||||
#endif /*__DEFINES_H__*/
|
130
tools/esp8266/eep.cpp
Normal file
130
tools/esp8266/eep.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
#include "eep.h"
|
||||
#include <EEPROM.h>
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
eep::eep() {
|
||||
EEPROM.begin(500);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
eep::~eep() {
|
||||
EEPROM.end();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::read(uint32_t addr, char *str, uint8_t length) {
|
||||
for(uint8_t i = 0; i < length; i ++) {
|
||||
*(str++) = (char)EEPROM.read(addr++);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::read(uint32_t addr, float *value) {
|
||||
uint8_t *p = (uint8_t*)value;
|
||||
for(uint8_t i = 0; i < 4; i ++) {
|
||||
*(p++) = (uint8_t)EEPROM.read(addr++);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::read(uint32_t addr, bool *value) {
|
||||
uint8_t intVal = 0x00;
|
||||
intVal = EEPROM.read(addr++);
|
||||
*value = (intVal == 0x01);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::read(uint32_t addr, uint8_t *value) {
|
||||
*value = (EEPROM.read(addr++));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::read(uint32_t addr, uint8_t data[], uint8_t length) {
|
||||
for(uint8_t i = 0; i < length; i ++) {
|
||||
*(data++) = EEPROM.read(addr++);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::read(uint32_t addr, uint16_t *value) {
|
||||
*value = (EEPROM.read(addr++) << 8);
|
||||
*value |= (EEPROM.read(addr++));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::read(uint32_t addr, uint32_t *value) {
|
||||
*value = (EEPROM.read(addr++) << 24);
|
||||
*value |= (EEPROM.read(addr++) << 16);
|
||||
*value |= (EEPROM.read(addr++) << 8);
|
||||
*value |= (EEPROM.read(addr++));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::write(uint32_t addr, const char *str, uint8_t length) {
|
||||
for(uint8_t i = 0; i < length; i ++) {
|
||||
EEPROM.write(addr++, str[i]);
|
||||
}
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::write(uint32_t addr, uint8_t data[], uint8_t length) {
|
||||
for(uint8_t i = 0; i < length; i ++) {
|
||||
EEPROM.write(addr++, data[i]);
|
||||
}
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::write(uint32_t addr, float value) {
|
||||
uint8_t *p = (uint8_t*)&value;
|
||||
for(uint8_t i = 0; i < 4; i ++) {
|
||||
EEPROM.write(addr++, p[i]);
|
||||
}
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::write(uint32_t addr, bool value) {
|
||||
uint8_t intVal = (value) ? 0x01 : 0x00;
|
||||
EEPROM.write(addr++, intVal);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::write(uint32_t addr, uint8_t value) {
|
||||
EEPROM.write(addr++, value);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::write(uint32_t addr, uint16_t value) {
|
||||
EEPROM.write(addr++, (value >> 8) & 0xff);
|
||||
EEPROM.write(addr++, (value ) & 0xff);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void eep::write(uint32_t addr, uint32_t value) {
|
||||
EEPROM.write(addr++, (value >> 24) & 0xff);
|
||||
EEPROM.write(addr++, (value >> 16) & 0xff);
|
||||
EEPROM.write(addr++, (value >> 8) & 0xff);
|
||||
EEPROM.write(addr++, (value ) & 0xff);
|
||||
EEPROM.commit();
|
||||
}
|
30
tools/esp8266/eep.h
Normal file
30
tools/esp8266/eep.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#ifndef __EEP_H__
|
||||
#define __EEP_H__
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
class eep {
|
||||
public:
|
||||
eep();
|
||||
~eep();
|
||||
|
||||
void read(uint32_t addr, char *str, uint8_t length);
|
||||
void read(uint32_t addr, float *value);
|
||||
void read(uint32_t addr, bool *value);
|
||||
void read(uint32_t addr, uint8_t *value);
|
||||
void read(uint32_t addr, uint8_t data[], uint8_t length);
|
||||
void read(uint32_t addr, uint16_t *value);
|
||||
void read(uint32_t addr, uint32_t *value);
|
||||
void write(uint32_t addr, const char *str, uint8_t length);
|
||||
void write(uint32_t addr, uint8_t data[], uint8_t length);
|
||||
void write(uint32_t addr, float value);
|
||||
void write(uint32_t addr, bool value);
|
||||
void write(uint32_t addr, uint8_t value);
|
||||
void write(uint32_t addr, uint16_t value);
|
||||
void write(uint32_t addr, uint32_t value);
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
#endif /*__EEP_H__*/
|
228
tools/esp8266/hoymiles.h
Normal file
228
tools/esp8266/hoymiles.h
Normal file
|
@ -0,0 +1,228 @@
|
|||
#ifndef __HOYMILES_H__
|
||||
#define __HOYMILES_H__
|
||||
|
||||
#include <RF24.h>
|
||||
#include <RF24_config.h>
|
||||
|
||||
#define CHANNEL_HOP // switch between channels or use static channel to send
|
||||
|
||||
#define luint64_t long long unsigned int
|
||||
|
||||
#define DEFAULT_RECV_CHANNEL 3
|
||||
#define MAX_RF_PAYLOAD_SIZE 32
|
||||
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
|
||||
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
|
||||
|
||||
#define PACKET_BUFFER_SIZE 30
|
||||
#define CRC8_INIT 0x00
|
||||
#define CRC8_POLY 0x01
|
||||
|
||||
#define CRC16_MODBUS_POLYNOM 0xA001
|
||||
#define CRC16_NRF24_POLYNOM 0x1021
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// MACROS
|
||||
#define CP_U32_LittleEndian(buf, v) ({ \
|
||||
uint8_t *b = buf; \
|
||||
b[0] = ((v >> 24) & 0xff); \
|
||||
b[1] = ((v >> 16) & 0xff); \
|
||||
b[2] = ((v >> 8) & 0xff); \
|
||||
b[3] = ((v ) & 0xff); \
|
||||
})
|
||||
|
||||
#define CP_U32_BigEndian(buf, v) ({ \
|
||||
uint8_t *b = buf; \
|
||||
b[3] = ((v >> 24) & 0xff); \
|
||||
b[2] = ((v >> 16) & 0xff); \
|
||||
b[1] = ((v >> 8) & 0xff); \
|
||||
b[0] = ((v ) & 0xff); \
|
||||
})
|
||||
|
||||
#define BIT_CNT(x) ((x)<<3)
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
union uint64Bytes {
|
||||
uint64_t ull;
|
||||
uint8_t bytes[8];
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t timestamp;
|
||||
uint8_t packetsLost;
|
||||
uint8_t packet[MAX_RF_PAYLOAD_SIZE];
|
||||
} NRF24_packet_t;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
class hoymiles {
|
||||
public:
|
||||
hoymiles() {
|
||||
serial2RadioId();
|
||||
calcDtuIdCrc();
|
||||
|
||||
mChannels = new uint8_t(4);
|
||||
mChannels[0] = 23;
|
||||
mChannels[1] = 40;
|
||||
mChannels[2] = 61;
|
||||
mChannels[3] = 75;
|
||||
mChanIdx = 1;
|
||||
|
||||
mLastCrc = 0x0000;
|
||||
mRptCnt = 0;
|
||||
}
|
||||
|
||||
~hoymiles() {}
|
||||
|
||||
uint8_t getDefaultChannel(void) {
|
||||
return mChannels[1];
|
||||
}
|
||||
|
||||
uint8_t getNxtChannel(void) {
|
||||
if(++mChanIdx >= 4)
|
||||
mChanIdx = 0;
|
||||
return mChannels[mChanIdx];
|
||||
}
|
||||
|
||||
void serial2RadioId(void) {
|
||||
uint64Bytes id;
|
||||
|
||||
id.ull = 0ULL;
|
||||
id.bytes[4] = mAddrBytes[5];
|
||||
id.bytes[3] = mAddrBytes[4];
|
||||
id.bytes[2] = mAddrBytes[3];
|
||||
id.bytes[1] = mAddrBytes[2];
|
||||
id.bytes[0] = 0x01;
|
||||
|
||||
mRadioId = id.ull;
|
||||
}
|
||||
|
||||
uint8_t getTimePacket(uint8_t buf[], uint32_t ts) {
|
||||
memset(buf, 0, MAX_RF_PAYLOAD_SIZE);
|
||||
|
||||
buf[0] = 0x15;
|
||||
CP_U32_BigEndian(&buf[1], (mRadioId >> 8));
|
||||
CP_U32_BigEndian(&buf[5], (DTU_RADIO_ID >> 8));
|
||||
buf[9] = 0x00;
|
||||
buf[10] = 0x0b; // cid
|
||||
buf[11] = 0x00;
|
||||
CP_U32_LittleEndian(&buf[12], ts);
|
||||
buf[19] = 0x05;
|
||||
|
||||
uint16_t crc = crc16(&buf[10], 14);
|
||||
buf[24] = (crc >> 8) & 0xff;
|
||||
buf[25] = (crc ) & 0xff;
|
||||
buf[26] = crc8(buf, 26);
|
||||
|
||||
return 27;
|
||||
}
|
||||
|
||||
uint8_t getCmdPacket(uint8_t buf[], uint8_t mid, uint8_t cmd) {
|
||||
buf[0] = mid;
|
||||
CP_U32_BigEndian(&buf[1], (mRadioId >> 8));
|
||||
CP_U32_BigEndian(&buf[5], (DTU_RADIO_ID >> 8));
|
||||
buf[9] = cmd;
|
||||
buf[10] = crc8(buf, 10);
|
||||
|
||||
return 11;
|
||||
}
|
||||
|
||||
bool checkCrc(uint8_t buf[], uint8_t *len, uint8_t *rptCnt) {
|
||||
*len = (buf[0] >> 2);
|
||||
for (int16_t i = MAX_RF_PAYLOAD_SIZE - 1; i >= 0; i--) {
|
||||
buf[i] = ((buf[i] >> 7) | ((i > 0) ? (buf[i-1] << 1) : 0x00));
|
||||
}
|
||||
uint16_t crc = crc16nrf24(buf, BIT_CNT(*len + 2), 7, mDtuIdCrc);
|
||||
|
||||
bool valid = (crc == ((buf[*len+2] << 8) | (buf[*len+3])));
|
||||
|
||||
if(valid) {
|
||||
if(mLastCrc == crc)
|
||||
*rptCnt = (++mRptCnt);
|
||||
else {
|
||||
mRptCnt = 0;
|
||||
*rptCnt = 0;
|
||||
mLastCrc = crc;
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
void dumpBuf(const char *info, uint8_t buf[], uint8_t len) {
|
||||
Serial.print(String(info));
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
Serial.print(buf[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
uint8_t mAddrBytes[6];
|
||||
luint64_t mRadioId;
|
||||
|
||||
private:
|
||||
void calcDtuIdCrc(void) {
|
||||
uint64_t addr = DTU_RADIO_ID;
|
||||
uint8_t dtuAddr[5];
|
||||
for(int8_t i = 4; i >= 0; i--) {
|
||||
dtuAddr[i] = addr;
|
||||
addr >>= 8;
|
||||
}
|
||||
mDtuIdCrc = crc16nrf24(dtuAddr, BIT_CNT(5));
|
||||
}
|
||||
|
||||
uint8_t crc8(uint8_t buf[], uint8_t len) {
|
||||
uint8_t crc = CRC8_INIT;
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
crc ^= buf[i];
|
||||
for(uint8_t b = 0; b < 8; b ++) {
|
||||
crc = (crc << 1) ^ ((crc & 0x80) ? CRC8_POLY : 0x00);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t crc16(uint8_t buf[], uint8_t len) {
|
||||
uint16_t crc = 0xffff;
|
||||
uint8_t lsb;
|
||||
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
crc = crc ^ buf[i];
|
||||
for(int8_t b = 7; b >= 0; b--) {
|
||||
lsb = (crc & 0x0001);
|
||||
if(lsb == 0x01)
|
||||
crc--;
|
||||
crc = crc >> 1;
|
||||
if(lsb == 0x01)
|
||||
crc = crc ^ CRC16_MODBUS_POLYNOM;
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t crc16nrf24(uint8_t buf[], uint16_t lenBits, uint16_t startBit = 0, uint16_t crcIn = 0xffff) {
|
||||
uint16_t crc = crcIn;
|
||||
uint8_t idx, val = buf[(startBit >> 3)];
|
||||
|
||||
for(uint16_t bit = startBit; bit < lenBits; bit ++) {
|
||||
idx = bit & 0x07;
|
||||
if(0 == idx)
|
||||
val = buf[(bit >> 3)];
|
||||
crc ^= 0x8000 & (val << (8 + idx));
|
||||
crc = (crc & 0x8000) ? ((crc << 1) ^ CRC16_NRF24_POLYNOM) : (crc << 1);
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint8_t *mChannels;
|
||||
uint8_t mChanIdx;
|
||||
uint16_t mDtuIdCrc;
|
||||
uint16_t mLastCrc;
|
||||
uint8_t mRptCnt;
|
||||
};
|
||||
|
||||
#endif /*__HOYMILES_H__*/
|
4
tools/esp8266/html/conv.bat
Normal file
4
tools/esp8266/html/conv.bat
Normal file
|
@ -0,0 +1,4 @@
|
|||
..\tools\fileConv.exe index.html h\index_html.h index_html
|
||||
..\tools\fileConv.exe setup.html h\setup_html.h setup_html
|
||||
..\tools\fileConv.exe style.css h\style_css.h style_css
|
||||
pause
|
1
tools/esp8266/html/h/index_html.h
Normal file
1
tools/esp8266/html/h/index_html.h
Normal file
|
@ -0,0 +1 @@
|
|||
String index_html = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><script type=\"text/javascript\"> window.setInterval(\"getAjax('/uptime', 'uptime')\", 1000); window.setInterval(\"getAjax('/time', 'time')\", 1000); function getAjax(url, resid) { var http = null; http = new XMLHttpRequest(); if(http != null) { http.open(\"GET\", url, true); http.onreadystatechange = print; http.send(null); } function print() { if(http.readyState == 4) { document.getElementById(resid).innerHTML = http.responseText; } } } </script></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><p><a href=\"/update\">Update</a><br/><br/><a href=\"/setup\">Setup</a><br/><a href=\"/reboot\">Reboot</a></p><p><span class=\"des\">Uptime: </span><span id=\"uptime\"></span></p><p><span class=\"des\">Time: </span><span id=\"time\"></span></p></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"right\">AHOY :: {VERSION}</p></div></body></html>";
|
1
tools/esp8266/html/h/setup_html.h
Normal file
1
tools/esp8266/html/h/setup_html.h
Normal file
|
@ -0,0 +1 @@
|
|||
String setup_html = "<!doctype html><html><head><title>Setup - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"></head><body><h1>Setup</h1><div id=\"setup\" class=\"content\"><div id=\"content\"><p> Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information. </p><form method=\"post\" action=\"/save\"><p class=\"des\">WiFi</p><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"ssid\" value=\"{SSID}\" required/><span class=\"floating_label\">SSID</span></div><div class=\"inputWrp\"><input type=\"password\" class=\"inputText\" name=\"pwd\" value=\"{PWD}\" required/><span class=\"floating_label\">PASSWORD</span></div><p class=\"des\">Device Host Name</p><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"device\" value=\"{DEVICE}\" required/><span class=\"floating_label\">DEVICE NAME</span></div><p class=\"des\">General</p><div class=\"inputWrp\"><input type=\"text\" class=\"inputText\" name=\"hoy_addr\" value=\"{HOY_ADDR}\" required/><span class=\"floating_label\">HOYMILES ADDRESS (eg. 11:22:33:44:55:66)</span></div><input type=\"checkbox\" class=\"cb\" name=\"reboot\"/><label for=\"reboot\">Reboot device after successful save</label><input type=\"submit\" value=\"save\" class=\"button\" /></form></div></div><div id=\"footer\"><p class=\"left\"><a href=\"/\">Home</a></p><p class=\"left\"><a href=\"/update\">Update Firmware</a></p><p class=\"right\">AHOY - {VERSION}</p></div></body></html>";
|
1
tools/esp8266/html/h/style_css.h
Normal file
1
tools/esp8266/html/h/style_css.h
Normal file
|
@ -0,0 +1 @@
|
|||
String style_css = "h1 { margin: 0; padding: 20pt; font-size: 22pt; color: #fff; background-color: #006ec0; display: block; text-transform: uppercase; } html, body { font-family: Arial; margin: 0; padding: 0; } p { text-align: justify; font-size: 13pt; } .des { font-size: 14pt; color: #006ec0; padding-bottom: 0px !important; } .fw { width: 60px; display: block; float: left; } .color { width: 50px; height: 50px; border: 1px solid #ccc; } .range { width: 300px; } a:link, a:visited { text-decoration: none; font-size: 13pt; color: #006ec0; } a:hover, a:focus { color: #f00; } #content { padding: 15px 15px 60px 15px; } #footer { position: fixed; bottom: 0px; height: 45px; background-color: #006ec0; width: 100%; } #footer p { color: #fff; padding-left: 20px; padding-right: 20px; font-size: 10pt !important; } #footer a { color: #fff; } #footer a:hover { color: #f00; } div.content { background-color: #fff; padding-bottom: 65px; overflow: hidden; } span.warn { display: inline-block; padding-left: 20px; color: #ff9900; font-style: italic; } input { padding: 10px; font-size: 13pt; } input.button { background-color: #006ec0; color: #fff; border: 0px; float: right; text-transform: uppercase; } input.cb { margin-bottom: 20px; } label { font-size: 14pt; } .left { float: left; } .right { float: right; } .inputWrp { position: relative; } .inputWrp .inputText { height: 35px; width: 90%; margin-bottom: 20px; border: 1px solid #ccc; border-top: none; border-right: none; } .inputWrp .floating_label { position: absolute; pointer-events: none; top: 20px; left: 10px; transition: 0.2s ease all; } .inputWrp input:focus ~ .floating_label, .inputWrp input:not(:focus):valid ~ .floating_label { top: 0px; left: 20px; font-size: 10px; color: blue; opacity: 1; } ";
|
45
tools/esp8266/html/index.html
Normal file
45
tools/esp8266/html/index.html
Normal file
|
@ -0,0 +1,45 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Index - {DEVICE}</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script type="text/javascript">
|
||||
window.setInterval("getAjax('/uptime', 'uptime')", 1000);
|
||||
window.setInterval("getAjax('/time', 'time')", 1000);
|
||||
|
||||
function getAjax(url, resid) {
|
||||
var http = null;
|
||||
http = new XMLHttpRequest();
|
||||
if(http != null) {
|
||||
http.open("GET", url, true);
|
||||
http.onreadystatechange = print;
|
||||
http.send(null);
|
||||
}
|
||||
|
||||
function print() {
|
||||
if(http.readyState == 4) {
|
||||
document.getElementById(resid).innerHTML = http.responseText;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>AHOY - {DEVICE}</h1>
|
||||
<div id="content" class="content">
|
||||
<p>
|
||||
<a href="/update">Update</a><br/>
|
||||
<br/>
|
||||
<a href="/setup">Setup</a><br/>
|
||||
<a href="/reboot">Reboot</a>
|
||||
</p>
|
||||
<p><span class="des">Uptime: </span><span id="uptime"></span></p>
|
||||
<p><span class="des">Time: </span><span id="time"></span></p>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<p class="left">© 2022</p>
|
||||
<p class="right">AHOY :: {VERSION}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
51
tools/esp8266/html/setup.html
Normal file
51
tools/esp8266/html/setup.html
Normal file
|
@ -0,0 +1,51 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Setup - {DEVICE}</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Setup</h1>
|
||||
<div id="setup" class="content">
|
||||
<div id="content">
|
||||
<p>
|
||||
Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.
|
||||
</p>
|
||||
<form method="post" action="/save">
|
||||
<p class="des">WiFi</p>
|
||||
<div class="inputWrp">
|
||||
<input type="text" class="inputText" name="ssid" value="{SSID}" required/>
|
||||
<span class="floating_label">SSID</span>
|
||||
</div>
|
||||
<div class="inputWrp">
|
||||
<input type="password" class="inputText" name="pwd" value="{PWD}" required/>
|
||||
<span class="floating_label">PASSWORD</span>
|
||||
</div>
|
||||
<p class="des">Device Host Name</p>
|
||||
<div class="inputWrp">
|
||||
<input type="text" class="inputText" name="device" value="{DEVICE}" required/>
|
||||
<span class="floating_label">DEVICE NAME</span>
|
||||
</div>
|
||||
|
||||
<p class="des">General</p>
|
||||
|
||||
<div class="inputWrp">
|
||||
<input type="text" class="inputText" name="hoy_addr" value="{HOY_ADDR}" required/>
|
||||
<span class="floating_label">HOYMILES ADDRESS (eg. 11:22:33:44:55:66)</span>
|
||||
</div>
|
||||
<input type="checkbox" class="cb" name="reboot"/>
|
||||
<label for="reboot">Reboot device after successful save</label>
|
||||
|
||||
<input type="submit" value="save" class="button" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p class="left"><a href="/">Home</a></p>
|
||||
<p class="left"><a href="/update">Update Firmware</a></p>
|
||||
<p class="right">AHOY - {VERSION}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
151
tools/esp8266/html/style.css
Normal file
151
tools/esp8266/html/style.css
Normal file
|
@ -0,0 +1,151 @@
|
|||
h1 {
|
||||
margin: 0;
|
||||
padding: 20pt;
|
||||
font-size: 22pt;
|
||||
color: #fff;
|
||||
background-color: #006ec0;
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-family: Arial;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
.des {
|
||||
font-size: 14pt;
|
||||
color: #006ec0;
|
||||
padding-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.fw {
|
||||
width: 60px;
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.color {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.range {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
a:link, a:visited {
|
||||
text-decoration: none;
|
||||
font-size: 13pt;
|
||||
color: #006ec0;
|
||||
}
|
||||
|
||||
a:hover, a:focus {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
#content {
|
||||
padding: 15px 15px 60px 15px;
|
||||
}
|
||||
|
||||
#footer {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
height: 45px;
|
||||
background-color: #006ec0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#footer p {
|
||||
color: #fff;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
font-size: 10pt !important;
|
||||
}
|
||||
|
||||
#footer a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#footer a:hover {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
div.content {
|
||||
background-color: #fff;
|
||||
padding-bottom: 65px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
span.warn {
|
||||
display: inline-block;
|
||||
padding-left: 20px;
|
||||
color: #ff9900;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
input.button {
|
||||
background-color: #006ec0;
|
||||
color: #fff;
|
||||
border: 0px;
|
||||
float: right;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
input.cb {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.inputWrp {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inputWrp .inputText {
|
||||
height: 35px;
|
||||
width: 90%;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.inputWrp .floating_label {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 20px;
|
||||
left: 10px;
|
||||
transition: 0.2s ease all;
|
||||
}
|
||||
|
||||
.inputWrp input:focus ~ .floating_label,
|
||||
.inputWrp input:not(:focus):valid ~ .floating_label {
|
||||
top: 0px;
|
||||
left: 20px;
|
||||
font-size: 10px;
|
||||
color: blue;
|
||||
opacity: 1;
|
||||
}
|
340
tools/esp8266/main.cpp
Normal file
340
tools/esp8266/main.cpp
Normal file
|
@ -0,0 +1,340 @@
|
|||
#include "main.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "html/h/style_css.h"
|
||||
#include "html/h/setup_html.h"
|
||||
|
||||
Main::Main(void) {
|
||||
mDns = new DNSServer();
|
||||
mWeb = new ESP8266WebServer(80);
|
||||
mUpdater = new ESP8266HTTPUpdateServer();
|
||||
mUdp = new WiFiUDP();
|
||||
|
||||
mApActive = true;
|
||||
|
||||
snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
|
||||
|
||||
memset(&mDeviceName, 0, DEVNAME_LEN);
|
||||
|
||||
mEep = new eep();
|
||||
Serial.begin(115200);
|
||||
|
||||
mUptimeSecs = 0;
|
||||
mUptimeTicker = new Ticker();
|
||||
mUptimeTicker->attach(1, std::bind(&Main::uptimeTicker, this));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::setup(const char *ssid, const char *pwd, uint32_t timeout) {
|
||||
bool startAp = mApActive;
|
||||
|
||||
mWeb->on("/setup", std::bind(&Main::showSetup, this));
|
||||
mWeb->on("/save", std::bind(&Main::showSave, this));
|
||||
mWeb->on("/uptime", std::bind(&Main::showUptime, this));
|
||||
mWeb->on("/time", std::bind(&Main::showTime, this));
|
||||
mWeb->on("/style.css", std::bind(&Main::showCss, this));
|
||||
mWeb->on("/reboot", std::bind(&Main::showReboot, this));
|
||||
mWeb->onNotFound (std::bind(&Main::showNotFound, this));
|
||||
|
||||
startAp = getConfig();
|
||||
if(String(mDeviceName) != "")
|
||||
WiFi.hostname(mDeviceName);
|
||||
|
||||
if(false == startAp)
|
||||
startAp = setupStation(timeout);
|
||||
|
||||
if(true == startAp) {
|
||||
if(strlen(pwd) < 8)
|
||||
Serial.println("password must be at least 8 characters long");
|
||||
setupAp(ssid, pwd);
|
||||
}
|
||||
|
||||
mUpdater->setup(mWeb);
|
||||
|
||||
mApActive = startAp;
|
||||
|
||||
mTimestamp = getNtpTime();
|
||||
//Serial.println("[NTP]: " + getDateTimeStr(getNtpTime()));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::loop(void) {
|
||||
if(mApActive)
|
||||
mDns->processNextRequest();
|
||||
mWeb->handleClient();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool Main::getConfig(void) {
|
||||
bool mApActive = false;
|
||||
|
||||
mEep->read(ADDR_SSID, mStationSsid, SSID_LEN);
|
||||
mEep->read(ADDR_PWD, mStationPwd, PWD_LEN);
|
||||
mEep->read(ADDR_DEVNAME, mDeviceName, DEVNAME_LEN);
|
||||
|
||||
if(mStationSsid[0] == 0xff) { // empty memory
|
||||
mApActive = true;
|
||||
memset(mStationSsid, 0, SSID_LEN);
|
||||
}
|
||||
if(mStationPwd[0] == 0xff)
|
||||
memset(mStationPwd, 0, PWD_LEN);
|
||||
if(mDeviceName[0] == 0xff)
|
||||
memset(mDeviceName, 0, DEVNAME_LEN);
|
||||
|
||||
return mApActive;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::setupAp(const char *ssid, const char *pwd) {
|
||||
IPAddress apIp(192, 168, 1, 1);
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAPConfig(apIp, apIp, IPAddress(255, 255, 255, 0));
|
||||
WiFi.softAP(ssid, pwd);
|
||||
|
||||
mDns->start(mDnsPort, "*", apIp);
|
||||
|
||||
mWeb->onNotFound([&]() {
|
||||
showSetup();
|
||||
});
|
||||
mWeb->on("/", std::bind(&Main::showSetup, this));
|
||||
|
||||
mWeb->begin();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool Main::setupStation(uint32_t timeout) {
|
||||
int32_t cnt = timeout * 10;
|
||||
bool startAp = false;
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(mStationSsid, mStationPwd);
|
||||
|
||||
delay(5000);
|
||||
Serial.println("wait for network");
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(100);
|
||||
if(cnt % 100 == 0)
|
||||
Serial.println(".");
|
||||
else
|
||||
Serial.print(".");
|
||||
|
||||
if(timeout > 0) { // limit == 0 -> no limit
|
||||
if(--cnt <= 0) {
|
||||
startAp = true;
|
||||
WiFi.disconnect();
|
||||
delay(100);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Serial.println(".");
|
||||
|
||||
if(false == startAp) {
|
||||
mWeb->begin();
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
|
||||
return startAp;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::showSetup(void) {
|
||||
String html = setup_html;
|
||||
html.replace("{SSID}", mStationSsid);
|
||||
// PWD will be left at the default value (for protection)
|
||||
// -> the PWD will only be changed if it does not match the default "{PWD}"
|
||||
html.replace("{DEVICE}", String(mDeviceName));
|
||||
html.replace("{VERSION}", String(mVersion));
|
||||
|
||||
mWeb->send(200, "text/html", html);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::showCss(void) {
|
||||
mWeb->send(200, "text/css", style_css);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::showSave(void) {
|
||||
saveValues(true);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::saveValues(bool webSend = true) {
|
||||
if(mWeb->args() > 0) {
|
||||
if(mWeb->arg("ssid") != "") {
|
||||
memset(mStationSsid, 0, SSID_LEN);
|
||||
mWeb->arg("ssid").toCharArray(mStationSsid, SSID_LEN);
|
||||
mEep->write(ADDR_SSID, mStationSsid, SSID_LEN);
|
||||
|
||||
if(mWeb->arg("pwd") != "{PWD}") {
|
||||
memset(mStationPwd, 0, PWD_LEN);
|
||||
mWeb->arg("pwd").toCharArray(mStationPwd, PWD_LEN);
|
||||
mEep->write(ADDR_PWD, mStationPwd, PWD_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
memset(mDeviceName, 0, DEVNAME_LEN);
|
||||
mWeb->arg("device").toCharArray(mDeviceName, DEVNAME_LEN);
|
||||
mEep->write(ADDR_DEVNAME, mDeviceName, DEVNAME_LEN);
|
||||
|
||||
|
||||
if(webSend) {
|
||||
if(mWeb->arg("reboot") == "on")
|
||||
showReboot();
|
||||
else
|
||||
mWeb->send(200, "text/html", "<!doctype html><html><head><title>Setup saved</title><meta http-equiv=\"refresh\" content=\"0; URL=/setup\"></head><body>"
|
||||
"<p>saved</p></body></html>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::showUptime(void) {
|
||||
char time[20] = {0};
|
||||
|
||||
int upTimeSc = uint32_t((mUptimeSecs) % 60);
|
||||
int upTimeMn = uint32_t((mUptimeSecs / (60)) % 60);
|
||||
int upTimeHr = uint32_t((mUptimeSecs / (60 * 60)) % 24);
|
||||
int upTimeDy = uint32_t((mUptimeSecs / (60 * 60 * 24)) % 365);
|
||||
|
||||
snprintf(time, 20, "%d Tage, %02d:%02d:%02d", upTimeDy, upTimeHr, upTimeMn, upTimeSc);
|
||||
|
||||
mWeb->send(200, "text/plain", String(time));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::showTime(void) {
|
||||
mWeb->send(200, "text/plain", getDateTimeStr(mTimestamp));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::showNotFound(void) {
|
||||
String msg = "File Not Found\n\n";
|
||||
msg += "URI: ";
|
||||
msg += mWeb->uri();
|
||||
msg += "\nMethod: ";
|
||||
msg += ( mWeb->method() == HTTP_GET ) ? "GET" : "POST";
|
||||
msg += "\nArguments: ";
|
||||
msg += mWeb->args();
|
||||
msg += "\n";
|
||||
|
||||
for(uint8_t i = 0; i < mWeb->args(); i++ ) {
|
||||
msg += " " + mWeb->argName(i) + ": " + mWeb->arg(i) + "\n";
|
||||
}
|
||||
|
||||
mWeb->send(404, "text/plain", msg);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::showReboot(void) {
|
||||
mWeb->send(200, "text/html", "<!doctype html><html><head><title>Rebooting ...</title><meta http-equiv=\"refresh\" content=\"10; URL=/\"></head><body>rebooting ... auto reload after 10s</body></html>");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::uptimeTicker(void) {
|
||||
mUptimeSecs++;
|
||||
mTimestamp++;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
time_t Main::getNtpTime(void) {
|
||||
time_t date = 0;
|
||||
IPAddress timeServer;
|
||||
uint8_t buf[NTP_PACKET_SIZE];
|
||||
uint8_t retry = 0;
|
||||
|
||||
WiFi.hostByName (TIMESERVER_NAME, timeServer);
|
||||
mUdp->begin(TIME_LOCAL_PORT);
|
||||
|
||||
|
||||
sendNTPpacket(timeServer);
|
||||
|
||||
while(retry++ < 5) {
|
||||
int wait = 150;
|
||||
while(--wait) {
|
||||
if(NTP_PACKET_SIZE <= mUdp->parsePacket()) {
|
||||
uint64_t secsSince1900;
|
||||
mUdp->read(buf, NTP_PACKET_SIZE);
|
||||
secsSince1900 = (buf[40] << 24);
|
||||
secsSince1900 |= (buf[41] << 16);
|
||||
secsSince1900 |= (buf[42] << 8);
|
||||
secsSince1900 |= (buf[43] );
|
||||
|
||||
date = secsSince1900 - 2208988800UL; // UTC time
|
||||
date += (TIMEZONE + offsetDayLightSaving(date)) * 3600;
|
||||
break;
|
||||
}
|
||||
else
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void Main::sendNTPpacket(IPAddress& address) {
|
||||
uint8_t buf[NTP_PACKET_SIZE] = {0};
|
||||
|
||||
buf[0] = B11100011; // LI, Version, Mode
|
||||
buf[1] = 0; // Stratum
|
||||
buf[2] = 6; // Max Interval between messages in seconds
|
||||
buf[3] = 0xEC; // Clock Precision
|
||||
// bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset
|
||||
buf[12] = 49; // four-byte reference ID identifying
|
||||
buf[13] = 0x4E;
|
||||
buf[14] = 49;
|
||||
buf[15] = 52;
|
||||
|
||||
mUdp->beginPacket(address, 123); // NTP request, port 123
|
||||
mUdp->write(buf, NTP_PACKET_SIZE);
|
||||
mUdp->endPacket();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
String Main::getDateTimeStr(time_t t) {
|
||||
char str[20] = {0};
|
||||
sprintf(str, "%04d-%02d-%02d+%02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t));
|
||||
return String(str);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC
|
||||
// from: https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
|
||||
time_t Main::offsetDayLightSaving (uint32_t local_t) {
|
||||
int m = month (local_t);
|
||||
if(m < 3 || m > 10) return 0; // no DSL in Jan, Feb, Nov, Dez
|
||||
if(m > 3 && m < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep
|
||||
int y = year (local_t);
|
||||
int h = hour (local_t);
|
||||
int hToday = (h + 24 * day(local_t));
|
||||
if((m == 3 && hToday >= (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 4) % 7)))
|
||||
|| (m == 10 && hToday < (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 1) % 7))) )
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
79
tools/esp8266/main.h
Normal file
79
tools/esp8266/main.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
#ifndef __MAIN_H__
|
||||
#define __MAIN_H__
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <DNSServer.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <Ticker.h>
|
||||
|
||||
#include <ESP8266HTTPUpdateServer.h>
|
||||
|
||||
// NTP
|
||||
#include <WiFiUdp.h>
|
||||
#include <TimeLib.h>
|
||||
|
||||
#include "eep.h"
|
||||
#include "defines.h"
|
||||
|
||||
|
||||
const byte mDnsPort = 53;
|
||||
|
||||
/* TIMESERVER CONFIG */
|
||||
#define TIMESERVER_NAME "pool.ntp.org"
|
||||
#define TIME_LOCAL_PORT 8888
|
||||
#define NTP_PACKET_SIZE 48
|
||||
#define TIMEZONE 1 // Central European time +1
|
||||
|
||||
class Main {
|
||||
public:
|
||||
Main(void);
|
||||
virtual void setup(const char *ssid, const char *pwd, uint32_t timeout);
|
||||
virtual void loop();
|
||||
String getDateTimeStr (time_t t);
|
||||
|
||||
|
||||
protected:
|
||||
void showReboot(void);
|
||||
virtual void saveValues(bool webSend);
|
||||
|
||||
char mStationSsid[SSID_LEN];
|
||||
char mStationPwd[PWD_LEN];
|
||||
bool mLinkLedActive;
|
||||
bool mApActive;
|
||||
ESP8266WebServer *mWeb;
|
||||
char mVersion[9];
|
||||
char mDeviceName[DEVNAME_LEN];
|
||||
eep *mEep;
|
||||
uint32_t mTimestamp;
|
||||
|
||||
|
||||
private:
|
||||
bool getConfig(void);
|
||||
void setupAp(const char *ssid, const char *pwd);
|
||||
bool setupStation(uint32_t timeout);
|
||||
|
||||
void showNotFound(void);
|
||||
virtual void showSetup(void);
|
||||
virtual void showSave(void);
|
||||
void showUptime(void);
|
||||
void showTime(void);
|
||||
void showCss(void);
|
||||
void uptimeTicker(void);
|
||||
|
||||
|
||||
time_t getNtpTime(void);
|
||||
void sendNTPpacket(IPAddress& address);
|
||||
time_t offsetDayLightSaving (uint32_t local_t);
|
||||
|
||||
Ticker *mUptimeTicker;
|
||||
uint32_t mUptimeSecs;
|
||||
|
||||
DNSServer *mDns;
|
||||
ESP8266HTTPUpdateServer *mUpdater;
|
||||
|
||||
WiFiUDP *mUdp; // for time server
|
||||
};
|
||||
|
||||
#endif /*__MAIN_H__*/
|
BIN
tools/esp8266/tools/fileConv.exe
Normal file
BIN
tools/esp8266/tools/fileConv.exe
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue