diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 282a1e2..5fa9521 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -11,6 +11,7 @@ #include "Pagedata.h" #include "OBP60Hardware.h" #include "OBP60Extensions.h" +#include "imglib.h" // Character sets #include "Ubuntu_Bold8pt7b.h" @@ -404,4 +405,44 @@ void generatorGraphic(uint x, uint y, int pcolor, int bcolor){ getdisplay().print("G"); } -#endif \ No newline at end of file +// Function to handle HTTP image request +void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request) { + GwLog *logger = api->getLogger(); + + String imgformat = api->getConfig()->getConfigItem(api->getConfig()->imageFormat,true)->asString(); + imgformat.toLowerCase(); + String filename = "Page" + String(*pageno) + "_" + pages[*pageno].description->pageName + "." + imgformat; + + logger->logDebug(GwLog::LOG,"handle image request [%s]: %s", imgformat, filename); + + uint8_t *fb = getdisplay().getBuffer(); // EPD framebuffer + std::vector imageBuffer; // image in webserver transferbuffer + String mimetype; + + if (imgformat == "gif") { + // GIF is commpressed with LZW, so small + mimetype = "image/gif"; + if (!createGIF(fb, &imageBuffer, GxEPD_WIDTH, GxEPD_HEIGHT)) { + logger->logDebug(GwLog::LOG,"GIF creation failed: Hashtable init error!"); + return; + } + } + else if (imgformat == "bmp") { + // Microsoft BMP bitmap + mimetype = "image/bmp"; + createBMP(fb, &imageBuffer, GxEPD_WIDTH, GxEPD_HEIGHT); + } + else { + // PBM simple portable bitmap + mimetype = "image/x-portable-bitmap"; + createPBM(fb, &imageBuffer, GxEPD_WIDTH, GxEPD_HEIGHT); + } + + AsyncWebServerResponse *response = request->beginResponse_P(200, mimetype, (const uint8_t*)imageBuffer.data(), imageBuffer.size()); + response->addHeader("Content-Disposition", "inline; filename=" + filename); + request->send(response); + + imageBuffer.clear(); +} + +#endif diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index e02cd8b..0c7c117 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -70,4 +70,6 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor); // S void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic with fill level void startLedTask(GwApi *api); -#endif \ No newline at end of file +void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request); + +#endif diff --git a/lib/obp60task/OBP60Hardware.h b/lib/obp60task/OBP60Hardware.h index 7f0abe2..f450790 100644 --- a/lib/obp60task/OBP60Hardware.h +++ b/lib/obp60task/OBP60Hardware.h @@ -39,11 +39,6 @@ #define OBP_SPI_DIN 48 #define SHOW_TIME 6000 // Show time in [ms] for logo and WiFi QR code #define FULL_REFRESH_TIME 600 // Refresh cycle time in [s][600...3600] for full display update (very important healcy function) - #define MAX_PAGE_NUMBER 10 // Max number of pages for show data - #define FONT1 "Ubuntu_Bold8pt7b" - #define FONT2 "Ubuntu_Bold24pt7b" - #define FONT3 "Ubuntu_Bold32pt7b" - #define FONT4 "DSEG7Classic_BoldItalic80pt7b" // GPS (NEO-6M, NEO-M8N, ATGM336H) #define OBP_GPS_RX 2 diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index f4374c3..526d9b5 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -4,6 +4,8 @@ #include #include +#define MAX_PAGE_NUMBER 10 // Max number of pages for show data + typedef std::vector ValueList; typedef struct{ String pageName; @@ -114,6 +116,13 @@ class PageDescription{ } }; +class PageStruct{ + public: + Page *page=NULL; + PageData parameters; + PageDescription *description=NULL; +}; + // Structure for formated boat values typedef struct{ double value; diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index ca57f33..bac0db1 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -898,6 +898,22 @@ "obp60":"true" } }, + { + "name": "imageFormat", + "label": "Screenshot Format", + "type": "list", + "default":"PBM", + "description": "Graphics file format for screenshots [GIF|PBM|BMP]", + "list": [ + {"l":"Compressed image (GIF)","v":"GIF"}, + {"l":"Portable bitmap (PBM)","v":"PBM"}, + {"l":"Windows bitmap (BMP)","v":"BMP"} + ], + "category":"OBP60 Pages", + "capabilities": { + "obp60":"true" + } + }, { "name": "page1type", "label": "Type", diff --git a/lib/obp60task/imglib.cpp b/lib/obp60task/imglib.cpp new file mode 100644 index 0000000..80b795e --- /dev/null +++ b/lib/obp60task/imglib.cpp @@ -0,0 +1,347 @@ +/****************************************************************************** + * Image functions: + * - Convert a 1bit framebuffer in RAM to + * - GIF, compressed, based on giflib and gif_hash + * - PBM, portable bitmap, very simple copy + * - BMP, bigger with a little bit fiddling around + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +#include // needed for PROGMEM +#include +#include "GwLog.h" // needed for logger +#include "imglib.h" + +GifFilePrivateType gifprivate; + +void ClearHashTable(GifHashTableType *HashTable) { + memset(HashTable->HTable, 0xFF, HT_SIZE * sizeof(uint32_t)); +} + +GifHashTableType *InitHashTable(void) { + GifHashTableType *HashTable; + if ((HashTable = (GifHashTableType *)ps_malloc(sizeof(GifHashTableType))) == NULL) { + return NULL; + } + ClearHashTable(HashTable); + return HashTable; +} + +static int KeyItem(uint32_t Item) { + return ((Item >> 12) ^ Item) & HT_KEY_MASK; +} + +void InsertHashTable(GifHashTableType *HashTable, uint32_t Key, int Code) { + int HKey = KeyItem(Key); + uint32_t *HTable = HashTable->HTable; + while (HT_GET_KEY(HTable[HKey]) != 0xFFFFFL) { + HKey = (HKey + 1) & HT_KEY_MASK; + } + HTable[HKey] = HT_PUT_KEY(Key) | HT_PUT_CODE(Code); +} + +int ExistsHashTable(GifHashTableType *HashTable, uint32_t Key) { + int HKey = KeyItem(Key); + uint32_t *HTable = HashTable->HTable, HTKey; + while ((HTKey = HT_GET_KEY(HTable[HKey])) != 0xFFFFFL) { + if (Key == HTKey) { + return HT_GET_CODE(HTable[HKey]); + } + HKey = (HKey + 1) & HT_KEY_MASK; + } + return -1; +} + +/****************************************************************************** + Put 2 bytes (a word) into the given file in little-endian order: +******************************************************************************/ +void GifPutWord(std::vector* gifBuffer, uint16_t Word) { + /*cuint8_t c[2]; + [0] = LOBYTE(Word); + c[1] = HIBYTE(Word); + gifBuffer->push_back(c[0]); + gifBuffer->push_back(c[1]); */ + gifBuffer->push_back(LOBYTE(Word)); + gifBuffer->push_back(HIBYTE(Word)); +} + +/****************************************************************************** + This routines buffers the given characters until 255 characters are ready + to be output. If Code is equal to -1 the buffer is flushed (EOF). + The buffer is Dumped with first byte as its size, as GIF format requires. +******************************************************************************/ +void GifBufferedOutput(std::vector* gifBuffer, GifByteType *Buf, int c) { + if (c == FLUSH_OUTPUT) { + // Flush everything out. + for (int i = 0; i < Buf[0] + 1; i++) { + gifBuffer->push_back(Buf[i]); + } + // Mark end of compressed data, by an empty block (see GIF doc): + Buf[0] = 0; + gifBuffer->push_back(0); + } else { + if (Buf[0] == 255) { + // Dump out this buffer - it is full: + for (int i = 0; i < Buf[0] + 1; i++) { + gifBuffer->push_back(Buf[i]); + } + Buf[0] = 0; + } + Buf[++Buf[0]] = c; + } +} + +/****************************************************************************** + The LZ compression output routine: + This routine is responsible for the compression of the bit stream into + 8 bits (bytes) packets. +******************************************************************************/ +void GifCompressOutput(std::vector* gifBuffer, const int Code) { + + if (Code == FLUSH_OUTPUT) { + while (gifprivate.CrntShiftState > 0) { + // Get Rid of what is left in DWord, and flush it. + GifBufferedOutput(gifBuffer, gifprivate.Buf, gifprivate.CrntShiftDWord & 0xff); + gifprivate.CrntShiftDWord >>= 8; + gifprivate.CrntShiftState -= 8; + } + gifprivate.CrntShiftState = 0; // For next time. + GifBufferedOutput(gifBuffer, gifprivate.Buf, FLUSH_OUTPUT); + } else { + gifprivate.CrntShiftDWord |= ((long)Code) << gifprivate.CrntShiftState; + gifprivate.CrntShiftState += gifprivate.RunningBits; + while (gifprivate.CrntShiftState >= 8) { + // Dump out full bytes: + GifBufferedOutput(gifBuffer, gifprivate.Buf, gifprivate.CrntShiftDWord & 0xff); + gifprivate.CrntShiftDWord >>= 8; + gifprivate.CrntShiftState -= 8; + } + } + + /* If code cannt fit into RunningBits bits, must raise its size. Note */ + /* however that codes above LZ_MAX_CODE are used for special signaling. */ + if (gifprivate.RunningCode >= gifprivate.MaxCode1 && Code <= LZ_MAX_CODE) { + gifprivate.MaxCode1 = 1 << ++gifprivate.RunningBits; + } +} + +/****************************************************************************** + Setup the LZ compression for this image: +******************************************************************************/ +void GifSetupCompress(std::vector* gifBuffer) { + gifBuffer->push_back(0x02);// Bits per pixel wit minimum 2 + + gifprivate.Buf[0] = 0; // Nothing was output yet + gifprivate.BitsPerPixel = 2; // Minimum is 2 + gifprivate.ClearCode = (1 << 2); + gifprivate.EOFCode = gifprivate.ClearCode + 1; + gifprivate.RunningCode = gifprivate.EOFCode + 1; + gifprivate.RunningBits = 2 + 1; // Number of bits per code + gifprivate.MaxCode1 = 1 << gifprivate.RunningBits; // Max. code + 1 + gifprivate.CrntCode = FIRST_CODE; // Signal that this is first one! + gifprivate.CrntShiftState = 0; // No information in CrntShiftDWord + gifprivate.CrntShiftDWord = 0; + + GifCompressOutput(gifBuffer, gifprivate.ClearCode); +} + +void createGifHeader(std::vector* gifBuffer, uint16_t width, uint16_t height) { + + // SCREEN DESCRIPTOR + gifBuffer->push_back('G'); + gifBuffer->push_back('I'); + gifBuffer->push_back('F'); + gifBuffer->push_back('8'); + gifBuffer->push_back('7'); + gifBuffer->push_back('a'); + + GifPutWord(gifBuffer, width); + GifPutWord(gifBuffer, height); + + gifBuffer->push_back(0x80 | (1 << 4)); + gifBuffer->push_back(0x00); // Index into the ColorTable for background color + gifBuffer->push_back(0x00); // Pixel Aspect Ratio + + // Colormap + gifBuffer->push_back(0xff); // Color 0 + gifBuffer->push_back(0xff); + gifBuffer->push_back(0xff); + gifBuffer->push_back(0x00); // Color 1 + gifBuffer->push_back(0x00); + gifBuffer->push_back(0x00); + + // IMAGE DESCRIPTOR + gifBuffer->push_back(DESCRIPTOR_INTRODUCER); + + GifPutWord(gifBuffer, 0); + GifPutWord(gifBuffer, 0); + GifPutWord(gifBuffer, width); + GifPutWord(gifBuffer, height); + + gifBuffer->push_back(0x00); // No colormap here , we use the global one +} + +/****************************************************************************** + The LZ compression routine: + This version compresses the given buffer Line of length LineLen. + This routine can be called a few times (one per scan line, for example), in + order to complete the whole image. +******************************************************************************/ +void GifCompressLine(std::vector* gifBuffer, const GifPixelType *Line, const int LineLen) { + int i = 0, CrntCode; + GifHashTableType *HashTable; + + HashTable = gifprivate.HashTable; + + if (gifprivate.CrntCode == FIRST_CODE) { // Its first time! + CrntCode = Line[i++]; + } else { + CrntCode = + gifprivate.CrntCode; // Get last code in compression + } + while (i < LineLen) { // Decode LineLen items + GifPixelType Pixel = Line[i++]; // Get next pixel from stream. + /* Form a new unique key to search hash table for the code + * combines CrntCode as Prefix string with Pixel as postfix + * char. + */ + int NewCode; + unsigned long NewKey = (((uint32_t)CrntCode) << 8) + Pixel; + if ((NewCode = ExistsHashTable(HashTable, NewKey)) >= 0) { + /* This Key is already there, or the string is old one, + * so simple take new code as our CrntCode: + */ + CrntCode = NewCode; + } else { + /* Put it in hash table, output the prefix code, and + * make our CrntCode equal to Pixel. + */ + GifCompressOutput(gifBuffer, CrntCode); + CrntCode = Pixel; + + /* If however the HashTable if full, we send a clear + * first and Clear the hash table. + */ + if (gifprivate.RunningCode >= LZ_MAX_CODE) { + // Time to do some clearance: + GifCompressOutput(gifBuffer, gifprivate.ClearCode); + gifprivate.RunningCode = gifprivate.EOFCode + 1; + gifprivate.RunningBits = gifprivate.BitsPerPixel + 1; + gifprivate.MaxCode1 = 1 << gifprivate.RunningBits; + ClearHashTable(HashTable); + } else { + // Put this unique key with its relative Code in hash table: + InsertHashTable(HashTable, NewKey, gifprivate.RunningCode++); + } + } + } + + // Preserve the current state of the compression algorithm: + gifprivate.CrntCode = CrntCode; + + if (gifprivate.PixelCount == 0) { + // We are done - output last Code and flush output buffers: + GifCompressOutput(gifBuffer, CrntCode); + GifCompressOutput(gifBuffer, gifprivate.EOFCode); + GifCompressOutput(gifBuffer, FLUSH_OUTPUT); + } +} + +bool createBMP(uint8_t *frameBuffer, std::vector* imageBuffer, uint16_t width, uint16_t height) { + // For BMP the line size has to be a multiple of 4 bytes. + // So padding is needed. Also the lines have to be in reverded + // order compared to plain buffer + + // BMP header for black-and-white image (1 bit per pixel) + const uint8_t bmp_header[] PROGMEM = { + // BITMAPFILEHEADER (14 Bytes) + 0x42, 0x4D, // bfType: 'BM' signature + 0x2e, 0x3d, 0x00, 0x00, // bfSize: file size in bytes + 0x00, 0x00, // bfReserved1 + 0x00, 0x00, // bfReserved2 + 0x3e, 0x00, 0x00, 0x00, // bfOffBits: offset in bytes to pixeldata + // BITMAPINFOHEADER (40 Bytes) + 0x28, 0x00, 0x00, 0x00, // biSize: DIB header size + (uint8_t)LOBYTE(width), (uint8_t)HIBYTE(width), 0x00, 0x00, // biWidth + (uint8_t)LOBYTE(height), (uint8_t)HIBYTE(height), 0x00, 0x00, // biHeight + 0x01, 0x00, // biPlanes: Number of color planes (1) + 0x01, 0x00, // biBitCount: Color depth (1 bit per pixel) + 0x00, 0x00, 0x00, 0x00, // biCompression: Compression (none) + 0xf0, 0x3c, 0x00, 0x00, // biSizeImage: Image data size (calculate) + 0x13, 0x0b, 0x00, 0x00, // biXPelsPerMeter: Horizontal resolution (2835 pixels/meter) + 0x13, 0x0b, 0x00, 0x00, // biYPelsPerMeter: Vertical resolution (2835 pixels/meter) + 0x02, 0x00, 0x00, 0x00, // biClrUsed: Colors in color palette (2) + 0x00, 0x00, 0x00, 0x00, // biClrImportant: Important colors (all) + // PALETTE: COLORTRIPLES of RGBQUAD (n * 4 Bytes) + 0x00, 0x00, 0x00, 0x00, // Color palette: Black + 0xff, 0xff, 0xff, 0x00 // Color palette: White + }; + size_t bmp_headerSize = sizeof(bmp_header); + + size_t lineSize = (width / 8); + size_t paddingSize = 0; + if (lineSize % 4 != 0) { + paddingSize = 4 - lineSize % 4; + } + size_t imageSize = bmp_headerSize + (lineSize + paddingSize) * height; + + imageBuffer->resize(imageSize); + memcpy(imageBuffer->data(), bmp_header, bmp_headerSize); + for (int y = 0; y < height; y++) { + uint8_t* srcRow = frameBuffer + (y * lineSize); + uint8_t* destRow = imageBuffer->data() + bmp_headerSize + ((height - 1 - y) * (lineSize + paddingSize)); + memcpy(destRow, srcRow, lineSize); + for (int j = 0; j < paddingSize; j++) { + destRow[lineSize + j] = 0x00; + } + } + return true; +} + +bool createPBM(uint8_t *frameBuffer, std::vector* imageBuffer, uint16_t width, uint16_t height) { + // creates binary PBM image inside imagebuffer + // returns bytesize of created image + const char pbm_header[] PROGMEM = "P4\n#Created by OBP60\n400 300\n"; + size_t pbm_headerSize = sizeof(pbm_header) - 1; // We don't want trailing zero + size_t imageSize = pbm_headerSize + width / 8 * height; + imageBuffer->resize(imageSize); + memcpy(imageBuffer->data(), pbm_header, pbm_headerSize); + memcpy(imageBuffer->data() + pbm_headerSize, frameBuffer, width / 8 * height); + return true; +} + +bool createGIF(uint8_t *framebuffer, std::vector* gifBuffer, uint16_t width, uint16_t height) { + + size_t imageSize = 0; + uint16_t bufOffset = 0; // Offset into imageBuffer for next write access + + gifprivate.HashTable = InitHashTable(); + if (gifprivate.HashTable == NULL) { + return false; + } + gifprivate.PixelCount = width * height; + + createGifHeader(gifBuffer, width, height); + + // Reset compress algorithm parameters. + GifSetupCompress(gifBuffer); + + gifBuffer->reserve(4096); // to avoid lots of alloactions + GifPixelType line[width]; + for (int y = 0; y < height; y++) { + // convert uint8_t pixels to single pixels + for (int x = 0; x < width; x++) { + int byteIndex = (y * width + x) / 8; + uint8_t bitIndex = 7 - ((y * width + x) % 8); + line[x] = (framebuffer[byteIndex] & (uint8_t)(1 << bitIndex)) == 0; + } + gifprivate.PixelCount -= width; + GifCompressLine(gifBuffer, line, width); + } + + gifBuffer->push_back(TERMINATOR_INTRODUCER); + free((GifHashTableType *)gifprivate.HashTable); + + return true; +} diff --git a/lib/obp60task/imglib.h b/lib/obp60task/imglib.h new file mode 100644 index 0000000..1299a7b --- /dev/null +++ b/lib/obp60task/imglib.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * + * imglib.h + * + * SPDX-License-Identifier: MIT + *****************************************************************************/ + +#ifndef _IMGLIB_H_ +#define _IMGLIB_H_ 1 + +// extract bytes from an unsigned word +#define LOBYTE(x) ((x)&0xff) +#define HIBYTE(x) (((x) >> 8) & 0xff) + +// GIF encoding constants +#define DESCRIPTOR_INTRODUCER 0x2c +#define TERMINATOR_INTRODUCER 0x3b + +#define LZ_MAX_CODE 4095 // Biggest code possible in 12 bits +#define LZ_BITS 12 + +#define FLUSH_OUTPUT 4096 // Impossible code, to signal flush +#define FIRST_CODE 4097 // Impossible code, to signal first +#define NO_SUCH_CODE 4098 // Impossible code, to signal empty + +// magfic constants and declarations for GIF LZW + +#define HT_SIZE 8192 /* 12bits = 4096 or twice as big! */ +#define HT_KEY_MASK 0x1FFF /* 13bits keys */ +#define HT_KEY_NUM_BITS 13 /* 13bits keys */ +#define HT_MAX_KEY 8191 /* 13bits - 1, maximal code possible */ +#define HT_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ + +/* The 32 bits of the long are divided into two parts for the key & code: */ +/* 1. The code is 12 bits as our compression algorithm is limited to 12bits */ +/* 2. The key is 12 bits Prefix code + 8 bit new char or 20 bits. */ +/* The key is the upper 20 bits. The code is the lower 12. */ +#define HT_GET_KEY(l) (l >> 12) +#define HT_GET_CODE(l) (l & 0x0FFF) +#define HT_PUT_KEY(l) (l << 12) +#define HT_PUT_CODE(l) (l & 0x0FFF) + +typedef unsigned char GifPixelType; +typedef unsigned char GifByteType; + +typedef struct GifHashTableType { + uint32_t HTable[HT_SIZE]; +} GifHashTableType; + +typedef struct GifFilePrivateType { + uint8_t BitsPerPixel; // Bits per pixel (Codes uses at least this + 1) + uint16_t ClearCode; // The CLEAR LZ code + uint16_t EOFCode; // The EOF LZ code + uint16_t RunningCode; // The next code algorithm can generate + uint16_t RunningBits; // The number of bits required to represent RunningCode + uint16_t MaxCode1; // 1 bigger than max. possible code, in RunningBits bits + uint16_t LastCode; // The code before the current code. + uint16_t CrntCode; // Current algorithm code + uint16_t CrntShiftState; // Number of bits in CrntShiftDWord + uint32_t CrntShiftDWord; // For bytes decomposition into codes + uint32_t PixelCount; // Number of pixels in image + GifByteType Buf[256]; // Compressed input is buffered here + GifHashTableType *HashTable; +} GifFilePrivateType; + +bool createGIF(uint8_t *framebuffer, std::vector* imageBuffer, uint16_t width, uint16_t height); +bool createBMP(uint8_t *framebuffer, std::vector* imageBuffer, uint16_t width, uint16_t height); +bool createPBM(uint8_t *framebuffer, std::vector* gifBuffer, uint16_t width, uint16_t height); + +#endif /* _IMGLIB_H */ diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 3b4c52d..7f65bbc 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -183,13 +183,6 @@ class PageList{ } }; -class PageStruct{ - public: - Page *page=NULL; - PageData parameters; - PageDescription *description=NULL; -}; - /** * this function will add all the pages we know to the pagelist * each page should have defined a registerXXXPage variable of type @@ -353,6 +346,10 @@ void OBP60Task(GwApi *api){ // Init pages int numPages=1; PageStruct pages[MAX_PAGE_NUMBER]; + // Set start page + int pageNumber = int(api->getConfig()->getConfigItem(api->getConfig()->startPage,true)->asInt()) - 1; + int lastPage=pageNumber; + BoatValueList boatValues; //all the boat values for the api query //commonData.distanceformat=config->getString(xxx); //add all necessary data to common data @@ -395,6 +392,11 @@ void OBP60Task(GwApi *api){ pages[i].parameters.values.push_back(value); } } + + api->registerRequestHandler("screenshot", [api, &pageNumber, pages](AsyncWebServerRequest *request) { + doImageRequest(api, &pageNumber, pages, request); + }); + //now we have prepared the page data //we start a separate task that will fetch our keys... MyData allParameters; @@ -431,9 +433,6 @@ void OBP60Task(GwApi *api){ GwApi::BoatValue *hdop = boatValues.findValueOrCreate("HDOP"); // Load GpsHDOP LOG_DEBUG(GwLog::LOG,"obp60task: start mainloop"); - // Set start page - int pageNumber = int(api->getConfig()->getConfigItem(api->getConfig()->startPage,true)->asInt()) - 1; - int lastPage=pageNumber; commonData.time = boatValues.findValueOrCreate("GPST"); // Load GpsTime commonData.date = boatValues.findValueOrCreate("GPSD"); // Load GpsTime diff --git a/lib/obp60task/platformio.ini b/lib/obp60task/platformio.ini index dc919a9..14bd788 100644 --- a/lib/obp60task/platformio.ini +++ b/lib/obp60task/platformio.ini @@ -23,8 +23,9 @@ lib_deps = blemasle/MCP23017@2.0.0 adafruit/Adafruit BusIO@1.5.0 adafruit/Adafruit GFX Library@1.11.9 - zinggjm/GxEPD2@1.5.8 + #zinggjm/GxEPD2@1.5.8 #https://github.com/ZinggJM/GxEPD2 + https://github.com/thooge/GxEPD2 sstaub/Ticker@4.4.0 adafruit/Adafruit BMP280 Library@2.6.2 adafruit/Adafruit BME280 Library@2.2.2