zeroconfig multiAP over UDP

This commit is contained in:
Nic Limper
2023-04-26 17:17:02 +02:00
parent cb9b9f164a
commit 0edb93be1a
9 changed files with 196 additions and 62 deletions

View File

@@ -8,7 +8,7 @@ const WAKEUP_REASON_FIRSTBOOT = 0xFC;
const WAKEUP_REASON_NETWORK_SCAN = 0xFD;
const WAKEUP_REASON_WDT_RESET = 0xFE;
const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url", "Weather forecast", "RSS feed", "QR code", "Calendar"];
const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url", "Weather forecast", "RSS feed", "QR code", "Calendar", "Remote AP"];
const models = ["1.54\" 152x152px", "2.9\" 296x128px", "4.2\" 400x300px"];
const displaySizeLookup = { 0: [152, 152], 1: [128, 296], 2: [400, 300] };
const colorTable = { 0: [255, 255, 255], 1: [0, 0, 0], 2: [255, 0, 0], 3: [255, 0, 0] };
@@ -25,6 +25,7 @@ contentModeOptions[8] = ["location"];
contentModeOptions[9] = ["title", "url", "interval"];
contentModeOptions[10] = ["title", "qr-content"];
contentModeOptions[11] = ["title", "apps_script_url", "interval"];
contentModeOptions[12] = [];
const imageQueue = [];
let isProcessing = false;
@@ -67,7 +68,6 @@ function connect() {
if (msg.sys) {
$('#sysinfo').innerHTML = 'free heap: ' + msg.sys.heap + ' bytes ┇ db size: ' + msg.sys.dbsize + ' bytes ┇ db record count: ' + msg.sys.recordcount + ' ┇ littlefs free: ' + msg.sys.littlefsfree + ' bytes';
servertimediff = (Date.now() / 1000) - msg.sys.currtime;
console.log("timediff: " + servertimediff);
}
});
@@ -89,12 +89,15 @@ function processTags(tagArray) {
div.dataset.mac = tagmac;
div.dataset.hwtype = -1;
$('#taglist').appendChild(div);
$('#tag' + tagmac + ' .mac').innerHTML = tagmac;
}
div.style.display = 'block';
if (element.isexternal) {
$('#tag' + tagmac + ' .mac').innerHTML = tagmac + " via ext AP";
} else {
$('#tag' + tagmac + ' .mac').innerHTML = tagmac;
}
let alias = element.alias;
if (!alias) alias = tagmac;
$('#tag' + tagmac + ' .alias').innerHTML = alias;
@@ -227,7 +230,6 @@ $('#taglist').addEventListener("click", (event) => {
fetch("/get_db?mac=" + mac)
.then(response => response.json())
.then(data => {
console.log(data);
var tagdata = data.tags[0];
$('#cfgalias').value = tagdata.alias;
$('#cfgcontent').value = tagdata.contentMode;
@@ -244,22 +246,24 @@ $('#cfgsave').onclick = function () {
let contentMode = $('#cfgcontent').value;
let extraoptions = contentModeOptions[contentMode];
let obj={};
extraoptions.forEach(element => {
obj[element] = $('#opt' + element).value;
});
if (contentMode) {
extraoptions.forEach(element => {
obj[element] = $('#opt' + element).value;
});
let formData = new FormData();
formData.append("mac", $('#cfgmac').dataset.mac);
formData.append("alias", $('#cfgalias').value);
formData.append("contentmode", contentMode);
formData.append("modecfgjson", JSON.stringify(obj));
fetch("/save_cfg", {
method: "POST",
body: formData
})
.then(response => response.text())
.then(data => showMessage(data))
.catch(error => showMessage('Error: ' + error));
let formData = new FormData();
formData.append("mac", $('#cfgmac').dataset.mac);
formData.append("alias", $('#cfgalias').value);
formData.append("contentmode", contentMode);
formData.append("modecfgjson", JSON.stringify(obj));
fetch("/save_cfg", {
method: "POST",
body: formData
})
.then(response => response.text())
.then(data => showMessage(data))
.catch(error => showMessage('Error: ' + error));
}
$('#configbox').style.display = 'none';
}
@@ -296,20 +300,21 @@ function contentselected() {
if ($('#cfgcontent').dataset.json && ($('#cfgcontent').dataset.json!="null")) {
obj = JSON.parse($('#cfgcontent').dataset.json);
}
console.log(obj);
extraoptions.forEach(element => {
var label = document.createElement("label");
label.innerHTML = element;
label.setAttribute("for", 'opt' + element);
var input = document.createElement("input");
input.type = "text";
input.id = 'opt' + element;
if (obj[element]) input.value = obj[element];
var p = document.createElement("p");
p.appendChild(label);
p.appendChild(input);
$('#customoptions').appendChild(p);
});
if (contentMode) {
extraoptions.forEach(element => {
var label = document.createElement("label");
label.innerHTML = element;
label.setAttribute("for", 'opt' + element);
var input = document.createElement("input");
input.type = "text";
input.id = 'opt' + element;
if (obj[element]) input.value = obj[element];
var p = document.createElement("p");
p.appendChild(label);
p.appendChild(input);
$('#customoptions').appendChild(p);
});
}
}
function showMessage(message,iserr) {

View File

@@ -6,6 +6,7 @@ extern bool checkCRC(void* p, uint8_t len);
extern void processBlockRequest(struct espBlockRequest* br);
extern void prepareIdleReq(uint8_t* dst, uint16_t nextCheckin);
extern bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t nextCheckin);
extern void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP);
extern void processXferComplete(struct espXferComplete* xfc);
extern void processXferTimeout(struct espXferComplete* xfc);
extern void processDataReq(struct espAvailDataReq* adr);

View File

@@ -18,7 +18,7 @@
class tagRecord {
public:
uint16_t nextCheckinpending;
tagRecord() : mac{0}, alias(""), lastseen(0), nextupdate(0), contentMode(0), pending(false), md5{0}, md5pending{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0) {}
tagRecord() : mac{0}, alias(""), lastseen(0), nextupdate(0), contentMode(0), pending(false), md5{0}, md5pending{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0), isExternal(false) {}
uint8_t mac[6];
String alias;
@@ -38,6 +38,7 @@ class tagRecord {
uint8_t wakeupReason;
uint8_t capabilities;
uint32_t lastfullupdate;
bool isExternal;
static tagRecord* findByMAC(uint8_t mac[6]);
};

View File

@@ -2,17 +2,24 @@
#include "AsyncUDP.h"
#ifndef defudpcomm
#define defudpcomm
class UDPcomm {
public:
UDPcomm();
~UDPcomm();
void init();
void send(uint8_t* output);
void processDataReq(struct espAvailDataReq* eadr);
private:
void netProcessDataReq(struct espAvailDataReq* eadr);
void netProcessXferComplete(struct espXferComplete* xfc);
void netProcessXferTimeout(struct espXferComplete* xfc);
void netSendDataAvail(struct pendingData* pending);
private:
AsyncUDP udp;
void processPacket(AsyncUDPPacket packet);
};
#endif
void init_udp();

View File

@@ -32,6 +32,7 @@ enum contentModes {
RSSFeed,
QRcode,
Calendar,
RemoteAP,
};
void contentRunner() {
@@ -99,8 +100,8 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
} else {
wsErr("Error accessing " + filename);
}
taginfo->nextupdate = 3216153600;
}
taginfo->nextupdate = 3216153600;
break;
case Today:
@@ -208,6 +209,11 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
taginfo->nextupdate = now + 300;
}
break;
case RemoteAP:
taginfo->nextupdate = 3216153600;
break;
}
taginfo->modeConfigJson = doc.as<String>();

View File

@@ -1,6 +1,9 @@
#include "newproto.h"
#include <Arduino.h>
#include <FS.h>
#include <HTTPClient.h>
#include <LittleFS.h>
#include <MD5Builder.h>
#include <makeimage.h>
#include <time.h>
@@ -11,9 +14,11 @@
#include "serial.h"
#include "settings.h"
#include "tag_db.h"
#include "udp.h"
#include "web.h"
extern uint16_t sendBlock(const void* data, const uint16_t len);
extern UDPcomm udpsync;
void addCRC(void* p, uint8_t len) {
uint8_t total = 0;
@@ -138,7 +143,7 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t
LittleFS.rename(*filename, dst_path);
*filename = String(dst_path);
wsLog("new image pending: " + String(dst_path));
wsLog("new image: " + String(dst_path));
time_t now;
time(&now);
taginfo->pending = true;
@@ -158,7 +163,11 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t
pending.availdatainfo.dataTypeArgument = lut;
pending.availdatainfo.nextCheckIn = nextCheckin;
pending.attemptsLeft = attempts;
sendDataAvail(&pending);
if (taginfo->isExternal == false) {
sendDataAvail(&pending);
} else {
udpsync.netSendDataAvail(&pending);
}
// data for the cache on the esp32; needs to hold the data longer than the maximum timeout on the AP
pendingdata* pendinginfo = nullptr;
@@ -175,6 +184,72 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t
return true;
}
void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
uint8_t src[8];
*((uint64_t*)src) = swap64(*((uint64_t*)pending->targetMac));
uint8_t mac[6];
memcpy(mac, src + 2, sizeof(mac));
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(mac);
if (taginfo == nullptr) {
return;
}
if (taginfo->isExternal == false) {
LittleFS.begin();
char buffer[64];
sprintf(buffer, "%02X%02X%02X%02X%02X%02X\0", src[2], src[3], src[4], src[5], src[6], src[7]);
String filename = "/current/" + (String)buffer + ".pending";
String imageUrl = "http://" + remoteIP.toString() + filename;
wsLog("GET " + imageUrl);
HTTPClient http;
http.begin(imageUrl);
int httpCode = http.GET();
if (httpCode == 200) {
File file = LittleFS.open(filename, "w");
http.writeToStream(&file);
file.close();
}
http.end();
fs::File file = LittleFS.open(filename);
uint32_t filesize = file.size();
if (filesize == 0) {
file.close();
wsErr("File has size 0. " + filename);
return;
}
uint8_t md5bytes[16];
{
MD5Builder md5;
md5.begin();
md5.addStream(file, filesize);
md5.calculate();
md5.getBytes(md5bytes);
}
file.close();
sendDataAvail(pending);
pendingdata* pendinginfo = nullptr;
pendinginfo = new pendingdata;
pendinginfo->filename = filename;
pendinginfo->ver = *((uint64_t*)md5bytes);
pendinginfo->len = filesize;
pendinginfo->data = nullptr;
pendinginfo->timeout = PENDING_TIMEOUT;
pendingfiles.push_back(pendinginfo);
taginfo->pending = true;
memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes));
taginfo->contentMode = 12;
taginfo->nextupdate = 3216153600;
wsSendTaginfo(mac);
}
}
void processBlockRequest(struct espBlockRequest* br) {
if (!checkCRC(br, sizeof(struct espBlockRequest))) {
Serial.print("Failed CRC on a blockrequest received by the AP");
@@ -239,9 +314,7 @@ void processXferComplete(struct espXferComplete* xfc) {
}
if (LittleFS.exists(src_path)) {
LittleFS.rename(src_path, dst_path);
} else {
wsErr("hm, weird, no pending image found after xfercomplete.");
}
}
time_t now;
time(&now);
@@ -307,6 +380,12 @@ void processDataReq(struct espAvailDataReq* eadr) {
time(&now);
taginfo->lastseen = now;
if (eadr->src[7] == 0xFF) {
taginfo->isExternal = true;
} else {
taginfo->isExternal = false;
}
uint16_t minutesUntilNextUpdate = 0;
if (taginfo->nextupdate > now + 2 * 60) {
minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60;

View File

@@ -25,6 +25,7 @@
QueueHandle_t rxCmdQueue;
SemaphoreHandle_t txActive;
extern UDPcomm udpsync;
#define CMD_REPLY_WAIT 0x00
#define CMD_REPLY_ACK 0x01
@@ -198,12 +199,15 @@ void rxCmdProcessor(void* parameter) {
break;
case RX_CMD_ADR:
processDataReq((struct espAvailDataReq*)rxcmd->data);
udpsync.netProcessDataReq((struct espAvailDataReq*)rxcmd->data);
break;
case RX_CMD_XFC:
processXferComplete((struct espXferComplete*)rxcmd->data);
udpsync.netProcessXferComplete((struct espXferComplete*)rxcmd->data);
break;
case RX_CMD_XTO:
processXferTimeout((struct espXferComplete*)rxcmd->data);
udpsync.netProcessXferTimeout((struct espXferComplete*)rxcmd->data);
break;
}

View File

@@ -88,6 +88,7 @@ void fillNode(JsonObject &tag, tagRecord* &taginfo) {
tag["wakeupReason"] = taginfo->wakeupReason;
tag["capabilities"] = taginfo->capabilities;
tag["modecfgjson"] = taginfo->modeConfigJson;
tag["isexternal"] = taginfo->isExternal;
}
void saveDB(String filename) {
@@ -180,6 +181,7 @@ void loadDB(String filename) {
taginfo->wakeupReason = tag["wakeupReason"];
taginfo->capabilities = tag["capabilities"];
taginfo->modeConfigJson = tag["modecfgjson"].as<String>();
taginfo->isExternal = tag["isexternal"].as<bool>();
}
} else {
Serial.print(F("deserializeJson() failed: "));

View File

@@ -5,21 +5,17 @@
#include "commstructs.h"
#include "newproto.h"
#define PKT_AVAIL_DATA_SHORTREQ 0xE3
#define PKT_AVAIL_DATA_REQ 0xE5
#define PKT_AVAIL_DATA_INFO 0xE6
#define PKT_BLOCK_PARTIAL_REQUEST 0xE7
#define PKT_BLOCK_REQUEST_ACK 0xE9
#define PKT_BLOCK_REQUEST 0xE4
#define PKT_BLOCK_PART 0xE8
#define PKT_XFER_COMPLETE 0xEA
#define PKT_XFER_COMPLETE_ACK 0xEB
#define PKT_XFER_TIMEOUT 0xED
#define PKT_CANCEL_XFER 0xEC
#define PKT_PING 0xED
#define PKT_PONG 0xEE
#define PKT_ID_APS 0x80
UDPcomm udpsync;
uint8_t channelList[6] = {11, 15, 20, 25, 26, 27};
void init_udp() {
udpsync.init();
}
@@ -34,29 +30,62 @@ UDPcomm::~UDPcomm() {
void UDPcomm::init() {
if (udp.listenMulticast(IPAddress(239, 10, 0, 1), 16033)) {
Serial.print("UDP Listening on IP: ");
Serial.println(WiFi.localIP());
udp.onPacket([this](AsyncUDPPacket packet) {
this->processPacket(packet);
});
}
}
void UDPcomm::send(uint8_t* output) {
udp.writeTo(output, strlen((char*)output), IPAddress(239, 10, 0, 1), 16572);
}
void UDPcomm::processPacket(AsyncUDPPacket packet) {
if (packet.data()[0] == 0xFD) {
if (packet.data()[0] == PKT_AVAIL_DATA_INFO) {
espAvailDataReq* adr = (espAvailDataReq*)&packet.data()[1];
adr->src[7] = 0xFF;
processDataReq(adr);
}
if (packet.data()[0] == PKT_XFER_COMPLETE) {
espXferComplete* xfc = (espXferComplete*)&packet.data()[1];
processXferComplete(xfc);
}
if (packet.data()[0] == PKT_XFER_TIMEOUT) {
espXferComplete* xfc = (espXferComplete*)&packet.data()[1];
processXferTimeout(xfc);
}
if (packet.data()[0] == PKT_AVAIL_DATA_REQ) {
pendingData* pending = (pendingData*)&packet.data()[1];
prepareExternalDataAvail(pending, packet.remoteIP());
}
if (packet.data()[0] == PKT_ID_APS) {
Serial.println("ap list req");
IPAddress senderIP = packet.remoteIP();
unsigned int senderPort = packet.remotePort();
//todo: autoselect channel
}
}
void UDPcomm::processDataReq(struct espAvailDataReq* eadr) {
void UDPcomm::netProcessDataReq(struct espAvailDataReq* eadr) {
uint8_t buffer[sizeof(struct espAvailDataReq) + 1];
buffer[0] = PKT_AVAIL_DATA_INFO;
memcpy(buffer + 1, eadr, sizeof(struct espAvailDataReq));
udp.writeTo(buffer, sizeof(buffer), IPAddress(239, 10, 0, 1), 16572);
udp.writeTo(buffer, sizeof(buffer), IPAddress(239, 10, 0, 1), 16033);
}
void UDPcomm::netProcessXferComplete(struct espXferComplete* xfc) {
uint8_t buffer[sizeof(struct espXferComplete) + 1];
buffer[0] = PKT_XFER_COMPLETE;
memcpy(buffer + 1, xfc, sizeof(struct espXferComplete));
udp.writeTo(buffer, sizeof(buffer), IPAddress(239, 10, 0, 1), 16033);
}
void UDPcomm::netProcessXferTimeout(struct espXferComplete* xfc) {
uint8_t buffer[sizeof(struct espXferComplete) + 1];
buffer[0] = PKT_XFER_TIMEOUT;
memcpy(buffer + 1, xfc, sizeof(struct espXferComplete));
udp.writeTo(buffer, sizeof(buffer), IPAddress(239, 10, 0, 1), 16033);
}
void UDPcomm::netSendDataAvail(struct pendingData* pending) {
uint8_t buffer[sizeof(struct pendingData) + 1];
buffer[0] = PKT_AVAIL_DATA_REQ;
memcpy(buffer + 1, pending, sizeof(struct pendingData));
udp.writeTo(buffer, sizeof(buffer), IPAddress(239, 10, 0, 1), 16033);
}