diff --git a/packet.cpp b/packet.cpp index 8f49e51..2f18310 100644 --- a/packet.cpp +++ b/packet.cpp @@ -4,7 +4,7 @@ using namespace vbit; -Packet::Packet(int mag, int row, std::string val) : _isHeader(false), _page(0x8ff), _coding(CODING_7BIT_TEXT) +Packet::Packet(int mag, int row, std::string val) : _isHeader(false), _coding(CODING_7BIT_TEXT) { //ctor SetMRAG(mag, row); @@ -53,6 +53,14 @@ void Packet::SetRow(int mag, int row, std::string val, PageCoding coding) default: // treat an invalid coding as 7-bit text case CODING_7BIT_TEXT: { + // Perform substitution of version number string + // %%%%%V version number eg. v2.0.0 + int off = Packet::GetOffsetOfSubstition("%%%%%V"); + if (off > -1) + { + std::copy_n(VBIT2_VERSION,6,_packet.begin() + off); + } + // first byte parity already set by first switch statement Parity(6); break; @@ -113,6 +121,15 @@ void Packet::SetRow(int mag, int row, std::string val, PageCoding coding) } } +void Packet::SetX27CRC(uint16_t crc) +{ + if (Hamming8DecodeTable[_packet[5] & 0xF] == 0) // only set CRC bytes for packet X/27/0 + { + _packet[43]=crc >> 8; + _packet[44]=crc & 0xFF; + } +} + void Packet::SetPacketRaw(std::vector data) { data.resize(40, 0x00); // ensure correct length @@ -174,15 +191,17 @@ bool Packet::get_offset_time(time_t t, uint8_t* str) int Packet::GetOffsetOfSubstition(std::string string) { - auto it = std::search(_packet.begin(), _packet.end(), string.begin(), string.end()); + auto it = std::search(_packet.begin()+1, _packet.end(), string.begin(), string.end()); if (it != _packet.end()) return std::distance(_packet.begin(), it); else return -1; } -/* Perform translations on packet for header substitution etc. +/* Perform translations on packet. * return pointer to 45 byte packet data vector + * + * *** Any substitutions applied by this function will break the checksum that has already been calculated and broadcast *** */ std::array* Packet::tx() { @@ -197,119 +216,11 @@ std::array* Packet::tx() char tmpstr[] = " "; int off; - if (_isHeader) // We can do header substitutions + if (_isHeader) { - // mpp page number - %%# - off = Packet::GetOffsetOfSubstition("%%#"); - if (off > -1) - { - if (_mag==0) - _packet[off]='8'; - else - _packet[off]=_mag+'0'; - _packet[off+1]=_page/0x10+'0'; - if (_packet[off+1]>'9') - _packet[off+1]=_packet[off+1]-'0'-10+'A'; // Particularly poor hex conversion algorithm - - _packet[off+2]=_page%0x10+'0'; - if (_packet[off+2]>'9') - _packet[off+2]=_packet[off+2]-'0'-10+'A'; // Particularly poor hex conversion algorithm - } - - // day name - %%a - off = Packet::GetOffsetOfSubstition("%%a"); - if (off > -1) - { - strftime(tmpstr,10,"%a",timeinfo); - _packet[off]=tmpstr[0]; - _packet[off+1]=tmpstr[1]; - _packet[off+2]=tmpstr[2]; - } - - // month name - %%b - off = Packet::GetOffsetOfSubstition("%%b"); - if (off > -1) - { - strftime(tmpstr,10,"%b",timeinfo); - _packet[off]=tmpstr[0]; - _packet[off+1]=tmpstr[1]; - _packet[off+2]=tmpstr[2]; - } - - // day of month with leading zero - %d - off = Packet::GetOffsetOfSubstition("%d"); - if (off > -1) - { - strftime(tmpstr,10,"%d",timeinfo); - _packet[off]=tmpstr[0]; - _packet[off+1]=tmpstr[1]; - } - - // day of month with no leading zero - %e - off = Packet::GetOffsetOfSubstition("%e"); - if (off > -1) - { - #ifndef WIN32 - strftime(tmpstr,10,"%e",timeinfo); - _packet[off]=tmpstr[0]; - #else - strftime(tmpstr,10,"%d",timeinfo); - if (tmpstr[0] == '0') - _packet[off]=' '; - else - _packet[off]=tmpstr[0]; - #endif - _packet[off+1]=tmpstr[1]; - } - - // month number with leading 0 - %m - off = Packet::GetOffsetOfSubstition("%m"); - if (off > -1) - { - strftime(tmpstr,10,"%m",timeinfo); - _packet[off]=tmpstr[0]; - _packet[off+1]=tmpstr[1]; - } - - // 2 digit year - %y - off = Packet::GetOffsetOfSubstition("%y"); - if (off > -1) - { - strftime(tmpstr,10,"%y",timeinfo); - _packet[off]=tmpstr[0]; - _packet[off+1]=tmpstr[1]; - } - - // hours - %H - off = Packet::GetOffsetOfSubstition("%H"); - if (off > -1) - { - strftime(tmpstr,10,"%H",timeinfo); - _packet[off]=tmpstr[0]; - _packet[off+1]=tmpstr[1]; - } - - // minutes - %M - off = Packet::GetOffsetOfSubstition("%M"); - if (off > -1) - { - strftime(tmpstr,10,"%M",timeinfo); - _packet[off]=tmpstr[0]; - _packet[off+1]=tmpstr[1]; - } - - // seconds - %S - off = Packet::GetOffsetOfSubstition("%S"); - if (off > -1) - { - strftime(tmpstr,10,"%S",timeinfo); - _packet[off]=tmpstr[0]; - _packet[off+1]=tmpstr[1]; - } - - Parity(13); // apply parity to the text of the header + // substitutions already done in HeaderText } - else if (_coding == CODING_7BIT_TEXT) // Other text rows + else if (_row < 26 && _coding == CODING_7BIT_TEXT) // Other text rows { for (int i=5;i<45;i++) _packet[i] &= 0x7f; // strip parity bits off // ======= TEMPERATURE ======== @@ -363,13 +274,7 @@ std::array* Packet::tx() strftime(tmpstr, 21, "\x02%a %d %b\x03%H:%M/%S", timeinfo); std::copy_n(tmpstr,20,_packet.begin() + off); } - // ======= VERSION ======== - // %%%%%V version number eg. v2.0.0 - off = Packet::GetOffsetOfSubstition("%%%%%V"); - if (off > -1) - { - std::copy_n(VBIT2_VERSION,6,_packet.begin() + off); - } + Parity(5); // redo the parity because substitutions will need processing } @@ -378,7 +283,7 @@ std::array* Packet::tx() /** A header has mag, row=0, page, flags, caption and time */ -void Packet::Header(uint8_t mag, uint8_t page, uint16_t subcode, uint16_t control) +void Packet::Header(uint8_t mag, uint8_t page, uint16_t subcode, uint16_t control, std::string text) { uint8_t cbit; SetMRAG(mag,0); @@ -415,14 +320,132 @@ void Packet::Header(uint8_t mag, uint8_t page, uint16_t subcode, uint16_t contro // if (control & 0x0040) cbit|=0x01; // C11 serial/parallel *** We only work in parallel mode, Serial would mean a different packet ordering. _packet[12]=Hamming8EncodeTable[cbit]; // C11 to C14 (C11=0 is parallel, C12,C13,C14 language) - _page=page; -} - -void Packet::HeaderText(std::string val) -{ _isHeader=true; // Because it must be a header - val.resize(32); - std::copy_n(val.begin(),32,_packet.begin() + 13); + text.resize(32); + std::copy_n(text.begin(),32,_packet.begin() + 13); + + // perform the header template substitutions for page number, date, etc. + + // get master clock singleton + vbit::MasterClock *mc = mc->Instance(); + time_t t = mc->GetMasterClock(); + + // Get local time + struct tm * timeinfo; + timeinfo=localtime(&t); + + char tmpstr[] = " "; + int off; + + // mpp page number - %%# + off = Packet::GetOffsetOfSubstition("%%#"); + if (off > -1) + { + if (_mag==0) + _packet[off]='8'; + else + _packet[off]=_mag+'0'; + _packet[off+1]=page/0x10+'0'; + if (_packet[off+1]>'9') + _packet[off+1]=_packet[off+1]-'0'-10+'A'; // Particularly poor hex conversion algorithm + + _packet[off+2]=page%0x10+'0'; + if (_packet[off+2]>'9') + _packet[off+2]=_packet[off+2]-'0'-10+'A'; // Particularly poor hex conversion algorithm + } + + // day name - %%a + off = Packet::GetOffsetOfSubstition("%%a"); + if (off > -1) + { + strftime(tmpstr,10,"%a",timeinfo); + _packet[off]=tmpstr[0]; + _packet[off+1]=tmpstr[1]; + _packet[off+2]=tmpstr[2]; + } + + // month name - %%b + off = Packet::GetOffsetOfSubstition("%%b"); + if (off > -1) + { + strftime(tmpstr,10,"%b",timeinfo); + _packet[off]=tmpstr[0]; + _packet[off+1]=tmpstr[1]; + _packet[off+2]=tmpstr[2]; + } + + // day of month with leading zero - %d + off = Packet::GetOffsetOfSubstition("%d"); + if (off > -1) + { + strftime(tmpstr,10,"%d",timeinfo); + _packet[off]=tmpstr[0]; + _packet[off+1]=tmpstr[1]; + } + + // day of month with no leading zero - %e + off = Packet::GetOffsetOfSubstition("%e"); + if (off > -1) + { + #ifndef WIN32 + strftime(tmpstr,10,"%e",timeinfo); + _packet[off]=tmpstr[0]; + #else + strftime(tmpstr,10,"%d",timeinfo); + if (tmpstr[0] == '0') + _packet[off]=' '; + else + _packet[off]=tmpstr[0]; + #endif + _packet[off+1]=tmpstr[1]; + } + + // month number with leading 0 - %m + off = Packet::GetOffsetOfSubstition("%m"); + if (off > -1) + { + strftime(tmpstr,10,"%m",timeinfo); + _packet[off]=tmpstr[0]; + _packet[off+1]=tmpstr[1]; + } + + // 2 digit year - %y + off = Packet::GetOffsetOfSubstition("%y"); + if (off > -1) + { + strftime(tmpstr,10,"%y",timeinfo); + _packet[off]=tmpstr[0]; + _packet[off+1]=tmpstr[1]; + } + + // hours - %H + off = Packet::GetOffsetOfSubstition("%H"); + if (off > -1) + { + strftime(tmpstr,10,"%H",timeinfo); + _packet[off]=tmpstr[0]; + _packet[off+1]=tmpstr[1]; + } + + // minutes - %M + off = Packet::GetOffsetOfSubstition("%M"); + if (off > -1) + { + strftime(tmpstr,10,"%M",timeinfo); + _packet[off]=tmpstr[0]; + _packet[off+1]=tmpstr[1]; + } + + // seconds - %S + off = Packet::GetOffsetOfSubstition("%S"); + if (off > -1) + { + strftime(tmpstr,10,"%S",timeinfo); + _packet[off]=tmpstr[0]; + _packet[off+1]=tmpstr[1]; + } + + Parity(13); // apply parity to the text of the header } /** @@ -443,15 +466,16 @@ void Packet::Fastext(int* links, int mag) { unsigned long nLink; uint8_t p=5; + _packet[p++]=Hamming8EncodeTable[0]; // Designation code 0 mag&=0x07; // Mask the mag just in case. Keep it valid // add the link control byte. This will allow row 24 to show. _packet[42]=Hamming8EncodeTable[0x0f]; - // and the page CRC - _packet[43]=Hamming8EncodeTable[0]; // can't calculate this correctly while we have the substitutions in tx() - _packet[44]=Hamming8EncodeTable[0]; + // and a blank page CRC - this is set later by Packet::SetX27CRC + _packet[43]=0x00; + _packet[44]=0x00; // for each of the six links for (uint8_t i=0; i<6; i++) @@ -571,6 +595,7 @@ int Packet::IDLA(uint8_t datachannel, uint8_t flags, uint8_t ial, uint32_t spa, void Packet::IDLcrc(uint16_t *crc, uint8_t data) { + // Perform the IDL A crc *crc ^= data; for (uint8_t i = 0; i < 8; i++) @@ -665,3 +690,34 @@ void Packet::Hamming24EncodeTriplet(uint8_t index, uint32_t triplet) P6 = 0x80 & ((Hamming24ParityTable[0][Byte_0] ^ Hamming24ParityTable[0][D5_D11]) << 2); _packet[index*3+5] = D12_D18 | P6; } + +uint16_t Packet::PacketCRC(uint16_t crc) +{ + int i; + uint16_t tempcrc = crc; + + if (_isHeader) + { + for (i=13; i<37; i++) + PageCRC(&tempcrc, _packet[i]); // calculate CRC for header text + } + else if (_row < 26) + { + for (i=5; i<45; i++) + PageCRC(&tempcrc, _packet[i]); // calculate CRC for text rows + } + + return tempcrc; +} + +void Packet::PageCRC(uint16_t *crc, uint8_t byte) +{ + // perform the teletext page CRC + uint8_t b; + + for (int i = 0; i < 8; i++) + { + b = ((byte >> (7-i)) & 1) ^ ((*crc>>6) & 1) ^ ((*crc>>8) & 1) ^ ((*crc>>11) & 1) ^ ((*crc>>15) & 1); + *crc = b | ((*crc&0x7FFF)<<1); + } +} diff --git a/packet.h b/packet.h index 28d64b4..3c22549 100644 --- a/packet.h +++ b/packet.h @@ -58,19 +58,13 @@ namespace vbit void SetMRAG(uint8_t mag, uint8_t row); /** Header - * Sets everything except the caption * @param mag 0..7 (where 0 is mag 8) * @param page number 00..ff * @param subcode (16 bit hex code as in tti file) * @param control C bits + * @param text header template */ - void Header(uint8_t mag, uint8_t page, uint16_t subcode, uint16_t control); - - /** HeaderText - * Sets last 32 bytes. This is the caption part - * @param val String of exactly 32 characters. - */ - void HeaderText(std::string val); + void Header(uint8_t mag, uint8_t page, uint16_t subcode, uint16_t control, std::string text); /** Parity * Sets the parity of the bytes starting from offset @@ -120,6 +114,18 @@ namespace vbit * @param coding - */ void SetRow(int mag, int row, std::string val, PageCoding coding); + + /** PacketCRC + * Set the 16 byte CRC in X/27/0 packets + * @param crc intial crc value + */ + void SetX27CRC(uint16_t crc); + + /** PacketCRC + * @return result of applying teletext page CRC to packet + * @param crc intial crc value + */ + uint16_t PacketCRC(uint16_t crc); protected: @@ -127,7 +133,6 @@ namespace vbit std::array _packet; // 45 byte packet bool _isHeader; //* pageSet, ttx::Config _pageSet(pageSet), _configure(configure), _page(nullptr), + _subpage(nullptr), _magNumber(mag), _priority(priority), _priorityCount(priority), @@ -50,6 +51,8 @@ Packet* PacketMag::GetPacket(Packet* p) unsigned int thisSubcode; int* links=NULL; bool updatedFlag=false; + + static vbit::Packet* TempPacket=new vbit::Packet(8,25," "); // a temporary packet for checksum calculation // We should only call GetPacket if IsReady has returned true @@ -103,17 +106,13 @@ Packet* PacketMag::GetPacket(Packet* p) } if (_page->IsCarousel()) - { - _status = _page->GetCarouselPage()->GetPageStatus() & 0x8000; // get transmit flag - _region = _page->GetCarouselPage()->GetRegion(); - thisSubcode = (_page->GetCarouselPage()->GetSubCode() & 0x000F) | (_page->GetCarouselPage()->GetLastPacket() << 8); - } + _subpage = _page->GetCarouselPage(); else - { - _status = _page->GetPageStatus() & 0x8000; // get transmit flag - _region = _page->GetRegion(); - thisSubcode = (_page->GetSubCode() & 0x000F) | (_page->GetLastPacket() << 8); - } + _subpage = _page; + + _status = _subpage->GetPageStatus() & 0x8000; // get transmit flag + _region = _subpage->GetRegion(); + thisSubcode = (_subpage->GetSubCode() & 0x000F) | (_subpage->GetLastPacket() << 8); thisSubcode |= _page->GetUpdateCount() << 4; @@ -154,8 +153,7 @@ Packet* PacketMag::GetPacket(Packet* p) if (_page == nullptr) { // couldn't get a page to send so sent a time filling header - p->Header(_magNumber,0xFF,0x0000,0x8010); - p->HeaderText(_headerTemplate); // Placeholder 32 characters. This gets replaced later + p->Header(_magNumber,0xFF,0x0000,0x8010,_headerTemplate); _waitingForField = 2; // enforce 20ms page erasure interval return p; } @@ -176,14 +174,19 @@ Packet* PacketMag::GetPacket(Packet* p) // clear any ERASE bit if page hasn't cycled to minimise flicker, and the interrupted status bit _status=_page->GetCarouselPage()->GetPageStatus() & ~(PAGESTATUS_C4_ERASEPAGE | PAGESTATUS_C9_INTERRUPTED); } - thisSubcode=_page->GetCarouselPage()->GetSubCode(); - _region=_page->GetCarouselPage()->GetRegion(); + + _subpage = _page->GetCarouselPage(); + + thisSubcode=_subpage->GetSubCode(); + _region=_subpage->GetRegion(); } else { - thisSubcode=_page->GetSubCode(); - _status=_page->GetPageStatus(); - _region=_page->GetRegion(); + _subpage = _page; + + thisSubcode=_subpage->GetSubCode(); + _status=_subpage->GetPageStatus(); + _region=_subpage->GetRegion(); } // Handle pages with update bit set in a useful way. @@ -191,7 +194,7 @@ Packet* PacketMag::GetPacket(Packet* p) if (_status & PAGESTATUS_C8_UPDATE) { // Clear update bit in stored page so that update flag is only transmitted once - _page->SetPageStatus(_status & ~PAGESTATUS_C8_UPDATE); + _subpage->SetPageStatus(_status & ~PAGESTATUS_C8_UPDATE); // Also set the erase flag in output. This will allow left over rows in adaptive transmission to be cleared without leaving the erase flag set causing flickering. _status|=PAGESTATUS_C4_ERASEPAGE; @@ -218,21 +221,31 @@ Packet* PacketMag::GetPacket(Packet* p) // clear a flag we use to prevent duplicated X/28/0 packets _hasX28Region = false; - p->Header(_magNumber,thisPageNum,thisSubcode,_status);// loads of stuff to do here! + p->Header(_magNumber,thisPageNum,thisSubcode,_status,_headerTemplate);// loads of stuff to do here! - p->HeaderText(_headerTemplate); // Placeholder 32 characters. This gets replaced later + uint16_t tempCRC = p->PacketCRC(0); // calculate the crc of the new header - // don't apply parity here it will screw up the template. parity for the header is done by tx() later - assert(p!=NULL); - - if (_page->IsCarousel()) + if (_subpage->HasHeaderChanged(tempCRC) || updatedFlag) { - links=_page->GetCarouselPage()->GetLinkSet(); - } - else - { - links=_page->GetLinkSet(); + // the content of the header has changed or the page has been reloaded + // we must now CRC the whole page + + for (int i=1; i<26; i++) + { + TempPacket->SetRow(_magNumber, _thisRow, _subpage->GetRow(i)->GetLine(), _subpage->GetPageCoding()); + tempCRC = TempPacket->PacketCRC(tempCRC); + } + + _subpage->SetPageCRC(tempCRC); + + // TODO: the page content may get modified by substitutions in Packet::tx() which will result in an invalid checksum + + //std::cerr << "[PacketMag::GetPacket] Calculated page CRC " << std::hex << tempCRC << " for page " << std::hex << _subpage->GetPageNumber() << std::endl; } + + assert(p!=NULL); + + links=_subpage->GetLinkSet(); if ((links[0] & links[1] & links[2] & links[3] & links[4] & links[5]) != 0x8FF) // only create if links were initialised { _state=PACKETSTATE_FASTEXT; @@ -252,7 +265,10 @@ Packet* PacketMag::GetPacket(Packet* p) if ((_lastTxt->GetLine()[0] & 0xF) > 3) // designation codes > 3 p->SetRow(_magNumber, 27, _lastTxt->GetLine(), CODING_13_TRIPLETS); // enhancement linking else - p->SetRow(_magNumber, 27, _lastTxt->GetLine(), CODING_HAMMING_8_4); // navigation packets (TODO: CRC in DC=0 is wrong) + { + p->SetRow(_magNumber, 27, _lastTxt->GetLine(), CODING_HAMMING_8_4); // navigation packets + p->SetX27CRC(_subpage->GetPageCRC()); + } _lastTxt=_lastTxt->GetNextLine(); break; } @@ -371,15 +387,9 @@ Packet* PacketMag::GetPacket(Packet* p) case PACKETSTATE_FASTEXT: { p->SetMRAG(_magNumber,27); - if (_page->IsCarousel()) - { - links=_page->GetCarouselPage()->GetLinkSet(); - } - else - { - links=_page->GetLinkSet(); - } + links=_subpage->GetLinkSet(); p->Fastext(links,_magNumber); + p->SetX27CRC(_subpage->GetPageCRC()); _lastTxt=_page->GetTxRow(27); // Get _lastTxt ready for packet 27 processing _state=PACKETSTATE_PACKET27; // makes no attempt to prevent an FL row and an X/27/0 both being sent break; diff --git a/packetmag.h b/packetmag.h index 769c051..0ee1a12 100644 --- a/packetmag.h +++ b/packetmag.h @@ -57,6 +57,7 @@ namespace vbit std::list* _pageSet; //!< Member variable "_pageSet" ttx::Configure* _configure; TTXPageStream* _page; //!< The current page being output + TTXPage* _subpage; // pointer to the actual subpage int _magNumber; //!< The number of this magazine. (where 0 is mag 8) uint8_t _priority; //!< Priority of transmission where 1 is highest diff --git a/packetsubtitle.cpp b/packetsubtitle.cpp index eb9ce7a..9a86c15 100644 --- a/packetsubtitle.cpp +++ b/packetsubtitle.cpp @@ -44,9 +44,8 @@ Packet* PacketSubtitle::GetPacket(Packet* p) status|=PAGESTATUS_C8_UPDATE; _C8Flag=false; } - p->Header(mag, page, 0, status); // Create the header + p->Header(mag, page, 0, status, "XENOXXX INDUSTRIES CLOCK"); // Create the header } - p->HeaderText("XENOXXX INDUSTRIES CLOCK"); // Only Jason will see this if he decodes a tape. ClearEvent(EVENT_FIELD); _state=SUBTITLE_STATE_TEXT_ROW; _rowCount=1; // Set up iterator for page rows diff --git a/ttxpage.cpp b/ttxpage.cpp index 7fa8bd6..700409e 100644 --- a/ttxpage.cpp +++ b/ttxpage.cpp @@ -33,7 +33,9 @@ TTXPage::TTXPage() : m_sourcepage("none"), //ctor m_subcode(0), m_Loaded(false), - _Selected(false) + _Selected(false), + _headerCRC(0), + _pageCRC(0) { m_Init(); } @@ -50,7 +52,9 @@ TTXPage::TTXPage(std::string filename) : m_sourcepage(filename), m_subcode(0), m_Loaded(false), - _Selected(false) + _Selected(false), + _headerCRC(0), + _pageCRC(0) { m_Init(); // Careful! We should move inits to the initialisation list and call the default constructor @@ -345,7 +349,7 @@ TTXLine* TTXPage::GetRow(unsigned int row) } TTXLine* line=m_pLine[row]; // Don't create row 0, or enhancement rows as they are special. - if (line==nullptr && row>0 && row<25) + if (line==nullptr && row>0 && row<26) line=m_pLine[row]=new TTXLine(" "); return line; } @@ -644,4 +648,16 @@ PageCoding TTXPage::ReturnPageCoding(int pageCoding) case 5: return CODING_PER_PACKET; } -} \ No newline at end of file +} + +bool TTXPage::HasHeaderChanged(uint16_t crc) +{ + if (_headerCRC != crc) + { + // update stored CRC and signal change + _headerCRC = crc; + return true; + } + + return false; // no change +} diff --git a/ttxpage.h b/ttxpage.h index 540a78b..b45eb44 100644 --- a/ttxpage.h +++ b/ttxpage.h @@ -203,6 +203,11 @@ class TTXPage void SetSelected(bool value){_Selected=value;}; /// Set the selected state to value bool Selected(){return _Selected;}; /// Return the selected state + bool HasHeaderChanged(uint16_t crc); + + void SetPageCRC(uint16_t crc){_pageCRC = crc;}; // update the stored crc + uint16_t GetPageCRC(){return _pageCRC;}; // retrieve the stored crc + protected: bool m_LoadTTI(std::string filename); int m_cycletimeseconds; // CT @@ -227,6 +232,9 @@ class TTXPage bool _Selected; /// True if this page has been selected. bool _fileChanged; // page was reloaded by the filemonitor + uint16_t _headerCRC; // holds the last calculated CRC of the page header + uint16_t _pageCRC; // holds the calculated CRC of the page + // Private functions void m_Init();