#include "newproto.h" #include #include #include #include #include #include #include #include "LittleFS.h" #include "commstructs.h" #include "serialap.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; for (uint8_t c = 1; c < len; c++) { total += ((uint8_t*)p)[c]; } ((uint8_t*)p)[0] = total; // pr("%d",total); } bool checkCRC(void* p, uint8_t len) { uint8_t total = 0; for (uint8_t c = 1; c < len; c++) { total += ((uint8_t*)p)[c]; } return ((uint8_t*)p)[0] == total; } uint8_t* getDataForFile(fs::File* file) { uint8_t* ret = nullptr; ret = (uint8_t*)malloc(file->size()); if (ret) { file->seek(0); file->readBytes((char*)ret, file->size()); } else { Serial.print("malloc failed for file...\n"); } return ret; } void prepareCancelPending(uint8_t dst[8]) { struct pendingData pending = {0}; memcpy(pending.targetMac, dst, 8); sendCancelPending(&pending); tagRecord* taginfo = nullptr; taginfo = tagRecord::findByMAC(dst); if (taginfo == nullptr) { wsErr("Tag not found, this shouldn't happen."); return; } clearPending(taginfo); wsSendTaginfo(dst); } void prepareIdleReq(uint8_t* dst, uint16_t nextCheckin) { if (nextCheckin > MIN_RESPONSE_TIME) nextCheckin = MIN_RESPONSE_TIME; if (nextCheckin > 0) { struct pendingData pending = {0}; memcpy(pending.targetMac, dst, 8); pending.availdatainfo.dataType = DATATYPE_NOUPDATE; pending.availdatainfo.nextCheckIn = nextCheckin; pending.attemptsLeft = 10 + MIN_RESPONSE_TIME; char buffer[64]; sprintf(buffer, ">SDA %02X%02X%02X%02X%02X%02X%02X%02X NOP\n", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); Serial.print(buffer); sendDataAvail(&pending); } } void prepareNFCReq(uint8_t* dst, const char* url) { tagRecord* taginfo = nullptr; taginfo = tagRecord::findByMAC(dst); if (taginfo == nullptr) { wsErr("Tag not found, this shouldn't happen."); return; } clearPending(taginfo); size_t len = strlen(url); taginfo->data = new uint8_t[len + 8]; // TLV taginfo->data[0] = 0x03; // NDEF message (TLV type) taginfo->data[1] = 4 + len + 1; // ndef record taginfo->data[2] = 0xD1; taginfo->data[3] = 0x01; // well known record type taginfo->data[4] = len + 1; // payload length taginfo->data[5] = 0x55; // payload type (URI record) taginfo->data[6] = 0x00; // URI identifier code (no prepending) memcpy(taginfo->data + 7, reinterpret_cast(url), len); len = 7 + len; taginfo->data[len] = 0xFE; len = 1 + len; taginfo->pending = true; taginfo->len = len; struct pendingData pending = {0}; memcpy(pending.targetMac, dst, 8); pending.availdatainfo.dataSize = len; pending.availdatainfo.dataType = DATATYPE_NFC_RAW_CONTENT; pending.availdatainfo.nextCheckIn = 0; pending.availdatainfo.dataVer = millis(); pending.attemptsLeft = 10; sendDataAvail(&pending); wsSendTaginfo(dst); } bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t nextCheckin) { if (nextCheckin > MIN_RESPONSE_TIME) nextCheckin = MIN_RESPONSE_TIME; if (wsClientCount()) nextCheckin=0; tagRecord* taginfo = nullptr; taginfo = tagRecord::findByMAC(dst); if (taginfo == nullptr) { wsErr("Tag not found, this shouldn't happen."); return true; } *filename = "/" + *filename; LittleFS.begin(); if (!LittleFS.exists(*filename)) { wsErr("File not found. " + *filename); return false; } fs::File file = LittleFS.open(*filename); uint32_t filesize = file.size(); if (filesize == 0) { file.close(); wsErr("File has size 0. " + *filename); return false; } uint8_t md5bytes[16]; { MD5Builder md5; md5.begin(); md5.addStream(file, filesize); md5.calculate(); md5.getBytes(md5bytes); } file.close(); uint16_t attempts = 60 * 24; uint8_t lut = EPD_LUT_NO_REPEATS; if (memcmp(md5bytes, taginfo->md5pending, 16) == 0) { wsLog("new image is the same as current or already pending image. not updating tag."); wsSendTaginfo(dst); if (LittleFS.exists(*filename)) { LittleFS.remove(*filename); } return true; } time_t now; time(&now); time_t last_midnight = now - now % (24 * 60 * 60) + 3 * 3600; // somewhere in the middle of the night if (taginfo->lastfullupdate < last_midnight || taginfo->hwType == SOLUM_29_UC8151) { lut = EPD_LUT_DEFAULT; // full update once a day taginfo->lastfullupdate = now; } if (dataType != DATATYPE_FW_UPDATE) { char dst_path[64]; sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); if (LittleFS.exists(dst_path)) { LittleFS.remove(dst_path); } LittleFS.rename(*filename, dst_path); *filename = String(dst_path); wsLog("new image: " + String(dst_path)); time_t now; time(&now); taginfo->expectedNextCheckin = now + nextCheckin * 60 + 60; taginfo->filename = *filename; taginfo->len = filesize; clearPending(taginfo); taginfo->pending = true; memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes)); } else { wsLog("firmware upload pending"); taginfo->filename = *filename; taginfo->len = filesize; clearPending(taginfo); taginfo->pending = true; } struct pendingData pending = {0}; memcpy(pending.targetMac, dst, 8); pending.availdatainfo.dataType = dataType; pending.availdatainfo.dataVer = *((uint64_t*)md5bytes); pending.availdatainfo.dataSize = filesize; pending.availdatainfo.dataTypeArgument = lut; pending.availdatainfo.nextCheckIn = nextCheckin; pending.attemptsLeft = attempts; if (taginfo->isExternal == false) { char buffer[64]; sprintf(buffer, ">SDA %02X%02X%02X%02X%02X%02X%02X%02X TYPE 0x%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0], pending.availdatainfo.dataType); Serial.print(buffer); sendDataAvail(&pending); } else { udpsync.netSendDataAvail(&pending); } wsSendTaginfo(dst); return true; } void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) { tagRecord* taginfo = nullptr; taginfo = tagRecord::findByMAC(pending->targetMac); if (taginfo == nullptr) { return; } if (taginfo->isExternal == false) { if (pending->availdatainfo.dataType != DATATYPE_UK_SEGMENTED) { LittleFS.begin(); char hexmac[17]; mac2hex(pending->targetMac, hexmac); String filename = "/current/" + String(hexmac) + ".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("Remote file not found. " + filename); return; } uint8_t md5bytes[16]; { MD5Builder md5; md5.begin(); md5.addStream(file, filesize); md5.calculate(); md5.getBytes(md5bytes); } file.close(); taginfo->filename = filename; taginfo->len = filesize; clearPending(taginfo); taginfo->pending = true; memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes)); } taginfo->contentMode = 12; taginfo->nextupdate = 3216153600; sendDataAvail(pending); wsSendTaginfo(pending->targetMac); } } void processBlockRequest(struct espBlockRequest* br) { if (!checkCRC(br, sizeof(struct espBlockRequest))) { Serial.print("Failed CRC on a blockrequest received by the AP"); return; } tagRecord* taginfo = nullptr; taginfo = tagRecord::findByMAC(br->src); if (taginfo == nullptr) { prepareCancelPending(br->src); Serial.printf("blockrequest: couldn't find taginfo %02X%02X%02X%02X%02X%02X%02X%02X\n", br->src[7], br->src[6], br->src[5], br->src[4], br->src[3], br->src[2], br->src[1], br->src[0]); return; } if (taginfo->data == nullptr) { // not cached. open file, cache the data fs::File file = LittleFS.open(taginfo->filename); if (!file) { Serial.print("Dunno how this happened... File pending but deleted in the meantime?\n"); prepareCancelPending(br->src); return; } taginfo->data = getDataForFile(&file); file.close(); } // check if we're not exceeding max blocks (to prevent sendBlock from exceeding its boundary) uint8_t totalblocks = (taginfo->len / BLOCK_DATA_SIZE); if (taginfo->len % BLOCK_DATA_SIZE) totalblocks++; if (br->blockId >= totalblocks) { br->blockId = totalblocks - 1; } uint32_t len = taginfo->len - (BLOCK_DATA_SIZE * br->blockId); if (len > BLOCK_DATA_SIZE) len = BLOCK_DATA_SIZE; uint16_t checksum = sendBlock(taginfo->data + (br->blockId * BLOCK_DATA_SIZE), len); char buffer[150]; sprintf(buffer, "< Block Request received for file %s block %d, len %d checksum %u\0", taginfo->filename.c_str(), br->blockId, len, checksum); wsLog((String)buffer); Serial.printf("filename.c_str(), br->blockId, len, checksum); } void processXferComplete(struct espXferComplete* xfc, bool local) { char buffer[64]; sprintf(buffer, "< %02X%02X%02X%02X%02X%02X%02X%02X reports xfer complete\n\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); wsLog((String)buffer); if (local) { Serial.printf("src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); } else { Serial.printf("src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); } char src_path[64]; char dst_path[64]; sprintf(src_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.pending\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X%02X%02X.raw\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); if (LittleFS.exists(dst_path) && LittleFS.exists(src_path)) { LittleFS.remove(dst_path); } if (LittleFS.exists(src_path)) { LittleFS.rename(src_path, dst_path); } time_t now; time(&now); tagRecord* taginfo = nullptr; taginfo = tagRecord::findByMAC(xfc->src); if (taginfo != nullptr) { memcpy(taginfo->md5, taginfo->md5pending, sizeof(taginfo->md5pending)); clearPending(taginfo); taginfo->wakeupReason = 0; } wsSendTaginfo(xfc->src); if (local) udpsync.netProcessXferComplete(xfc); } void processXferTimeout(struct espXferComplete* xfc, bool local) { char buffer[64]; sprintf(buffer, "< %02X%02X%02X%02X%02X%02X%02X%02X xfer timeout\n\0", xfc->src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); wsErr((String)buffer); if (local) { Serial.printf("src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); } else { Serial.printf("src[7], xfc->src[6], xfc->src[5], xfc->src[4], xfc->src[3], xfc->src[2], xfc->src[1], xfc->src[0]); } time_t now; time(&now); tagRecord* taginfo = nullptr; taginfo = tagRecord::findByMAC(xfc->src); if (taginfo != nullptr) { taginfo->expectedNextCheckin = now + 60; memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); clearPending(taginfo); } wsSendTaginfo(xfc->src); if (local) udpsync.netProcessXferTimeout(xfc); } void processDataReq(struct espAvailDataReq* eadr, bool local) { char buffer[64]; tagRecord* taginfo = nullptr; taginfo = tagRecord::findByMAC(eadr->src); if (taginfo == nullptr) { taginfo = new tagRecord; memcpy(taginfo->mac, eadr->src, sizeof(taginfo->mac)); taginfo->pending = false; tagDB.push_back(taginfo); } time_t now; time(&now); if (!local) { taginfo->isExternal = true; } else { taginfo->isExternal = false; } if (taginfo->pendingIdle == 0) { taginfo->expectedNextCheckin = now + 60; } else { taginfo->expectedNextCheckin = now + 60 * taginfo->pendingIdle; taginfo->pendingIdle = 0; } taginfo->lastseen = now; if (eadr->adr.lastPacketRSSI != 0) { taginfo->LQI = eadr->adr.lastPacketLQI; taginfo->hwType = eadr->adr.hwType; taginfo->RSSI = eadr->adr.lastPacketRSSI; taginfo->temperature = eadr->adr.temperature; taginfo->batteryMv = eadr->adr.batteryMv; taginfo->hwType = eadr->adr.hwType; taginfo->wakeupReason = eadr->adr.wakeupReason; taginfo->capabilities = eadr->adr.capabilities; if (eadr->adr.wakeupReason >= 0xF0) { if (!taginfo->pending) taginfo->nextupdate = 0; memset(taginfo->md5, 0, 16 * sizeof(uint8_t)); memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); } } if (local) { sprintf(buffer, "src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]); } else { sprintf(buffer, "src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]); } Serial.print(buffer); wsSendTaginfo(eadr->src); if (local) udpsync.netProcessDataReq(eadr); } void refreshAllPending() { for (int16_t c = 0; c < tagDB.size(); c++) { tagRecord* taginfo = nullptr; taginfo = tagDB.at(c); if (taginfo->pending) { clearPending(taginfo); taginfo->nextupdate = 0; memset(taginfo->md5, 0, 16 * sizeof(uint8_t)); memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t)); wsSendTaginfo(taginfo->mac); } } }; void setAPchannel() { if (APconfig["channel"].as() == 0) { // trigger channel autoselect UDPcomm udpsync; udpsync.getAPList(); } else { if (curChannel.channel != APconfig["channel"].as()) { curChannel.channel = APconfig["channel"].as(); sendChannelPower(&curChannel); } } } bool sendAPSegmentedData(uint8_t* dst, String data, uint16_t icons, bool inverted, bool local) { struct pendingData pending = {0}; memcpy(pending.targetMac, dst, 8); pending.availdatainfo.dataType = DATATYPE_UK_SEGMENTED; pending.availdatainfo.dataSize = icons << 16; memcpy((void*)&(pending.availdatainfo.dataVer), data.c_str(), 10); pending.availdatainfo.dataTypeArgument = inverted; pending.availdatainfo.nextCheckIn = 0; pending.attemptsLeft = 120; char buffer[64]; sprintf(buffer, ">AP Segmented Data %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); Serial.print(buffer); if (local) { return sendDataAvail(&pending); } else { udpsync.netSendDataAvail(&pending); return true; } } bool showAPSegmentedInfo(uint8_t* dst, bool local) { struct pendingData pending = {0}; memcpy(pending.targetMac, dst, 8); pending.availdatainfo.dataType = DATATYPE_UK_SEGMENTED; pending.availdatainfo.dataSize = 0x00; pending.availdatainfo.dataVer = 0x00; pending.availdatainfo.dataTypeArgument = 0; pending.availdatainfo.nextCheckIn = 0; pending.attemptsLeft = 120; char buffer[64]; sprintf(buffer, ">SDA %02X%02X%02X%02X%02X%02X%02X%02X\n\0", dst[7], dst[6], dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]); Serial.print(buffer); if (local) { return sendDataAvail(&pending); } else { udpsync.netSendDataAvail(&pending); return true; } }