Full Code of peterkvt80/vbit2 for AI

master 7573ef8e57d5 cached
65 files
336.2 KB
81.5k tokens
64 symbols
1 requests
Download .txt
Showing preview only (354K chars total). Download the full file or copy to clipboard to get everything.
Repository: peterkvt80/vbit2
Branch: master
Commit: 7573ef8e57d5
Files: 65
Total size: 336.2 KB

Directory structure:
gitextract_a2zf_04h/

├── .editorconfig
├── .gitattributes
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── carousel.cpp
├── carousel.h
├── configure.cpp
├── configure.h
├── debug.cpp
├── debug.h
├── example-vbit.conf
├── filemonitor.cpp
├── filemonitor.h
├── getvbit2
├── interfaceServer.cpp
├── interfaceServer.h
├── masterClock.h
├── normalpages.cpp
├── normalpages.h
├── packet.cpp
├── packet.h
├── packet830.cpp
├── packet830.h
├── packetDatacast.cpp
├── packetDatacast.h
├── packetDebug.cpp
├── packetDebug.h
├── packetServer.cpp
├── packetServer.h
├── packetmag.cpp
├── packetmag.h
├── packetsource.cpp
├── packetsource.h
├── page.cpp
├── page.h
├── pagelist.cpp
├── pagelist.h
├── postupdate.sh
├── scripts/
│   ├── config.py
│   ├── known_services.json
│   ├── runvbit2.py
│   ├── teletext-update.py
│   └── vbit-config.py
├── service.cpp
├── service.h
├── specialpages.cpp
├── specialpages.h
├── tables.cpp
├── tables.h
├── teletext-update.service
├── teletext-update.timer
├── ttxline.cpp
├── ttxline.h
├── ttxpagestream.cpp
├── ttxpagestream.h
├── update.sh
├── updatedpages.cpp
├── updatedpages.h
├── vbit2.cpp
├── vbit2.h
├── vbit2.service
└── version.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true

# 4 space indentation
[*]
indent_style = space
indent_size = 4

================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto

# Custom for Visual Studio
*.cs     diff=csharp

# Standard to msysgit
*.doc	 diff=astextplain
*.DOC	 diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot  diff=astextplain
*.DOT  diff=astextplain
*.pdf  diff=astextplain
*.PDF	 diff=astextplain
*.rtf	 diff=astextplain
*.RTF	 diff=astextplain


================================================
FILE: .gitignore
================================================
# g++ temporary files
*.o
*.bak
*.bak


================================================
FILE: Dockerfile
================================================
# make the buil container
FROM alpine as build-env
RUN apk add --no-cache build-base
WORKDIR /vbit2
# copy everything from source dir into build container
COPY . .
RUN make -j8

# make the runtime container
FROM alpine
RUN apk update && apk add --no-cache libstdc++ libgcc tzdata
COPY --from=build-env /vbit2/vbit2 /vbit2/vbit2
WORKDIR /vbit2
CMD ["/vbit2/vbit2"]

# build:
# docker build -t vbit2 .

# run:
# docker run -it --mount type=bind,source="/path/to/pages,target=/vbit2/pages,readonly vbit2


================================================
FILE: LICENSE
================================================
Copyright (C) 2025, Peter Kwan, Alistair Cree, Alistair Buxton

Permission to use, copy, modify, and distribute this software
and its documentation for any purpose and without fee is hereby
granted, provided that the above copyright notice appear in all
copies and that both that the copyright notice and this
permission notice and warranty disclaimer appear in supporting
documentation, and that the names of the authors not be used in
advertising or publicity pertaining to distribution of the
software without specific, written prior permission.

The authors disclaim all warranties with regard to this
software, including all implied warranties of merchantability
and fitness.  In no event shall the authors be liable for any
special, indirect or consequential damages or any damages
whatsoever resulting from loss of use, data or profits, whether
in an action of contract, negligence or other tortious action,
arising out of or in connection with the use or performance of
this software.


================================================
FILE: Makefile
================================================
CXX=g++

CXXFLAGS = -g -O2 -Wall -MMD -MP -std=gnu++11 -fstack-protector-all -Wextra -I.

LIBS = -lpthread -fstack-protector -s

ifeq ($(OS),Windows_NT)
	CXXFLAGS += -DWIN32
	LIBS += -lwsock32 -static
else
	ifeq ($(shell test -e /etc/os-release && echo -n yes),yes)
		ifeq ($(shell if [ `grep -c raspbian /etc/os-release` -gt 0 ]; then echo true ; else echo false ; fi), true)
			CXXFLAGS += -DRASPBIAN
		endif
	endif
endif

srcs = $(wildcard *.cpp)
objs = $(srcs:.cpp=.o)
deps = $(srcs:.cpp=.d)

vbit2: $(objs)
	$(CXX) -o $@ $^ $(LIBS)

%.o: %.c $(deps)
	$(CXX) -c $< -o $@

#Cleanup
.PHONY: clean

clean:
	rm -f $(objs) $(deps) vbit2

-include $(deps)

all: vbit2


================================================
FILE: README.md
================================================
# VBIT2

An installation guide and more can be found in the [github wiki](https://github.com/peterkvt80/vbit2/wiki).

## About

This program takes a set of teletext [page files](https://github.com/peterkvt80/vbit2/wiki/Page-files) and generates a feature rich transmission stream on stdout.

The transmission stream can be piped to raspi-teletext or any other application that needs a teletext packet stream.
It is a console application that can be compiled for Raspberry Pi or Windows.

It generates a T42 teletext stream that can be piped to [raspi-teletext](https://github.com/ali1234/raspi-teletext) to add a teletext signal to the Raspberry Pi composite output, [vbit-py](https://github.com/peterkvt80/vbit-py) to drive a Vbit teletext inserter board, or into the [vbit-iv](https://github.com/peterkvt80/vbit-iv) in-vision renderer.

VBIT2 can also optionally generate output an mpeg transport stream containing DVB-TXT for merging into a digital television stream.

## Features

VBIT2 includes the following features:
* Parallel mode transmission of teletext magazines.
* Cycling subpage carousels.
* Level 1.5 and 2.5 features including dynamic character and object downloading pages and Packet 29 insertion.
* Format 1 Broadcast Service Data Packet generation with automatic daylight saving time.
* Fastext and TOP navigation support.
* IDL Format A data broadcast transmission.
* TCP control interface for live manipulation of teletext pages and service parameters.


================================================
FILE: carousel.cpp
================================================

#include "carousel.h"

using namespace vbit;

Carousel::Carousel(int mag, PageList *pageList, Debug *debug) :
    _mag(mag),
    _pageList(pageList),
    _debug(debug)
{
    //ctor
}

Carousel::~Carousel()
{
    //dtor
}

void Carousel::addPage(std::shared_ptr<TTXPageStream> p)
{
    // @todo Don't allow duplicate entries
    p->SetCarouselFlag(true);
    int t = 1;
    if (std::shared_ptr<Subpage> s = p->GetSubpage())
        t += s->GetCycleTime();
    
    p->SetTransitionTime(t);
    _carouselList.push_front(p);
}

std::shared_ptr<TTXPageStream> Carousel::nextCarousel()
{
    std::shared_ptr<TTXPageStream> p;
    if (_carouselList.size()==0) return nullptr;
    
    for (std::list<std::shared_ptr<TTXPageStream>>::iterator it=_carouselList.begin();it!=_carouselList.end();++it)
    {
        p=*it;
        if (p->GetOneShotFlag())
        {
            p->SetCarouselFlag(false);
            _carouselList.erase(it--);
            continue;
        }
        
        if (p->GetLock()) // try to lock this page against changes
        {
            if (p->GetIsMarked() && p->GetCarouselFlag()) // only remove it once
            {
                std::stringstream ss;
                ss << "[Carousel::nextCarousel] Deleted " << std::hex << (p->GetPageNumber());
                _debug->Log(Debug::LogLevels::logINFO,ss.str());
                
                p->SetCarouselFlag(false);
                _carouselList.erase(it--);
                
                _pageList->RemovePage(p); // try to remove it from the pagelist immediately - will free the lock
                continue; // jump back to loop
            }
            else if ((!(p->IsCarousel())) || p->Special())
            {
                std::stringstream ss;
                ss << "[Carousel::nextCarousel] no longer a carousel " << std::hex << (p->GetPageNumber());
                _debug->Log(Debug::LogLevels::logINFO,ss.str());
                
                p->SetCarouselFlag(false);
                _carouselList.erase(it--);
            }
            else
            {
                if (p->Expired())
                {
                    // We found a carousel that is ready to step
                    if (std::shared_ptr<Subpage> s = p->GetSubpage()) // make sure there is a subpage
                    {
                        if (s->GetSubpageStatus() & PAGESTATUS_C9_INTERRUPTED)
                        {
                            // carousel should go out now out of sequence
                            return p; // return page locked
                        }
                    }
                }
            }
            
            p->FreeLock(); // unlock
        }
    }
    return nullptr;
}


================================================
FILE: carousel.h
================================================
#ifndef _CAROUSEL_H
#define _CAROUSEL_H

#include <list>

#include "debug.h"
#include "ttxpagestream.h"
#include "pagelist.h"

/** Carousel maintains a list of carousel pages.
 *  nextCarousel() iterates through the list to find pages which have hard cycle timings
 */

namespace vbit
{

class Carousel
{
    public:
        /** Default constructor */
        Carousel(int mag, PageList *pageList, Debug *debug);
        /** Default destructor */
        virtual ~Carousel();

        /** Add a page to the list
         */
        void addPage(std::shared_ptr<TTXPageStream> p);

        /** Find the next carousel page that needs to be transmitted
         *  @return The next carousel if it is time to go or NULL
         */
        std::shared_ptr<TTXPageStream> nextCarousel();


    protected:

    private:
        int _mag;
        PageList* _pageList;
        Debug* _debug;

        std::list<std::shared_ptr<TTXPageStream>> _carouselList; /// The list of carousel pages
};

}

#endif // _CAROUSEL_H


================================================
FILE: configure.cpp
================================================
/** Configure
 */
#include "configure.h"

using namespace vbit;

int Configure::DirExists(std::string *path)
{
    struct stat info;

    if(stat(path->c_str(), &info ) != 0)
        return 0;
    else if(info.st_mode & S_IFDIR)
        return 1;
    else
        return 0;
}

Configure::Configure(Debug *debug, int argc, char** argv) :
    _debug(debug),
    // settings for generation of packet 8/30
    _initialMag(1),
    _initialPage(0x00),
    _initialSubcode(0x3F7F),
    _NetworkIdentificationCode(0x0000),
    _CountryNetworkIdentificationCode(0x0000),
    _reservedBytes{0x15, 0x15, 0x15, 0x15}, // initialise reserved bytes to hamming 8/4 encoded 0
    _serviceStatusString(20, ' ')
{
    _configFile = CONFIGFILE;
    
#ifdef RASPBIAN
    _pageDir = "/home/pi/teletext";
#else
    _pageDir = "./pages"; // a relative path as a sensible default
#endif
    // This is where the default header template is defined.
    _headerTemplate = "VBIT2    %%# %%a %d %%b" "\x03" "%H:%M:%S";
    
    _reverseBits = false;

    _rowAdaptive = false;
    _linesPerField = 16; // default to 16 lines per field
    _datacastLines = 0; // no dedicated datacast lines

    _multiplexedSignalFlag = false; // using this would require changing all the line counting and a way to send full field through raspi-teletext - something for the distant future when everything else is done...
    
    _OutputFormat = T42; // t42 output is the default behaviour
    
    _PID = 0x20; // default PID is 0x20
    
    _packetServerPort = 0; // port 0 disables packet server
    _packetServerMaxClients = 5; // default to 5 connection limit
    _interfaceServerPort = 0; // port 0 disables interface server
    _interfaceServerMaxClients = 5; // default to 5 connection limit
    
    uint8_t priority[8]={9,3,3,6,3,3,5,6}; // 1=High priority,9=low. Note: priority[0] is mag 8
    
    for (int i=0; i<8; i++)
        _magazinePriority[i] = priority[i];

    //Scan the command line for overriding the pages file.
    if (argc>1)
    {
        for (int i=1;i<argc;++i)
        {
            std::string arg = argv[i];
            if (arg == "--dir")
            {
                if (i + 1 < argc)
                    _pageDir = argv[++i];
                else
                {
                    std::cerr << "--dir requires an argument\n";
                    exit(EXIT_FAILURE);
                }
            }
            else if (arg == "--format")
            {
                if (i + 1 < argc)
                {
                    arg = argv[++i];
                    
                    if (arg == "none")
                    {
                        _OutputFormat = None;
                    }
                    else if (arg == "t42")
                    {
                        _OutputFormat = T42;
                    }
                    else if (arg == "raw")
                    {
                        _OutputFormat = Raw;
                    }
                    else if (arg == "ts")
                    {
                        _OutputFormat = TS;
                    }
                    else if (arg == "tsnpts")
                    {
                        _OutputFormat = TSNPTS;
                    }
                    else
                    {
                        std::cerr << "invalid --format type\n";
                        exit(EXIT_FAILURE);
                    }
                    
                    if (_reverseBits && _OutputFormat != T42)
                    {
                        std::cerr << "--reverse requires t42 format\n";
                        exit(EXIT_FAILURE);
                    }
                }
                else
                {
                    std::cerr << "--format requires an argument\n";
                    exit(EXIT_FAILURE);
                }
            }
            else if (arg == "--reverse")
            {
                _reverseBits = true;
                
                if (_OutputFormat != T42)
                {
                    std::cerr << "--reverse requires t42 format\n";
                    exit(EXIT_FAILURE);
                }
            }
            else if (arg == "--pid")
            {
                if (i + 1 < argc)
                {
                    std::istringstream ss(argv[++i]);
                    ss >> _PID;
                    if (_PID < 0x20 || _PID >= 0x1FFF || !ss.eof())
                    {
                        std::cerr << "invalid PID\n";
                        exit(EXIT_FAILURE);
                    }
                }
                else
                {
                    std::cerr << "--pid requires an argument\n";
                    exit(EXIT_FAILURE);
                }
            }
            else if (arg == "--reserved")
            {
                if (i + 1 < argc)
                {
                    // Take a 32 bit hexadecimal value to set the four reserved bytes in the BSDP
                    // Store bytes big endian so that the order digits appear on the command line is the same as they appear in packet
                    errno = 0;
                    char *end_ptr;
                    unsigned long l = std::strtoul(argv[++i], &end_ptr, 16);
                    if (errno == 0 && *end_ptr == '\0')
                    {
                        _reservedBytes[0] = (l >> 24) & 0xff;
                        _reservedBytes[1] = (l >> 16) & 0xff;
                        _reservedBytes[2] = (l >> 8) & 0xff;
                        _reservedBytes[3] = (l >> 0) & 0xff;
                    }
                    else
                    {
                        std::cerr << "invalid reserved bytes argument\n";
                        exit(EXIT_FAILURE);
                    }
                }
                else
                {
                    std::cerr << "--reserved requires an argument\n";
                    exit(EXIT_FAILURE);
                }
            }
            else if (arg == "--debug")
            {
                if (i + 1 < argc)
                {
                    errno = 0;
                    char *end_ptr;
                    long l = std::strtol(argv[++i], &end_ptr, 10);
                    if (errno == 0 && *end_ptr == '\0' && l > -1)
                    {
                        switch(l){
                            case 0:
                                _debug->SetDebugLevel(Debug::LogLevels::logNONE);
                                break;
                            case 1:
                                _debug->SetDebugLevel(Debug::LogLevels::logERROR);
                                break;
                            case 2:
                                _debug->SetDebugLevel(Debug::LogLevels::logWARN);
                                break;
                            case 3:
                                _debug->SetDebugLevel(Debug::LogLevels::logINFO);
                                break;
                            case 4:
                            default:
                                _debug->SetDebugLevel(Debug::LogLevels::logDEBUG);
                                break;
                        }
                        
                    }
                    else
                    {
                        std::cerr << "invalid debug level argument\n";
                        exit(EXIT_FAILURE);
                    }
                }
                else
                {
                    std::cerr << "--debug requires an argument\n";
                    exit(EXIT_FAILURE);
                }
            }
            else if (arg == "--packetserver")
            {
                if (i + 1 < argc)
                {
                    errno = 0;
                    char *end_ptr;
                    long l = std::strtol(argv[++i], &end_ptr, 10);
                    if (errno == 0 && *end_ptr == '\0' && l > 0 && l < 65536)
                    {
                        _packetServerPort = (int)l;
                    }
                    else
                    {
                        std::cerr << "invalid packetserver port number\n";
                        exit(EXIT_FAILURE);
                    }
                    
                    if (i + 1 < argc)
                    {
                        arg = argv[i+1];
                        if (arg.compare(0,2,"--"))
                        {
                            // optional max clients argument
                            errno = 0;
                            l = std::strtol(argv[++i], &end_ptr, 10);
                            if (errno == 0 && *end_ptr == '\0' && l > -1 && l < 65536)
                            {
                                _packetServerMaxClients = (uint16_t)l;
                            }
                            else
                            {
                                std::cerr << "invalid packetserver max clients argument\n";
                                exit(EXIT_FAILURE);
                            }
                        }
                    }
                }
                else
                {
                    std::cerr << "--packetserver requires a port number\n";
                    exit(EXIT_FAILURE);
                }
            }
            else if (arg == "--interface")
            {
                if (i + 1 < argc)
                {
                    errno = 0;
                    char *end_ptr;
                    long l = std::strtol(argv[++i], &end_ptr, 10);
                    if (errno == 0 && *end_ptr == '\0' && l > 0 && l < 65536)
                    {
                        _interfaceServerPort = (int)l;
                    }
                    else
                    {
                        std::cerr << "invalid interface port number\n";
                        exit(EXIT_FAILURE);
                    }
                    
                    if (i + 1 < argc)
                    {
                        arg = argv[i+1];
                        if (arg.compare(0,2,"--"))
                        {
                            // optional max clients argument
                            errno = 0;
                            l = std::strtol(argv[++i], &end_ptr, 10);
                            if (errno == 0 && *end_ptr == '\0' && l > -1 && l < 65536)
                            {
                                _interfaceServerMaxClients = (uint16_t)l;
                            }
                            else
                            {
                                std::cerr << "invalid interface max clients argument\n";
                                exit(EXIT_FAILURE);
                            }
                        }
                    }
                }
                else
                {
                    std::cerr << "--interface requires a port number\n";
                    exit(EXIT_FAILURE);
                }
            }
            else
            {
                std::cerr << "unrecognised argument: " << arg << std::endl;
                exit(EXIT_FAILURE);
            }
        }
    }
    
    if (!DirExists(&_pageDir))
    {
        std::stringstream ss;
        ss << _pageDir << " does not exist or is not a directory\n";
        std::cerr << ss.str();
        exit(EXIT_FAILURE);
    }
    
    // TODO: allow overriding config file from command line
    _debug->Log(Debug::LogLevels::logINFO,"[Configure::Configure] Pages directory is " + _pageDir);
    _debug->Log(Debug::LogLevels::logINFO,"[Configure::Configure] Config file is " + _configFile);
    
    std::string path;
    path = _pageDir;
    path += "/";
    path += _configFile;
    LoadConfigFile(path); // load main config file (vbit.conf)
    
    LoadConfigFile(path+".override"); // allow overriding main config file for local configuration where main config is in version control
    
    if (_datacastLines > _linesPerField)
    {
        _debug->Log(Debug::LogLevels::logERROR,"[Configure] datacast lines cannot be greater than lines per field");
        _datacastLines = _linesPerField; // clamp
    }
}

Configure::~Configure()
{
    
}

void Configure::SetHeaderTemplate(std::shared_ptr<TTXLine> line)
{
    std::string str = "";
    for (int i=8; i<40; i++)
        str += line->GetCharAt(i) & 0x7f;
    _headerTemplate.assign(str);
}

int Configure::LoadConfigFile(std::string filename)
{
    std::ifstream filein(filename.c_str()); // Open the file

    std::vector<std::string>::iterator iter;
    // these are all the valid strings for config lines
    std::vector<std::string> nameStrings{ "header_template", "initial_teletext_page", "row_adaptive_mode", "network_identification_code", "country_network_identification", "full_field", "status_display","lines_per_field","datacast_lines","magazine_priority"};

    if (filein.is_open())
    {
        _debug->Log(Debug::LogLevels::logINFO,"[Configure::LoadConfigFile] opened " + filename);

        std::string line;
        std::string name;
        std::string value;
        while (std::getline(filein >> std::ws, line))
        {
            if (line.front() != ';') // ignore comments
            { 
                std::size_t delim = line.find("=", 0);
                int error = 0;

                if (delim != std::string::npos)
                {
                    name = line.substr(0, delim);
                    value = line.substr(delim + 1);
                    iter = find(nameStrings.begin(), nameStrings.end(), name);
                    if(iter != nameStrings.end())
                    {
                        // matched string
                        switch(iter - nameStrings.begin())
                        {
                            case 0: // header_template
                            {
                                std::shared_ptr<TTXLine> header(new TTXLine("        "+value)); // use to process escape codes
                                SetHeaderTemplate(header);
                                break;
                            }
                            case 1: // initial_teletext_page
                            {
                                if (value.size() >= 3)
                                {
                                    size_t idx;
                                    int magpage;
                                    int subcode;
                                    try
                                    {
                                        magpage = stoi(std::string(value, 0, 3), &idx, 16);
                                    }
                                    catch (const std::invalid_argument& ia)
                                    {
                                        error = 1;
                                        break;
                                    }
                                    if (magpage < 0x100 || magpage > 0x8FF || (magpage & 0xFF) == 0xFF)
                                    {
                                        error = 1;
                                        break;
                                    }
                                    if (value.size() > 3)
                                    {
                                        if ((value.size() != 8) || (value.at(idx) != ':'))
                                        {
                                            error = 1;
                                            break;
                                        }
                                        try
                                        {
                                            subcode = stoi(std::string(value, 4, 4), &idx, 16);
                                        }
                                        catch (const std::invalid_argument& ia)
                                        {
                                            error = 1;
                                            break;
                                        }
                                        if (subcode < 0x0000 || subcode > 0x3F7F || subcode & 0xC080)
                                        {
                                            error = 1;
                                            break;
                                        }
                                        _initialSubcode = subcode;
                                    }
                                    _initialMag = magpage / 0x100;
                                    _initialPage = magpage % 0x100;
                                    break;
                                }
                                error = 1;
                                break;
                            }
                            case 2: // row_adaptive_mode
                            {
                                if (!value.compare("true"))
                                {
                                    _rowAdaptive = true;
                                }
                                else if (!value.compare("false"))
                                {
                                    _rowAdaptive = false;
                                }
                                else
                                {
                                    error = 1;
                                }
                                break;
                            }
                            case 3: // "network_identification_code" - four character hex. eg. FA6F
                            {
                                if (value.size() == 4)
                                {
                                    size_t idx;
                                    try
                                    {
                                        _NetworkIdentificationCode = stoi(std::string(value, 0, 4), &idx, 16);
                                    }
                                    catch (const std::invalid_argument& ia)
                                    {
                                        error = 1;
                                        break;
                                    }
                                }
                                else
                                {
                                    error = 1;
                                }
                                break;
                            }
                            case 4: // "country_network_identification" - four character hex. eg. 2C2F
                            {
                                if (value.size() == 4)
                                {
                                    size_t idx;
                                    try
                                    {
                                        _CountryNetworkIdentificationCode = stoi(std::string(value, 0, 4), &idx, 16);
                                    }
                                    catch (const std::invalid_argument& ia)
                                    {
                                        error = 1;
                                        break;
                                    }
                                }
                                else
                                {
                                    error = 1;
                                }
                                break;
                            }
                            case 5: // "full_field"
                            {
                                break;
                            }
                            case 6: // "status_display"
                            {
                                SetServiceStatusString(value);
                                break;
                            }
                            case 7: // "lines_per_field"
                            {
                                if (value.size() > 0 && value.size() < 4)
                                {
                                    try
                                    {
                                        _linesPerField = stoi(std::string(value, 0, 3));
                                    }
                                    catch (const std::invalid_argument& ia)
                                    {
                                        error = 1;
                                        break;
                                    }
                                }
                                else
                                {
                                    error = 1;
                                }
                                break;
                            }
                            case 8: // datacast_lines
                            {
                                if (value.size() > 0 && value.size() < 4)
                                {
                                    try
                                    {
                                        _datacastLines = stoi(std::string(value, 0, 3));
                                    }
                                    catch (const std::invalid_argument& ia)
                                    {
                                        error = 1;
                                        break;
                                    }
                                }
                                else
                                {
                                    error = 1;
                                }
                                break;
                            }
                            case 9: // "magazine_priority"
                            {
                                std::stringstream ss(value);
                                std::string temps;
                                int tmp[8];
                                int i;
                                for (i=0; i<8; i++)
                                {
                                    if (std::getline(ss, temps, ','))
                                    {
                                        try
                                        {
                                            tmp[i] = stoi(temps);
                                        }
                                        catch (const std::invalid_argument& ia)
                                        {
                                            error = 1;
                                            break;
                                        }
                                        if (!(tmp[i] > 0 && tmp[i] < 10)) // must be 1-9
                                        {
                                            error = 1;
                                            break;
                                        }
                                    }
                                    else
                                    {
                                        error = 1;
                                        break;
                                    }
                                }
                                for (i=0; i<8; i++)
                                    _magazinePriority[i] = tmp[i];
                                break;
                            }
                        }
                    }
                    else
                    {
                        error = 1; // unrecognised config line
                    }
                }
                if (error)
                {
                    _debug->Log(Debug::LogLevels::logERROR,"[Configure::LoadConfigFile] invalid config line: " + line);
                }
            }
        }
        filein.close();
        return 0;
    }
    else
    {
        _debug->Log(Debug::LogLevels::logWARN,"[Configure::LoadConfigFile] open failed");
        return -1;
    }
}


================================================
FILE: configure.h
================================================
#ifndef _CONFIGURE_H_
#define _CONFIGURE_H_
/** Configure processes settings related to the teletext service and vbit2 command line options.
 */

#include <iostream>
#include <fstream>
#include <sstream>
#include <stdint.h>
#include <cstring>
#include <sys/stat.h>
#include <vector>
#include <array>
#include <algorithm>
#include <stdexcept>
#include <memory>

#include "debug.h"
#include "ttxline.h"

#define CONFIGFILE "vbit.conf" // default config file name

namespace vbit

{
class Configure
{
    public:
        enum OutputFormat
        {
            None,
            T42,
            Raw,
            TS,
            TSNPTS
        };
        
        //Configure();
        /** Constructor can take overrides from the command line
         */
        Configure(Debug *debug, int argc=0, char** argv=NULL);
        ~Configure();
        
        inline std::string GetPageDirectory(){return _pageDir;};
        
        std::string GetHeaderTemplate(){return _headerTemplate;}
        void SetHeaderTemplate(std::shared_ptr<TTXLine> line);
        bool GetRowAdaptive(){return _rowAdaptive;}
        void SetRowAdaptive(bool flag){_rowAdaptive = flag;}
        std::string GetServiceStatusString(){return _serviceStatusString;}
        void SetServiceStatusString(std::string status){status.resize(20,' '); _serviceStatusString = status;}
        bool GetMultiplexedSignalFlag(){return _multiplexedSignalFlag;}
        uint16_t GetNetworkIdentificationCode(){return _NetworkIdentificationCode;}
        std::array<uint8_t, 4> GetReservedBytes(){return _reservedBytes;}
        void SetReservedBytes(std::array<uint8_t, 4> reserved){_reservedBytes = reserved;}
        uint8_t GetInitialMag(){return _initialMag;}
        uint8_t GetInitialPage(){return _initialPage;}
        uint16_t GetInitialSubcode(){return _initialSubcode;}
        uint16_t GetLinesPerField(){return _linesPerField;}
        uint16_t GetDatacastLines(){return _datacastLines;}
        bool GetReverseFlag(){return _reverseBits;}
        int GetMagazinePriority(uint8_t mag){return _magazinePriority[mag];}
        
        OutputFormat GetOutputFormat(){return _OutputFormat;}
        uint16_t GetTSPID(){return _PID;}
        
        uint16_t GetPacketServerPort(){return _packetServerPort;}
        bool GetPacketServerEnabled(){return _packetServerPort != 0;}
        uint16_t GetPacketServerMaxClients(){return _packetServerMaxClients;}
        
        uint16_t GetInterfaceServerPort(){return _interfaceServerPort;}
        bool GetInterfaceServerEnabled(){return _interfaceServerPort != 0;}
        uint16_t GetInterfaceServerMaxClients(){return _interfaceServerMaxClients;}
        
    private:
        Debug* _debug;
        int DirExists(std::string *path);
        
        int LoadConfigFile(std::string filename);
        
        // template string for generating header packets
        std::string _headerTemplate;
        
        bool _rowAdaptive;
        uint16_t _linesPerField;
        uint16_t _datacastLines;
        
        // settings for generation of packet 8/30
        bool _multiplexedSignalFlag; // false indicates teletext is multiplexed with video, true means full frame teletext.
        int _magazinePriority[8];
        uint8_t _initialMag;
        uint8_t _initialPage;
        uint16_t _initialSubcode;
        uint16_t _NetworkIdentificationCode;
        uint16_t _CountryNetworkIdentificationCode;
        std::array<uint8_t, 4> _reservedBytes; // four bytes which the teletext specification marks reserved
        std::string _serviceStatusString; /// 20 characters
        
        std::string _configFile; /// Configuration file name --config
        std::string _pageDir; /// Configuration file name --dir
        bool _reverseBits;
        
        OutputFormat _OutputFormat;
        uint16_t _PID;
        
        uint16_t _packetServerPort;
        uint16_t _packetServerMaxClients;
        uint16_t _interfaceServerPort;
        uint16_t _interfaceServerMaxClients;
    };
}

#endif


================================================
FILE: debug.cpp
================================================
#include <debug.h>

using namespace vbit;

Debug::Debug() :
    _debugLevel(logNONE)
{
    //ctor
    _magDurations.fill(-1);
    _magSizes.fill(0);
}

Debug::~Debug()
{
    //dtor
}

void Debug::Log(LogLevels level, std::string str)
{
    if (_debugLevel >= level){
        std::cerr << str + "\n"; // keep sending messages to stderr for now
    }
}

void Debug::SetMagCycleDuration(int mag, int duration)
{
    if (mag >= 0 && mag < 8)
    {
        _magDurations[mag] = duration;
        Log(logDEBUG, "[Debug::SetMagCycleDuration] Magazine " + std::to_string(mag) + " cycle duration: " + std::to_string(duration / 50) + " seconds, " + std::to_string(duration % 50) + " fields");
    }
}

void Debug::SetMagazineSize(int mag, int size)
{
    if (mag >= 0 && mag < 8)
    {
        _magSizes[mag] = (size<255)?size:255; // clamp erroneous sizes to 255
        Log(logDEBUG, "[Debug::SetMagazineSize] Magazine " + std::to_string(mag) + " size: " + std::to_string(size) + " pages");
    }
}


================================================
FILE: debug.h
================================================
/** debugging class
 */
#ifndef _DEBUG_H_
#define _DEBUG_H_

#include <iostream>
#include <array>

namespace vbit
{
    class Debug
    {
        public:
            enum LogLevels
            {
                logNONE,
                logERROR,
                logWARN,
                logINFO,
                logDEBUG
            };
            
            /** Default constructor */
            Debug();
            /** Default destructor */
            virtual ~Debug();
            
            void Log(LogLevels level, std::string str);
            void SetDebugLevel(LogLevels level){ _debugLevel = level; };
            LogLevels GetDebugLevel(){ return _debugLevel; };
            void SetMagCycleDuration(int mag, int duration);
            std::array<int,8> GetMagCycleDurations(){ return _magDurations; };
            void SetMagazineSize(int mag, int size);
            std::array<int,8> GetMagSizes(){ return _magSizes; };
            
        protected:

        private:
            LogLevels _debugLevel;
            std::array<int, 8> _magDurations;
            std::array<int, 8> _magSizes;
    };
}

#endif // _DEBUG_H_


================================================
FILE: example-vbit.conf
================================================
; example vbit config file
; lines beginning with a semicolon are comment lines
; settings are in the form name=value and there must be no whitespace.

;--------------------------------- HEADER ROW ---------------------------------
; header template value must be 32 teletext characters long.
; both 8-bit control codes and 7-bit tti escape sequences are supported.
; %%# is replaced by the page number
; time and date use a subset of posix strftime conversion specifier characters
; pre-padded with % symbols to the appropriate length.
; day name = %%a
; month name = %%b
; day number with leading zero = %d
; day number without leading zero = %e
; month number = %m
; 2 digit year = %y
; hour = %H
; minute = %M
; second = %S
; here are some historic examples of header rows:
;header_template=CEEFAX %%#  %%a %e %%b C%H:%M/%S
;header_template=CEEFAX 1 %%# %%a %d %%bC%H:%M/%S
;header_template=ORACLE %%# %%a%e %%b ITVB%H%M:%S
;header_template=ORACLE %%# %%a%e %%bCITV %H%M:%S
;header_template=Teletext on 4 %%# %%b%dC%H:%M:%S
;header_template=D]CTeletext G\%%# %%b%dC%H:%M:%S

; TEEFAX header
header_template=TEEFAX %%#  %%a %e %%b C%H:%M/%S

;---------------------------- INITIAL TELETEXT PAGE ---------------------------
; magazine, page, and subcode to transmit within broadcast data service packets
; magazine and page number are specified as you would on a teletext set.
; i.e. magazine 1-8 followed by two hex digits for example 100, 888, 19F, etc.
; the initial subcode can optionally be appended, separated by a colon.
;initial_teletext_page=100
;initial_teletext_page=100:3F7F

;------------------------------ SERVICE SETTINGS ------------------------------
; omit blank rows to increase transmission efficiency (defaults to false)
;row_adaptive_mode=false

; specify number of VBI lines per video field
;lines_per_field=16

; set the priority of each magazine. 1=highest priority, 9=lowest.
; eight comma separated values for magazines 8,1,2,3,4,5,6,7.
;magazine_priority=9,3,3,6,3,3,5,6

; 20 character status message for broadcast service data packet
status_display=TEEFAX

; 16 bit hexadecimal code assigned to network in ETSI TR 101 231
; defaults to 0000 (no NI code assigned)
;network_identification_code=0000

; 16 bit hexadecimal code assigned for PDC
;country_network_identification=0000


================================================
FILE: filemonitor.cpp
================================================
#include "filemonitor.h"

using namespace vbit;

File::File(std::string filename) :
    _page(new TTXPageStream()),
    _filename(filename),
    _fileStatus(NEW)
{
    LoadFile(filename);
}

void File::LoadFile(std::string filename)
{
    _loaded = false;
    if (filename.size() >= 4)
    {
        std::string ext = filename.substr(filename.size() - 4); // get last four characters of string
        
        if (ext == ".tti")
        {
            _loaded = LoadTTI(filename);
        }
        // else other types of file we might want to load in future
    }
}

bool File::LoadTTI(std::string filename)
{
    const std::string cmd[]={"DS","SP","DE","CT","PN","SC","PS","MS","OL","FL","RD","RE","PF"};
    const int cmdCount = 13; // There are 13 possible commands, maybe DT and RT too on really old files
    unsigned int lineNumber;
    int lines=0;
    // Open the file
    std::ifstream filein(filename.c_str());
    _page->ClearPage(); // reset to blank page
    char * ptr;
    unsigned int subcode;
    int pageNumber = 0;
    int pagestatus = 0;
    char timedmode = false;
    int cycletime = 1;
    int region = 0;
    char m;
    
    /* We're going to create subpages in parallel with the old system for a moment... */
    std::shared_ptr<Subpage> s = nullptr;
    
    for (std::string line; std::getline(filein, line, ','); )
    {
        // This shows the command code:
        bool found=false;
        for (int i=0;i<cmdCount && !found; i++)
        {
            if (!line.compare(cmd[i]))
            {
                found=true;
                switch (i)
                {
                    case 0 : // "DS"
                    case 1 : // "SP"
                    case 2 : // "DE"
                    case 7 : // "MS" - Mask
                    case 10 : // "RD" - not sure!
                    {
                        std::getline(filein, line); // consume line
                        break;
                    }
                    case 3 : // "CT" - Cycle time (seconds)
                    {
                        // CT,8,T
                        std::getline(filein, line, ',');
                        cycletime = (atoi(line.c_str()));
                        if (cycletime < 1)
                            cycletime = 1;
                        else if (cycletime > 255)
                            cycletime = 255;
                        
                        std::getline(filein, line);
                        timedmode = (line[0]=='T'?true:false);
                        
                        if (s != nullptr)
                        {
                            s->SetTimedMode(timedmode);
                            s->SetCycleTime(cycletime);
                        }
                        
                        break;
                    }
                    case 4 : // "PN" - Page Number mppss
                    {
                        // Where m=1..8
                        // pp=00 to ff (hex)
                        // ss=00 to 99 (decimal)
                        // PN,10000
                        
                        std::getline(filein, line);
                        if (!pageNumber) // use the first page number we see
                        {
                            if (line.length()<3) // Must have at least three characters for a page number
                                break;
                            m=line[0];
                            if (m<'1' || m>'8') // Magazine must be 1 to 8
                                break;
                            pageNumber=std::strtol(line.c_str(), &ptr, 16);
                            if (line.length()<5 && pageNumber<=0x8ff) // Page number without subpage? Shouldn't happen but you never know.
                            {
                                // leave it alone and hope for the best
                            }
                            else   // Normally has subpage digits, we don't care
                            {
                                pageNumber=(pageNumber & 0xfff00) >> 8;
                            }
                            
                            _page->SetPageNumber(pageNumber);
                        }
                        
                        s = std::shared_ptr<Subpage>(new Subpage()); // create a new subpage
                        // inherit settings from previous subpage
                        s->SetTimedMode(timedmode);
                        s->SetCycleTime(cycletime);
                        s->SetSubpageStatus(pagestatus);
                        s->SetRegion(region);
                        _page->AppendSubpage(s); // add it to the page

                        break;
                    }
                    case 5 : // "SC" - Subcode
                    {
                        // SC,0000
                        std::getline(filein, line);
                        subcode=std::strtol(line.c_str(), &ptr, 16);
                        
                        if (s != nullptr)
                            s->SetSubCode(subcode); // set subcode explicitly
                        
                        break;
                    }
                    case 6 : // "PS" - Page status flags
                    {
                        // PS,8000
                        std::getline(filein, line);
                        pagestatus = std::strtol(line.c_str(), &ptr, 16);
                        if (s != nullptr)
                            s->SetSubpageStatus(pagestatus);
                        
                        break;
                    }
                    case 8 : // "OL" - Output line
                    {
                        std::getline(filein, line, ',');
                        lineNumber=atoi(line.c_str());
                        std::getline(filein, line);
                        if (lineNumber>MAXROW) break;
                        if (s != nullptr)
                        {
                            std::shared_ptr<TTXLine> ttxline(new TTXLine(line));
                            s->SetRow(lineNumber,ttxline);
                            lines++;
                        }
                        
                        // check for and decode OL,28 page function and coding
                        if (lineNumber == 28 && line.length() >= 40)
                        {
                            uint8_t dc = line.at(0) & 0x0F;
                            if (dc == 0 || dc == 2 || dc == 3 || dc == 4)
                            {
                                // packet is X/28/0, X/28/2, X/28/3, or X/28/4
                                int triplet = line.at(1) & 0x3F;
                                triplet |= (line.at(2) & 0x3F) << 6;
                                triplet |= (line.at(3) & 0x3F) << 12; // first triplet contains page function and coding
                                
                                // Page function and coding override previous values
                                _page->SetPageFunctionInt(triplet & 0x0F);
                                _page->SetPageCodingInt((triplet & 0x70) >> 4);
                            }
                        }
                        
                        break;
                    }
                    case 9 : // "FL"; - Fastext links
                    {
                        std::array<FastextLink,6> links;
                        
                        for (int fli=0;fli<6;fli++)
                        {
                            if (fli<5)
                                std::getline(filein, line, ',');
                            else
                                std::getline(filein, line); // Last parameter no comma
                            
                            links[fli].page = std::strtol(line.c_str(), &ptr, 16);
                            links[fli].subpage = 0x3f7f;
                        }
                        
                        if (s != nullptr)
                        {
                            s->SetFastext(links);
                        }
                        break;
                    }
                    case 11 : // "RE"; - Set page region code 0..f
                    {
                        std::getline(filein, line);
                        int region = std::strtol(line.c_str(), &ptr, 16);
                        if (s != nullptr)
                            s->SetRegion(region);
                        break;
                    }
                    case 12 : // "PF"; - not in the tti spec, page function and coding
                    {
                        std::getline(filein, line);
                        if (line.length()<3)
                        {
                            // invalid page function/coding
                        }
                        else
                        {
                            _page->SetPageFunctionInt(std::strtol(line.substr(0,1).c_str(), &ptr, 16));
                            _page->SetPageCodingInt(std::strtol(line.substr(2,1).c_str(), &ptr, 16));
                        }
                        break;
                    }
                    default:
                    {
                        // line not understood
                    }
                } // switch
            } // if matched command
            // If the command was not found then skip the rest of the line
        } // seek command
        if (!found) std::getline(filein, line);
    }
    filein.close(); // Not sure that we need to close it
    _page->RenumberSubpages();
    return (lines>0);
}

FileMonitor::FileMonitor(Configure *configure, Debug *debug, PageList *pageList) :
    _configure(configure),
    _debug(debug),
    _pageList(pageList)
{
    //ctor
}

FileMonitor::FileMonitor()
    : _pageList(nullptr)
{
    //ctor
}

FileMonitor::~FileMonitor()
{
    //dtor
}

void FileMonitor::run()
{
    std::string path=_configure->GetPageDirectory() ;
    _debug->Log(Debug::LogLevels::logINFO,"[FileMonitor::run] Monitoring " + path);
    
    readDirectory(path, true);

    while (true)
    {
        ClearFlags(); // Assume that no files exist
        
        readDirectory(path);
        
        // Delete pages that no longer exist (this blocks the thread until the pages are removed)
        DeleteOldPages();

        // Wait 5 seconds to avoid hogging cpu
        // Sounds like a job for a mutex.
        struct timespec rec;
        int ms;

        ms=5000;
        rec.tv_sec = ms / 1000;
        rec.tv_nsec=(ms % 1000) *1000000;
        nanosleep(&rec,nullptr);
    }
} // run

int FileMonitor::readDirectory(std::string path, bool firstrun)
{
    struct dirent *dirp;
    struct stat attrib;
    
    DIR *dp;

    // Open the directory
    if ( (dp = opendir(path.c_str())) == NULL)
    {
        _debug->Log(Debug::LogLevels::logERROR,"Error(" + std::to_string(errno) + ") opening " + path);
        return errno;
    }
    
    // Load the filenames into a list
    while ((dirp = readdir(dp)) != NULL)
    {
        std::string name;
        name=path;
        name+="/";
        name+=dirp->d_name;
        if (stat(name.c_str(), &attrib) == -1) // get the attributes of the file
            continue; // skip file on failure
        
        if (attrib.st_mode & S_IFDIR)
        {
            // directory entry is another directory
            if (dirp->d_name[0] != '.') // ignore anything beginning with .
            {
                if (readDirectory(name)) // recurse into directory
                {
                    _debug->Log(Debug::LogLevels::logERROR,"Error(" + std::to_string(errno) + ") recursing into " + name);
                }
            }
            continue;
        }
        
        const std::vector<std::string> filetypes{".tti"}; // the filetypes we will attempt to load
        if (name.size() >= 4)
        {
            std::string ext = name.substr(name.size() - 4); // get last four characters of string
            if (find(filetypes.begin(), filetypes.end(), ext) != filetypes.end())
            {
                // Now we want to process changes
                // 1) Is it a new page? Then add it.
                std::shared_ptr<File> f = Locate(name);
                if (f) // File was found
                {
                    f->SetState(File::FOUND); // Mark this page as existing on the drive
                    std::shared_ptr<TTXPageStream> page = f->GetPage();
                    if (attrib.st_mtime!=f->GetModifiedTime()) // File exists. Has it changed?
                    {
                        if (page->GetIsMarked()) // file is mid-deletion
                        {
                            f->SetState(File::NOTFOUND); // let this file object get deleted and reloaded
                        }
                        else
                        {
                            // We just load the new page and update the modified time
                            if (page->GetLock()) // try to lock page
                            {
                                int curnum = page->GetPageNumber();
                                f->LoadFile(name);
                                if (page->GetPageNumber() != curnum)
                                {
                                    // page number changed
                                    page->MarkForDeletion(); // mark page for deletion from service
                                    f->SetState(File::NOTFOUND); // let this file object get deleted and reloaded
                                }
                                else
                                {
                                    if (f->Loaded())
                                    {
                                        if (page->GetOneShotFlag())
                                        {
                                            // file load clears oneshot status
                                            page->SetOneShotFlag(false);
                                            _debug->Log(Debug::LogLevels::logINFO,"[FileMonitor::run] Reloading page from " + std::string(dirp->d_name));
                                        }
                                        
                                        page->IncrementUpdateCount();
                                        int update = false;
                                        
                                        page->StepFirstSubpage(); // Only check update flag on first subpage. Carousels don't get pushed out anyway
                                        if (std::shared_ptr<Subpage> s = page->GetSubpage())
                                            update = (s->GetSubpageStatus() & PAGESTATUS_C8_UPDATE);
                                        
                                        if (!(_pageList->Contains(page)))
                                        {
                                            // this page is not currently in pagelist
                                            _pageList->AddPage(page, !update); // only transmit immediate update if update flag is set
                                        }
                                        else
                                        {
                                            _pageList->UpdatePageLists(page, !update); // only transmit immediate update if update flag is set
                                        }
                                        
                                        page->StepLastSubpage(); // prepare for page to roll to first subpage
                                    }
                                    else
                                    {
                                        _debug->Log(Debug::LogLevels::logWARN,"[FileMonitor::run] Failed to load " + std::string(dirp->d_name));
                                        page->MarkForDeletion(); // mark page for deletion from service
                                    }
                                    
                                    _pageList->CheckForPacket29OrCustomHeader(page);
                                    
                                    f->SetModifiedTime(attrib.st_mtime);
                                }
                                page->FreeLock(); // must unlock or everything will grind to a halt
                            }
                        }
                    }
                }
                else
                {
                    if (!firstrun){ // suppress logspam on first run
                        _debug->Log(Debug::LogLevels::logINFO,"[FileMonitor::run] Adding a new page " + std::string(dirp->d_name));
                    }
                    // A new file. Create the page object and add it to the page list.
                    
                    std::shared_ptr<File> f(new File(name));
                    f->SetModifiedTime(attrib.st_mtime); // set timestamp
                    if (f->Loaded())
                    {
                        std::shared_ptr<TTXPageStream> page = f->GetPage();
                        
                        // don't add to updated pages list if this is the initial startup
                        int update = false;
                        if (std::shared_ptr<Subpage> s = page->GetSubpage())
                            update = (s->GetSubpageStatus() & PAGESTATUS_C8_UPDATE) && !page->IsCarousel(); // only check update flag of single subpages
                        _pageList->AddPage(page, firstrun | !update); // only transmit immediate update if update flag is set and vbit2 isn't starting up
                        _pageList->CheckForPacket29OrCustomHeader(page);
                    }
                    else
                    {
                        _debug->Log(Debug::LogLevels::logWARN,"[FileMonitor::run] Failed to load " + std::string(dirp->d_name));
                    }
                    _FilesList.push_back(f);
                }
            }
        }
    }
    closedir(dp);
    
    return 0;
}


// Find a file by filename
std::shared_ptr<File> FileMonitor::Locate(std::string filename)
{
    for (std::list<std::shared_ptr<File>>::iterator p=_FilesList.begin();p!=_FilesList.end();++p)
    {
        std::shared_ptr<File> ptr = *p;
        if (filename==ptr->GetFilename())
            return ptr;
    }
    
    return NULL; // @todo placeholder What should we do here?
}

// Detect pages that have been deleted from the drive
// Do this by first clearing all the "exists" flags
// As we scan through the list, set the "exists" flag as we match up the drive to the loaded page
void FileMonitor::ClearFlags()
{
    for (std::list<std::shared_ptr<File>>::iterator p=_FilesList.begin();p!=_FilesList.end();++p)
    {
        std::shared_ptr<File> ptr = *p;

        ptr->SetState(File::NOTFOUND);
    }
}

void FileMonitor::DeleteOldPages()
{
    for (std::list<std::shared_ptr<File>>::iterator p=_FilesList.begin();p!=_FilesList.end();++p)
    {
        std::shared_ptr<File> ptr = *p;
        if (ptr->GetStatusFlag()==File::NOTFOUND)
        {
            ptr->GetPage()->MarkForDeletion(); // mark page for deletion from service
            _debug->Log(Debug::LogLevels::logINFO,"[FileMonitor::DeleteOldPages] Deleted " + ptr->GetFilename());
                
            Delete29AndHeader(ptr->GetPage());
            _FilesList.remove(*p--); // remove file from filelist
        }
    }
}

void FileMonitor::Delete29AndHeader(std::shared_ptr<TTXPageStream> page)
{
    int mag=(page->GetPageNumber() >> 8) & 0x7;
    if (page->GetPacket29Flag())
    {
        // Packet 29 was loaded from this page, so remove it.
        _pageList->GetMagazines()[mag]->DeletePacket29();
        _debug->Log(Debug::LogLevels::logINFO,"[PageList::DeleteOldPages] Removing packet 29 from magazine " + std::to_string((mag == 0)?8:mag));
    }
    if (page->GetCustomHeaderFlag())
    {
        // Custom header was loaded from this page, so remove it.
        _pageList->GetMagazines()[mag]->DeleteCustomHeader();
    }
}


================================================
FILE: filemonitor.h
================================================
#ifndef _FILEMONITOR_H_
#define _FILEMONITOR_H_

#include <iostream>
#include <sstream>
#include <thread>
#include <list>
#include <strings.h>
#include <sys/stat.h>
#include <array>

#include "configure.h"
#include "pagelist.h"
#include "packetmag.h"
#include "ttxpagestream.h"

namespace vbit
{
    class File
    {
        public:
            enum Status
            {
              NEW,      // Just created
              NOTFOUND, // Not found yet
              FOUND     // Matched on drive
            };
            
            File(std::string filename);
            std::shared_ptr<TTXPageStream> GetPage(){return _page;};
            
            // The time that the file was modified.
            time_t GetModifiedTime(){return _modifiedTime;};
            void SetModifiedTime(time_t timeVal){_modifiedTime=timeVal;};
            
            void SetState(Status state){_fileStatus=state;};
            Status GetStatusFlag(){return _fileStatus;};
            
            std::string GetFilename() const {return _filename;}
            
            void LoadFile(std::string filename);
            bool Loaded(){return _loaded;}
            
        private:
            std::shared_ptr<TTXPageStream> _page; // the page loaded from this file
            std::string _filename;
            time_t _modifiedTime;   /// Poll this in case the source file changes (Used to detect updates)
            Status _fileStatus; /// Used to mark if we found the file. (Used to detect deletions)
            bool LoadTTI(std::string filename);
            bool _loaded;
    };
    
    /**
     * Watches for changes to teletext page files and adds them to the page list or marks them for removal
     */
    class FileMonitor
    {
        public:
            /** Default constructor */
            FileMonitor();
            FileMonitor(Configure *configure, Debug *debug, PageList *pageList);
            /** Default destructor */
            virtual ~FileMonitor();

            /**
             * Runs the monitoring thread and does not terminate (at least for now)
             * @return Nothing useful yet. Perhaps return an error status if something goes wrong
             */
            void run();

        protected:

        private:
            Configure* _configure; /// Member reference to the configuration settings
            Debug* _debug;
            PageList* _pageList;
            std::list<std::shared_ptr<File>> _FilesList;
            int readDirectory(std::string path, bool firstrun=false);
            
            std::shared_ptr<File> Locate(std::string filename);
            void ClearFlags();
            void DeleteOldPages();
            void Delete29AndHeader(std::shared_ptr<TTXPageStream> page);
    };
}

#endif // _FILEMONITOR_H_


================================================
FILE: getvbit2
================================================
#!/bin/bash

if [ ! -f /etc/rpi-issue ]; then
    echo "This installer is intended for Raspberry Pi OS only"
    exit 1
fi

# install required packages
sudo apt update
sudo apt -y install git subversion dialog python3-dialog

if (( $(lsb_release -r | tr -dc '0-9') > 11 )); then
    # bookworm
    sudo apt -y install libraspberrypi-dev
    
    # Disable the KMS graphics driver
    sudo sed -i s/dtoverlay=vc4-kms-v3d/#dtoverlay=vc4-kms-v3d/ /boot/firmware/config.txt
    
    if ! grep -q "sdtv_mode" /boot/firmware/config.txt; then
        sudo sed -i -e $'$a\\\nsdtv_mode=2' /boot/firmware/config.txt
    fi
    
    if ! grep -q "enable_tvout" /boot/firmware/config.txt; then
        sudo sed -i -e $'$a\\\nenable_tvout=1' /boot/firmware/config.txt
    fi
else
    # System sets SD video on bootup 
    sudo sed -i s/#sdtv_mode/sdtv_mode/ /boot/config.txt

    # Disable the new KMS graphics driver in bullseye
    sudo sed -i s/dtoverlay=vc4-kms-v3d/#dtoverlay=vc4-kms-v3d/ /boot/config.txt
fi

# download the raspi-teletext git repository and compile it
git clone https://github.com/ali1234/raspi-teletext.git $HOME/raspi-teletext
cd $HOME/raspi-teletext
make

# download the vbit2 git repository
git clone https://github.com/peterkvt80/vbit2.git $HOME/vbit2
cd $HOME/vbit2

# switch to the latest release branch and compile it
latestTag=`curl --silent "https://api.github.com/repos/peterkvt80/vbit2/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")'`
git checkout $latestTag
make

# create links
mkdir -p $HOME/.local/bin
ln -s -f `pwd`/vbit2 $HOME/.local/bin/
ln -s -f `pwd`/scripts/runvbit2.py $HOME/.local/bin/runvbit2
ln -s -f `pwd`/scripts/teletext-update.py $HOME/.local/bin/teletext-update
ln -s -f `pwd`/scripts/vbit-config.py $HOME/.local/bin/vbit-config

# install systemd user scripts
mkdir -p $HOME/.local/share/systemd/user
cp vbit2.service $HOME/.local/share/systemd/user
cp teletext-update.timer $HOME/.local/share/systemd/user
cp teletext-update.service $HOME/.local/share/systemd/user

mkdir -p $HOME/.config/systemd/user/
systemctl --user daemon-reload
loginctl enable-linger

if [[ ! $PATH =~ "$HOME/.local/bin" ]]; then
  PATH="$HOME/.local/bin:$PATH"
fi

vbit-config


================================================
FILE: interfaceServer.cpp
================================================
/** A TCP server for various configuration and teletext input interfaces
    A network interface for the injection of databroadcast packets, inserter configuration, and
    page configuration, for multiple simultaneous clients.
    
    This is a binary message interface where the first byte of any message holds the message
    length, and the second byte contains either a command number (for messages to the server) or an
    error code (for server replies). A client should transmit a command and then read the server's
    response to get the error state and any returned data.
    
    The command numbers and error codes are defined in interfaceServer.h
    
    The client must select which channel subsequent commands relate to using the SETCHAN command.
    Databroadcast packet commands must be sent to any of channels 1-15 (corresponding to the packet
    addresses 1/30 to 7/31), and only a single client may select each of these datacast channels at
    one time.
    Channel 0 is used for non databroadcast commands and may have multiple simultaneous users.
    
    The server currently implements:
    * A databroadcast interface, which can encode IDL format A datacast, or inject raw packet data.
    * A configuration interface, which can set and retrieve certain vbit2 configuration options.
    * A page data API, capable of creating and deleting pages, modifying settings and row data.
*/

#include "interfaceServer.h"

using namespace vbit;

InterfaceServer::InterfaceServer(Configure *configure, Debug *debug, PageList *pageList) :
    _configure(configure),
    _debug(debug),
    _pageList(pageList),
    _portNumber(configure->GetInterfaceServerPort()),
    _maxClients(configure->GetInterfaceServerMaxClients()),
    _isActive(false)
{
    /* initialise sockets */
    _serverSock = -1;
    
    _datachannel[0]=nullptr; // do not create PacketDatacast for reserved data channel 0
    for (int dc=1; dc<16; dc++)
    {
        _datachannel[dc] = new PacketDatacast(dc, configure); // initialise remaining 15 datacast channels
    }
}

InterfaceServer::~InterfaceServer()
{
}

void InterfaceServer::SocketError(std::string errorMessage)
{
    if (_serverSock >= 0)
    {
        #ifdef WIN32
            closesocket(_serverSock);
        #else
            close(_serverSock);
        #endif
    }
    
    for(std::list<ClientState>::iterator it = _clients.begin(); it != _clients.end();)
    {
        ClientState client = *it;
        
        if (client.socket >= 0)
            CloseClient(&client);
        
        it = _clients.erase(it);
    }
    
    _debug->Log(Debug::LogLevels::logERROR,errorMessage);
}

void InterfaceServer::CloseClient(ClientState *client)
{
    // close socket
    #ifdef WIN32
        closesocket(client->socket);
    #else
        close(client->socket);
    #endif
    
    if (client->page)
    {
        client->page->FreeLock(); // unlock page
    }
}

void InterfaceServer::run()
{
    _debug->Log(Debug::LogLevels::logINFO,"[InterfaceServer::run] Datacast server thread started for "+(_maxClients?"max "+std::to_string(_maxClients):"unlimited")+" connections");
    
    int newSock;
    struct sockaddr_in address;
    char readBuffer[256];
    fd_set readfds;
    
#ifdef WIN32
    int addrlen;
    WSADATA wsaData;
    int iResult;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0)
    {
        SocketError("[InterfaceServer::run] WSAStartup failed\n");
        return;
    }
#else
    unsigned int addrlen;
#endif
    
    /* Create socket */
    if ((_serverSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    {
        SocketError("[InterfaceServer::run] socket() failed\n");
        return;
    }
    
    int reuse = true;
    
    /* Allow multiple connnections */
    if(setsockopt(_serverSock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)) < 0)
    {
        SocketError("[InterfaceServer::run] setsockopt() SO_REUSEADDR failed\n");
        return;
    }
    
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = htonl(INADDR_ANY);
    address.sin_port = htons(_portNumber);
    
    /* bind socked */
    if (bind(_serverSock, (struct sockaddr *) &address, sizeof(address)) < 0)
    {
        SocketError("[InterfaceServer::run] bind() failed\n");
        return;
    }
    
    /* Listen for incoming connections */
    if (listen(_serverSock, MAXPENDING) < 0)
    {
        SocketError("[InterfaceServer::run] listen() failed\n");
        return;
    }
    
    addrlen = sizeof(address);
    
    while(true)
    {
        FD_ZERO(&readfds);
        FD_SET(_serverSock, &readfds);
        
        for(std::list<ClientState>::iterator it = _clients.begin(); it != _clients.end(); ++it)
        {
            if ((*it).socket > -1)
                FD_SET((*it).socket , &readfds);
        }
        _isActive = !(_clients.empty());
        
        /* wait for activity on any socket */
        if ((select(FD_SETSIZE, &readfds, NULL, NULL, NULL) < 0) && (errno!=EINTR))
            SocketError("[InterfaceServer::run] select() failed");
        
        if (FD_ISSET(_serverSock, &readfds))
        {
            /* incoming connection to server */
            if ((newSock = accept(_serverSock, (struct sockaddr *)&address, &addrlen))<0)
            {
                SocketError("[InterfaceServer::run] accept() failed");
                return;
            }
            
            #ifdef WIN32
                u_long ul = 1;
                if (ioctlsocket(newSock, FIONBIO, &ul) < 0)
                {
                    SocketError("[InterfaceServer::run] ioctlsocket() failed");
                    return;
                }
            #else
                if (fcntl(newSock, F_SETFL, fcntl(newSock, F_GETFL, 0) | O_NONBLOCK) < 0)
                {
                    SocketError("[InterfaceServer::run] fcntl() failed");
                    return;
                }
            #endif
            
            if (_maxClients > 0 && _maxClients == _clients.size())
            {
                /* no more client slots so reject */
                #ifdef WIN32
                    closesocket(newSock);
                #else
                    close(newSock);
                #endif
                _debug->Log(Debug::LogLevels::logWARN,"[InterfaceServer::run] reject new connection from " + std::string(inet_ntoa(address.sin_addr)) + " (too many connections)");
                continue;
            }
            
            ClientState newClient;
            newClient.socket = newSock;
            _clients.push_back(newClient);
            
            _debug->Log(Debug::LogLevels::logINFO,"[InterfaceServer::run] new connection from " + std::string(inet_ntoa(address.sin_addr)) + ":" + std::to_string(ntohs(address.sin_port)) + " as socket " + std::to_string(newSock));
        }
        else
        {
            /* a client socket has activity */
            for(std::list<ClientState>::iterator it = _clients.begin(); it != _clients.end();)
            {
                ClientState* client = &(*it);
                
                if (client->socket >= 0 && FD_ISSET(client->socket , &readfds))
                {
                    /* socket has activity */
                    getpeername(client->socket, (struct sockaddr*)&address, &addrlen);
                    
                    int n = recv(client->socket, readBuffer, 1, MSG_PEEK); // peek at first byte of message
                    if (n == 1)
                    {
                        // byte 0 of message is message length
                        int len = (uint8_t)readBuffer[0];
                        if (len)
                        {
                            n = recv(client->socket, readBuffer, len, 0); // try to read whole message
                            if (n == len)
                            {
                                std::vector<uint8_t> res = {CMDOK}; // create "OK" response
                                
                                // byte 1 of message is interface server command number
                                switch ((uint8_t)readBuffer[1]){
                                    case SETCHAN: // set interface channel
                                    {
                                        int ch = (uint8_t)readBuffer[2];
                                        if (n == 3 && ch >= 0 && ch <= 15)
                                        {
                                            client->channel = -1; // release current channel
                                            
                                            
                                            if (ch) // allows multiple connections for channel 0
                                            {
                                                // check if another client is using desired datachannel
                                                for(std::list<ClientState>::iterator it = _clients.begin(); it != _clients.end(); ++it)
                                                {
                                                    if ((*it).channel == ch)
                                                    {
                                                        res[0] = CMDBUSY;
                                                        goto SETCHANError; // jump out to return error
                                                    }
                                                }
                                            }
                                            
                                            client->channel = ch; // use requested channel
                                            
                                            std::stringstream ss;
                                            ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": SETCHAN " << ch;
                                            _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                            break;
                                        }
                                        
                                        res[0] = CMDERR;
                                        SETCHANError:
                                        break;
                                    }
                                    
                                    case GETAPIVER:
                                    {
                                        /* get API version number */
                                        if (n == 2)
                                        {
                                            res.push_back(APIVERSION[0]); // major version
                                            res.push_back(APIVERSION[1]); // minor version
                                            res.push_back(APIVERSION[2]); // patch
                                        }
                                        else
                                            res[0] = CMDERR;
                                        break;
                                    }
                                    
                                    case DBCASTAPI:
                                    {
                                        /* databroadcast API */
                                        if (client->channel > 0 && n > 2) // databroadcast commands only on channels 1-15
                                        {
                                            switch((uint8_t)readBuffer[2]) // byte 2 is databroadcast command number
                                            {
                                                case DCRAW: // push raw datacast packet to buffer
                                                {
                                                    if (n == 43) // 40 bytes of packet data
                                                    {
                                                        std::vector<uint8_t> data(readBuffer+3, readBuffer+n);
                                                        
                                                        if(_datachannel[client->channel]->PushRaw(&data))
                                                        {
                                                            res[0] = CMDBUSY; // buffer full
                                                        }
                                                    }
                                                    else
                                                    {
                                                        res[0] = CMDERR;
                                                    }
                                                    break;
                                                }
                                                
                                                case DCFORMATA: // encode and push a format A datacast payload to buffer
                                                {
                                                    /* Does _not_ automate repeats, continuity indicator, etc.
                                                       Format is:
                                                         byte 3: bits 4-7 IAL, bits 1-3 flags RI,CI,DL
                                                         byte 4-6: 24 bit Service Packet Address (little endian)
                                                         byte 7: Repeat indicator
                                                         byte 8: Continuity indicator
                                                         byte 9+: payload data
                                                    */
                                                    if (n > 9)
                                                    {
                                                        uint8_t flags = (uint8_t)readBuffer[3] & 0xe;
                                                        uint8_t ial = (uint8_t)readBuffer[3] >> 4;
                                                        uint32_t spa = (uint8_t)readBuffer[4] | ((uint8_t)readBuffer[5] << 8) | ((uint8_t)readBuffer[6] << 16);
                                                        uint8_t ri = (uint8_t)readBuffer[7];
                                                        uint8_t ci = (uint8_t)readBuffer[8];
                                                        
                                                        std::vector<uint8_t> data(readBuffer+9, readBuffer+n);
                                                        
                                                        int bytes = _datachannel[client->channel]->PushIDLA(flags, ial, spa, ri, ci, &data);
                                                        
                                                        if (bytes == 0) // buffer full
                                                            res[0] = CMDBUSY;
                                                        else if (bytes < n-9) // payload didn't fit
                                                            res[0] = CMDTRUNC; // warn of truncation
                                                        
                                                        res.push_back(bytes); // return number of bytes written
                                                        
                                                        break;
                                                    }
                                                    else
                                                    {
                                                        res[0] = CMDERR;
                                                        res.push_back(0); // no bytes written
                                                    }
                                                    break;
                                                }
                                                
                                                default: // unknown datacast command
                                                    res[0] = CMDERR;
                                            }
                                        }
                                        else
                                        {
                                            res[0] = CMDERR;
                                        }
                                        break;
                                    }
                                    
                                    case CONFIGAPI:
                                    {
                                        /* vbit2 configuration API */
                                        if (client->channel == 0 && n > 2) // allow VBIT2 configuration commands on channel 0 only
                                        {
                                            switch((uint8_t)readBuffer[2]){ // byte 2 is configuration command number
                                                case CONFRAFLAG: /* get/set row adaptive flag */
                                                {
                                                    if (n == 4)
                                                    {
                                                        _configure->SetRowAdaptive((uint8_t)readBuffer[3]&1);
                                                        std::stringstream ss;
                                                        ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": CONFRAFLAG " << ((readBuffer[3] & 1)?"ON":"OFF");
                                                        _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                    }
                                                    else if (n != 3)
                                                    {
                                                        res[0] = CMDERR;
                                                    }
                                                    res.push_back(_configure->GetRowAdaptive()?1:0);
                                                    
                                                    break;
                                                }
                                                
                                                case CONFRBYTES: /* get/set BSDP reserved bytes */
                                                {
                                                    if (n == 7) // set new bytes
                                                    {
                                                        _configure->SetReservedBytes(std::array<uint8_t, 4>({(uint8_t)readBuffer[3],(uint8_t)readBuffer[4],(uint8_t)readBuffer[5],(uint8_t)readBuffer[6]}));
                                                        std::stringstream ss;
                                                        ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": CONFRBYTES set";
                                                        _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                    }
                                                    else if (n != 3)
                                                    {
                                                        res[0] = CMDERR;
                                                    }
                                                    
                                                    std::array<uint8_t, 4> bytes = _configure->GetReservedBytes();
                                                    res.push_back(bytes[0]);
                                                    res.push_back(bytes[1]);
                                                    res.push_back(bytes[2]);
                                                    res.push_back(bytes[3]); // read back reserved bytes
                                                    break;
                                                }
                                                
                                                case CONFSTATUS: /* get/set BSDP status message */
                                                {
                                                    if (n == 23) // set new status
                                                    {
                                                        std::ostringstream tmp;
                                                        for (int i = 3; i < 23; i++)
                                                        {
                                                            tmp << (char)(readBuffer[i] & 0x7f);
                                                        }
                                                        
                                                        _configure->SetServiceStatusString(tmp.str());
                                                        std::stringstream ss;
                                                        ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": CONFSTATUS set";
                                                        _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                    }
                                                    else if (n != 3)
                                                    {
                                                        res[0] = CMDERR;
                                                    }
                                                    
                                                    for(char& c : _configure->GetServiceStatusString()) {
                                                        res.push_back((uint8_t)c & 0x7f);
                                                    }
                                                    
                                                    break;
                                                }
                                                
                                                case CONFHEADER: /* get/set header template */
                                                {
                                                    if (n == 35 || n == 36) // set new header template
                                                    {
                                                        std::array<uint8_t, 40> tmp;
                                                        for (int i = 0; i < 32; i++)
                                                            tmp[8+i] = readBuffer[(n-32)+i];
                                                        std::shared_ptr<TTXLine> line(new TTXLine(tmp));
                                                        
                                                        std::stringstream ss;
                                                        if (n==35)
                                                        {
                                                            _configure->SetHeaderTemplate(line);
                                                            ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": CONFHEADER set";
                                                        }
                                                        else
                                                        {
                                                            uint8_t mag = (uint8_t)readBuffer[3] & 0x7;
                                                            _pageList->GetMagazines()[mag]->SetCustomHeader(line);
                                                            ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": CONFHEADER set on magazine " << (int)mag;
                                                        }
                                                        _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                    }
                                                    else if (n == 3)
                                                    {
                                                        for(char& c : _configure->GetHeaderTemplate()) {
                                                            res.push_back((uint8_t)c);
                                                        }
                                                    }
                                                    else if (n == 4)
                                                    {
                                                        uint8_t mag = (uint8_t)readBuffer[3];
                                                        if (mag & 0x80)
                                                        {
                                                            // delete flag
                                                            _pageList->GetMagazines()[mag & 0x7]->DeleteCustomHeader();
                                                        }
                                                        else
                                                        {
                                                            std::string tmp = _pageList->GetMagazines()[mag & 0x7]->GetCustomHeader();
                                                            if (tmp.length() == 32)
                                                            {
                                                                for(char& c : tmp) {
                                                                    res.push_back((uint8_t)c);
                                                                }
                                                            }
                                                            else
                                                            {
                                                                // no custom header set for magazine
                                                                res[0] = CMDNOENT;
                                                            }
                                                        }
                                                    }
                                                    else
                                                    {
                                                        res[0] = CMDERR;
                                                    }
                                                    
                                                    break;
                                                }
                                                
                                                case CONFENHANC:
                                                {
                                                    res[0] = CMDERR; // default to an returning an error unless we have success
                                                    if (n > 3)
                                                    {
                                                        int mag = (uint8_t)readBuffer[3] & 0x1F;
                                                        int deleteFlag = (uint8_t)readBuffer[3]&0x80;
                                                        if (mag < 8)
                                                        {
                                                            if (!deleteFlag)
                                                            {
                                                                if (n == 44)
                                                                {
                                                                    // write row data
                                                                    std::array<uint8_t, 40> tmp;
                                                                    for (int i = 0; i < 40; i++)
                                                                        tmp[i] = (uint8_t)readBuffer[4+i];
                                                                    std::shared_ptr<TTXLine> line(new TTXLine(tmp));
                                                                    
                                                                    _pageList->GetMagazines()[mag]->SetPacket29(line);
                                                                    
                                                                    res[0] = CMDOK;
                                                                }
                                                                else if (n == 5)
                                                                {
                                                                    // read row data
                                                                    std::shared_ptr<TTXLine> line = _pageList->GetMagazines()[mag]->GetPacket29();
                                                                    if (line!=nullptr)
                                                                        line = line->LocateLine((uint8_t)readBuffer[4]&0xF);
                                                                    
                                                                    if (line != nullptr)
                                                                    {
                                                                        std::array<uint8_t, 40> tmp = line->GetLine();
                                                                        res.insert (res.end(), tmp.data(), tmp.data()+tmp.size());
                                                                        res[0] = CMDOK;
                                                                    }
                                                                    else
                                                                        res[0] = CMDNOENT; // row doesn't exist
                                                                }
                                                            }
                                                            else
                                                            {
                                                                if (n==4)
                                                                {
                                                                    // delete all packets
                                                                    _pageList->GetMagazines()[mag]->DeletePacket29();
                                                                }
                                                                else if (n==5)
                                                                {
                                                                    // delete dc
                                                                    _pageList->GetMagazines()[mag]->DeletePacket29((uint8_t)readBuffer[4]&0xF);
                                                                }
                                                                res[0] = CMDOK; // didn't check if line existed, just returns OK
                                                            }
                                                        }
                                                    }
                                                    break;
                                                }
                                                
                                                default: // unknown configuration command
                                                    res[0] = CMDERR;
                                            }
                                        }
                                        else
                                        {
                                            res[0] = CMDERR;
                                        }
                                        break;
                                    }
                                    
                                    case PAGESAPI:
                                    {
                                        /* page data API */
                                        
                                        if (client->channel == 0 && n > 2) // allow page data commands on channel 0 only
                                        {
                                            int cmd = (uint8_t)readBuffer[2]; // byte 2 is page data API command number
                                            
                                            if (cmd <= PAGEDELSUB) // all commands that start with a page/subpage number
                                            {
                                                if (cmd <= PAGEOPEN && client->page)
                                                {
                                                    // implicitly close page when issuing other page delete/open commands
                                                    client->page->FreeLock();
                                                    client->page = nullptr;
                                                    client->subpage = nullptr;
                                                }
                                                
                                                if (n >= 5)
                                                {
                                                    int num = ((uint8_t)readBuffer[3] << 8) | (uint8_t)readBuffer[4];
                                                    if (cmd == PAGEDELETE)
                                                    {
                                                        if (n == 5)
                                                        {
                                                            std::stringstream ss;
                                                            ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": PAGEDELETE " << std::hex << num;
                                                            _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                            
                                                            std::shared_ptr<TTXPageStream> p = _pageList->Locate(num);
                                                            if (p != nullptr)
                                                            {
                                                                p->SetOneShotFlag(false);
                                                                p->MarkForDeletion();
                                                            }
                                                            else
                                                            {
                                                                res[0] = CMDNOENT;
                                                            }
                                                        }
                                                        else
                                                            res[0] = CMDERR;
                                                    }
                                                    else if (cmd == PAGEOPEN)
                                                    {
                                                        bool OneShot = false;
                                                        if (n > 5)
                                                            OneShot = (readBuffer[5] & 1);
                                                        
                                                        if (n < 7)
                                                        {
                                                            std::stringstream ss;
                                                            ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": PAGEOPEN " << std::hex << num << (OneShot?" as OneShot":"");
                                                            _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                            if ((uint8_t)readBuffer[3] > 0 && (uint8_t)readBuffer[3] <= 8 && (uint8_t)readBuffer[4] < 0xff)
                                                            {
                                                                std::shared_ptr<TTXPageStream> p = _pageList->Locate(num);
                                                                if (p == nullptr || p->GetIsMarked())
                                                                {
                                                                    p = std::shared_ptr<TTXPageStream>(new TTXPageStream()); // create new page
                                                                    std::stringstream ss;
                                                                    ss << "[InterfaceServer::run] Created new page " << std::hex << num;
                                                                    _debug->Log(Debug::LogLevels::logINFO,ss.str());
                                                                    if (p->GetLock()) // if this fails we have a real problem!
                                                                    {
                                                                        p->SetPageNumber(num);
                                                                        p->SetOneShotFlag(OneShot);
                                                                        _pageList->AddPage(p, true); // put it in the page lists
                                                                        
                                                                        // at this stage it has no subpages!
                                                                        client->page = p;
                                                                    }
                                                                    else
                                                                    {
                                                                        res[0] = CMDBUSY;
                                                                    }
                                                                }
                                                                else
                                                                {
                                                                    res[0] = CMDBUSY; // overwritten if successful
                                                                    if (p->GetOneShotFlag() && p->GetUpdatedFlag())
                                                                    {
                                                                        // previous oneshot hasn't yet sent
                                                                    }
                                                                    else if (p->GetLock()) // try to lock page
                                                                    {
                                                                        if (OneShot || (p->GetOneShotFlag() != OneShot)) // oneshot or oneshot changed
                                                                        {
                                                                            p->SetOneShotFlag(OneShot);
                                                                            _pageList->UpdatePageLists(p);
                                                                        }
                                                                        
                                                                        client->page = p;
                                                                        res[0] = CMDOK;
                                                                    }
                                                                }
                                                            }
                                                        }
                                                        else
                                                            res[0] = CMDERR;
                                                    }
                                                    else if (cmd == PAGESETSUB || cmd == PAGEDELSUB)
                                                    {
                                                        client->subpage = nullptr; // invalidate previous subpage
                                                        if (n == 5 && client->page)
                                                        {
                                                            std::stringstream ss;
                                                            ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": " << ((cmd==PAGESETSUB)?"PAGESETSUB ":"PAGEDELSUB ") << std::hex << num;
                                                            _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                            
                                                            if ((num & 0xc080) || num >= 0x3f7f) // reject invalid subpage numbers
                                                            {
                                                                res[0] = CMDERR;
                                                            }
                                                            else
                                                            {
                                                                client->subpage = client->page->LocateSubpage(num);
                                                                if (client->subpage == nullptr) // subpage not found
                                                                {
                                                                    if (cmd == PAGESETSUB)
                                                                    {
                                                                        client->subpage = std::shared_ptr<Subpage>(new Subpage()); // create new subpage
                                                                        client->subpage->SetSubCode(num); // set subcode first
                                                                        client->page->InsertSubpage(client->subpage); // add to page
                                                                        _pageList->UpdatePageLists(client->page);
                                                                        client->subpage->SetSubpageStatus(PAGESTATUS_TRANSMITPAGE);
                                                                        
                                                                        if (client->page->GetOneShotFlag()) // page is a oneshot
                                                                            client->page->SetSubpage(num); // put this subpage on air
                                                                    }
                                                                    else // PAGEDELSUB
                                                                    {
                                                                        res[0] = CMDNOENT;
                                                                    }
                                                                }
                                                                else
                                                                {
                                                                    if (cmd == PAGESETSUB)
                                                                    {
                                                                        if (client->page->GetOneShotFlag()) // page is a oneshot
                                                                            client->page->SetSubpage(num); // put this subpage on air
                                                                    }
                                                                    else // PAGEDELSUB
                                                                    {
                                                                        client->page->RemoveSubpage(client->subpage);
                                                                    }
                                                                }
                                                                unsigned int count = client->page->GetSubpageCount();
                                                                res.push_back((count >> 8) & 0xff);
                                                                res.push_back(count & 0xff); // return subpage count (big endian)
                                                            }
                                                        }
                                                        else
                                                            res[0] = CMDERR;
                                                    }
                                                }
                                                else
                                                    res[0] = CMDERR;
                                            }
                                            else if (cmd == PAGECLOSE)
                                            {
                                                std::stringstream ss;
                                                ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": PAGECLOSE";
                                                _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                if (n==3)
                                                {
                                                    if (client->page)
                                                        client->page->FreeLock();
                                                    else
                                                        res[0] = CMDNOENT;
                                                    client->page = nullptr;
                                                    client->subpage = nullptr;
                                                    
                                                    
                                                }
                                                else
                                                {
                                                    res[0] = CMDERR;
                                                }
                                            }
                                            else if (cmd == PAGEFANDC)
                                            {
                                                if (client->page)
                                                {
                                                    std::stringstream ss;
                                                    ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": PAGEFANDC";
                                                    _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                    if (n == 5) // write
                                                    {
                                                        client->page->SetPageFunctionInt((uint8_t)readBuffer[3]);
                                                        client->page->SetPageCodingInt((uint8_t)readBuffer[4]);
                                                        _pageList->UpdatePageLists(client->page);
                                                    }
                                                    else if (n != 3)
                                                    {
                                                        res[0] = CMDERR;
                                                    }
                                                    res.push_back(client->page->GetPageFunction());
                                                    res.push_back(client->page->GetPageCoding());
                                                }
                                                else
                                                {
                                                    res[0] = CMDERR;
                                                }
                                            }
                                            else if (cmd == PAGEOPTNS)
                                            {
                                                if (client->subpage)
                                                {
                                                    std::stringstream ss;
                                                    ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": PAGEOPTNS";
                                                    _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                    
                                                    if (n == 8) // write
                                                    {
                                                        client->subpage->SetSubpageStatus(((uint8_t)readBuffer[3] << 8) | (uint8_t)readBuffer[4]);
                                                        client->subpage->SetRegion((uint8_t)readBuffer[5]);
                                                        client->subpage->SetCycleTime((uint8_t)readBuffer[6]);
                                                        client->subpage->SetTimedMode((uint8_t)readBuffer[7] & 1);
                                                    }
                                                    else if (n != 3)
                                                    {
                                                        res[0] = CMDERR;
                                                    }
                                                    uint16_t status = client->subpage->GetSubpageStatus();
                                                    res.push_back((status >> 8) & 0xff);
                                                    res.push_back(status & 0xff);
                                                    res.push_back(client->subpage->GetRegion());
                                                    res.push_back(client->subpage->GetCycleTime());
                                                    res.push_back(client->subpage->GetTimedMode()?1:0);
                                                }
                                                else
                                                {
                                                    res[0] = CMDERR;
                                                }
                                            }
                                            else if (cmd == PAGEROW)
                                            {
                                                res[0] = CMDERR; // default to an returning an error unless we have success
                                                if (client->subpage)
                                                {
                                                    std::stringstream ss;
                                                    ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": PAGEROW";
                                                    _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                    
                                                    if (n > 3)
                                                    {
                                                        int num = (uint8_t)readBuffer[3] & 0x1F;
                                                        int deleteFlag = (uint8_t)readBuffer[3]&0x80;
                                                        if (num > 0 && num < 29)
                                                        {
                                                            if (!deleteFlag)
                                                            {
                                                                if (n == 44)
                                                                {
                                                                    // write row data
                                                                    std::array<uint8_t, 40> tmp;
                                                                    for (int i = 0; i < 40; i++)
                                                                        tmp[i] = (uint8_t)readBuffer[4+i];
                                                                    std::shared_ptr<TTXLine> line(new TTXLine(tmp));
                                                                    
                                                                    client->subpage->SetRow(num, line);
                                                                    
                                                                    res[0] = CMDOK;
                                                                }
                                                                else if ((num < 26 && n==4) || (num > 25 && n==5))
                                                                {
                                                                    // read row data
                                                                    std::shared_ptr<TTXLine> line = client->subpage->GetRow(num);
                                                                    if (n==5 && line!=nullptr)
                                                                        line = line->LocateLine((uint8_t)readBuffer[4]&0xF);
                                                                    
                                                                    if (line != nullptr)
                                                                    {
                                                                        std::array<uint8_t, 40> tmp = line->GetLine();
                                                                        res.insert (res.end(), tmp.data(), tmp.data()+tmp.size());
                                                                        res[0] = CMDOK;
                                                                    }
                                                                    else
                                                                        res[0] = CMDNOENT; // row doesn't exist
                                                                }
                                                            }
                                                            else
                                                            {
                                                                if (n==4)
                                                                {
                                                                    // delete row data
                                                                    client->subpage->DeleteRow(num);
                                                                }
                                                                else if (num > 25 && n==5)
                                                                {
                                                                    // delete dc
                                                                    client->subpage->DeleteRow(num, (uint8_t)readBuffer[4]&0xF);
                                                                }
                                                                res[0] = CMDOK; // didn't check if line existed, just returns OK
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                            else if (cmd == PAGELINKS)
                                            {
                                                if (client->subpage)
                                                {
                                                    std::stringstream ss;
                                                    ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": PAGELINKS";
                                                    _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                                    
                                                    std::array<FastextLink, 6> links;
                                                    
                                                    if (n == 15 || n == 27)
                                                    {
                                                        for (int l=0; l<6; l++)
                                                        {
                                                            links[l].page = (((uint8_t)readBuffer[3+(l*2)] & 0x7) << 8) | (uint8_t)readBuffer[4+(l*2)];
                                                            
                                                            if (n == 27)
                                                            {
                                                                links[l].subpage = (((uint8_t)readBuffer[15+(l*2)] << 8) | (uint8_t)readBuffer[16+(l*2)]) & 0x3f7f;
                                                            }
                                                            else
                                                            {
                                                                links[l].subpage = 0x3f7f;
                                                            }
                                                        }
                                                        client->subpage->SetFastext(links);
                                                    }
                                                    else if (n != 3)
                                                    {
                                                        res[0] = CMDERR;
                                                    }
                                                    
                                                    if (res[0] == CMDOK)
                                                    {
                                                        if (client->subpage->GetFastext(&links))
                                                        {
                                                            for (int l = 0; l < 6; l++)
                                                            {
                                                                res.push_back((links[l].page >> 8) & 7);
                                                                res.push_back(links[l].page & 0xff);
                                                            }
                                                            for (int l = 0; l < 6; l++)
                                                            {
                                                                res.push_back((links[l].subpage >> 8) & 0x3f);
                                                                res.push_back(links[l].subpage & 0x7f);
                                                            }
                                                        }
                                                        else
                                                        {
                                                            res[0] = CMDNOENT;
                                                        }
                                                    }
                                                }
                                                else
                                                {
                                                    res[0] = CMDERR;
                                                }
                                            }
                                            else if (cmd > PAGELINKS) // last defined command number
                                            {
                                                std::stringstream ss;
                                                ss << "[InterfaceServer::run] Client " << std::string(inet_ntoa(address.sin_addr)) << ":" << std::to_string(ntohs(address.sin_port)) << ": Unknown PAGESAPI command received " << std::hex << cmd;
                                                _debug->Log(Debug::LogLevels::logDEBUG,ss.str());
                                            }
                                        }
                                        break;
                                    }
                                    
                                    default: // unknown command
                                    {
                                        res[0] = CMDERR;
                                        break;
                                    }
                                }
                                
                                if (res.size() > 254)
                                {
                                    _debug->Log(Debug::LogLevels::logERROR,"[InterfaceServer::run] Response too long");
                                    res.resize(254); // truncate!
                                }
                                
                                res.insert(res.begin(), res.size()+1); // prepend message size
                                
                                unsigned int n = send(client->socket, (char*)res.data(), res.size(), 0); // send response
                                
                                if (n == res.size()) // fail if only partial response can be sent
                                {
                                    ++it;
                                    continue; // next socket in loop
                                }
                            }
                            // else fall through to error handling
                        }
                    }
                    // couldn't read/write all bytes
                    if (n == 0)
                    {
                        /* client disconnected */
                        _debug->Log(Debug::LogLevels::logINFO,"[InterfaceServer::run] closing connection from " + std::string(inet_ntoa(address.sin_addr)) + ":" + std::to_string(ntohs(address.sin_port)) + " on socket " + std::to_string(client->socket));
                    }
                    else
                    {
                        #ifdef WIN32
                            int e = WSAGetLastError();
                        #else
                            int e = errno;
                        #endif
                        
                        _debug->Log(Debug::LogLevels::logWARN,"[InterfaceServer::run] closing connection from " + std::string(inet_ntoa(address.sin_addr)) + ":" + std::to_string(ntohs(address.sin_port)) + " recv error " + std::to_string(e) + " on socket " + std::to_string(client->socket));
                    }
                    
                    /* close the socket when any error occurs */
                    CloseClient(client);
                    it = _clients.erase(it);
                }
                else
                {
                    ++it;
                }
            }
        }
    }
}


================================================
FILE: interfaceServer.h
================================================
#ifndef _INTERFACESERVER_H_
#define _INTERFACESERVER_H_

#include "configure.h"
#include "debug.h"
#include "pagelist.h"
#include "packetmag.h"
#include "ttxpagestream.h"
#include "packet.h"
#include "packetDatacast.h"

#ifdef WIN32
#include <winsock2.h>
#else
#include <fcntl.h>
#include <sys/socket.h> /* for socket(), bind(), and connect() */
#include <sys/select.h> /* for fd_set() */
#include <arpa/inet.h>  /* for sockaddr_in and inet_ntoa() */
#include <unistd.h>     /* for close() */
#endif

/* interface server command numbers */
#define SETCHAN     0x00    /* set channel number for subsequent commands */
#define DBCASTAPI   0x01    /* databroadcast API command */
#define CONFIGAPI   0x02    /* vbit2 configuration API command */
#define PAGESAPI    0x03    /* page data API */
#define GETAPIVER   0x04    /* get API version number */

/* interface server response codes */
#define CMDOK       0x00    /* command successful */
#define CMDTRUNC    0xfc    /* command completed but data was truncated */
#define CMDNOENT    0xfd    /* command failed because entity doesn't exist */
#define CMDBUSY     0xfe    /* command failed because operation is blocked */
#define CMDERR      0xff    /* command failed */

/* command numbers for databroadcast API */
#define DCRAW       0x00    /* push raw packet data to datacast buffer */
#define DCFORMATA   0x01    /* push format A packet to datacast buffer */
#define DCFORMATB   0x02    /* placeholder - may never implement */

/* command numbers for vbit2 configuration API */
#define CONFRAFLAG  0x00    /* get/set row adaptive flag */
#define CONFRBYTES  0x01    /* get/set BSDP reserved bytes */
#define CONFSTATUS  0x02    /* get/set 20 byte BSDP status message */
#define CONFHEADER  0x03    /* get/set 32 byte header template */
#define CONFENHANC  0x04    /* Get/Set/Delete magazine enhancements */

/* command numbers for page data API */
#define PAGEDELETE  0x00    /* remove a page from the service */
#define PAGEOPEN    0x01    /* open a page for updating */
#define PAGESETSUB  0x02    /* select subpage */
#define PAGEDELSUB  0x03    /* delete subpage */
#define PAGECLOSE   0x04    /* close updated page */
#define PAGEFANDC   0x05    /* get/set page function and coding */
#define PAGEOPTNS   0x06    /* get/set subpage options */
#define PAGEROW     0x07    /* read/write/delete subpage row data */
#define PAGELINKS   0x08    /* get/set fastext link values */

namespace vbit

{
    class ClientState
    {
        public:
            int socket = -1;
            int channel = -1;
            std::shared_ptr<TTXPageStream> page = nullptr;
            std::shared_ptr<Subpage> subpage = nullptr;
    };
    class InterfaceServer
    {
        public:
            InterfaceServer(Configure *configure, Debug *debug, PageList *pageList);
            ~InterfaceServer();
            
            void run();
            bool GetIsActive(){return _isActive;}; /* is interface server in use? */
            
            
            PacketDatacast** GetDatachannels() { PacketDatacast **channels=_datachannel; return channels; };
            
        private:
            const uint8_t APIVERSION[3] = {1,0,0}; // Version number for interface API.
            
            Configure* _configure;
            Debug* _debug;
            PageList* _pageList;
            PacketDatacast* _datachannel[16]; /* array of datacast sources */
            
            static const uint16_t MAXPENDING=5;
            
            int _portNumber;
            int _serverSock;
            
            std::list<ClientState> _clients;
            uint16_t _maxClients;
            
            bool _isActive;
            
            void SocketError(std::string errorMessage); // handle fatal socket errors
            void CloseClient(ClientState *client); // clean up after a connected client
    };
}

#endif


================================================
FILE: masterClock.h
================================================
#ifndef _MASTERCLOCK_H_
#define _MASTERCLOCK_H_

#include <cstdint>
#include <ctime>

namespace vbit
{
    class MasterClock {
        public:
            struct timeStruct {
                time_t seconds;
                uint8_t fields;
            };
            
            static MasterClock *Instance(){
                if (!instance)
                    instance = new MasterClock;
                return instance;
            }
            
            void SetMasterClock(timeStruct t){ _masterClock = t; }
            timeStruct GetMasterClock(){ return _masterClock; }
            
        private:
            MasterClock(){ _masterClock = {time(NULL)-1, 0}; }; // initialise master clock to system time - 1
            static MasterClock *instance;
            timeStruct _masterClock;
    };
}

#endif // _MASTERCLOCK_H_


================================================
FILE: normalpages.cpp
================================================

#include "normalpages.h"

using namespace vbit;

NormalPages::NormalPages(int mag, PageList *pageList, Debug *debug) :
    _mag(mag),
    _pageList(pageList),
    _debug(debug)
{
    _iter=_NormalPagesList.begin();
    _page=nullptr;
}

NormalPages::~NormalPages()
{

}

void NormalPages::addPage(std::shared_ptr<TTXPageStream> p)
{
    p->SetNormalFlag(true);
    
    for (std::list<std::shared_ptr<TTXPageStream>>::iterator it=_NormalPagesList.begin();it!=_NormalPagesList.end();++it)
    {
        // find first page with a higher number
        std::shared_ptr<TTXPageStream> ptr = *it;
        if (ptr->GetPageNumber() > p->GetPageNumber())
        {
            _NormalPagesList.insert(it,p);
            return;
        }
    }
    // if we are here we ran to the end of the list without a match
    _NormalPagesList.push_back(p);
}

std::shared_ptr<TTXPageStream> NormalPages::NextPage()
{
    if (_page == nullptr)
    {
        _iter=_NormalPagesList.begin();
        _page = *_iter;
    }
    else
    {
        ++_iter;
        _page = *_iter;
    }

    while(true)
    {
        if (_iter == _NormalPagesList.end())
        {
            _page = nullptr;
            return _page;
        }
        
        if (_page)
        {
            if (_page->GetOneShotFlag())
            {
                _page->SetNormalFlag(false);
                _iter = _NormalPagesList.erase(_iter); // remove oneshot pages from the page list
                _page = *_iter;
                continue;
            }
            
            if (_page->GetLock()) // try to lock this page against changes
            {
                /* remove pointers from this list if the pages are marked for deletion */
                if (_page->GetIsMarked() && _page->GetNormalFlag()) // only remove it once
                {
                    std::stringstream ss;
                    ss << "[NormalPages::NextPage] Deleted " << std::hex << (_page->GetPageNumber());
                    _debug->Log(Debug::LogLevels::logINFO,ss.str());
                    _iter = _NormalPagesList.erase(_iter);
                    _page->SetNormalFlag(false);
                    _pageList->RemovePage(_page); // try to remove it from the pagelist immediately - will free the lock
                    _page = *_iter;
                    continue; // jump back to loop
                }
                else if (_page->Special())
                {
                    std::stringstream ss;
                    ss << "[NormalPages::NextPage] page became Special "  << std::hex << (_page->GetPageNumber());
                    _debug->Log(Debug::LogLevels::logINFO,ss.str());
                    _iter = _NormalPagesList.erase(_iter);
                    _page->SetNormalFlag(false);
                }
                else if ((_page->GetPageNumber() & 0xFF) == 0xFF) // never return page mFF from the page list
                {
                    ++_iter;
                }
                else if (_page->GetSubpageCount() == 0) // skip pages with no subpages
                {
                    ++_iter;
                }
                else
                {
                    return _page; // return page locked
                }
                
                _page->FreeLock(); // must unlock page again
                _page = *_iter;
            }
            else
            {
                // skip page
                ++_iter;
                _page = *_iter;
            }
        }
    }
}


================================================
FILE: normalpages.h
================================================
#ifndef _NORMALPAGES_H
#define _NORMALPAGES_H

#include <list>
#include <mutex>

#include "debug.h"
#include "ttxpagestream.h"
#include "pagelist.h"

// list of normal pages

namespace vbit
{

class NormalPages
{
    public:
        /** Default constructor */
        NormalPages(int mag, PageList *pageList, Debug *debug);
        /** Default destructor */
        virtual ~NormalPages();

        std::shared_ptr<TTXPageStream> NextPage();

        void addPage(std::shared_ptr<TTXPageStream> p);

    protected:

    private:
        int _mag;
        PageList* _pageList;
        Debug* _debug;
        std::list<std::shared_ptr<TTXPageStream>> _NormalPagesList;
        std::list<std::shared_ptr<TTXPageStream>>::iterator _iter;
        std::shared_ptr<TTXPageStream> _page;
};

}

#endif // _NORMALPAGES_H


================================================
FILE: packet.cpp
================================================
#include "packet.h"
#include "version.h"

using namespace vbit;

Packet::Packet(int mag, int row) : _isHeader(false), _coding(CODING_7BIT_TEXT)
{
    //ctor
    _packet.fill(0x20); // fill with spaces
    SetMRAG(mag, row); // overwrite front 5 bytes
    assert(_row!=0); // Use Header for row 0
}

Packet::~Packet()
{
    //dtor
}

void Packet::SetRow(int mag, int row, std::array<uint8_t, 40> val, PageCoding coding)
{
    SetMRAG(mag, row);
    
    _isHeader=false; // Because it can't be a header
    std::copy(val.begin(), val.end(), _packet.begin() + 5);
    
    _coding = coding;
    
    switch(coding)
    {
        case CODING_PER_PACKET:
        {
            _coding = Page::ReturnPageCoding(_packet[5] & 0xF); // set packet coding based on first byte of packet
            /* fallthrough */
            [[gnu::fallthrough]];
        }
        case CODING_13_TRIPLETS:
        case CODING_HAMMING_8_4:
        case CODING_HAMMING_7BIT_GROUPS:
        {
            _packet[5] = Hamming8EncodeTable[_packet[5] & 0x0F]; // first byte is hamming 8/4 coded
            break;
        }
        case CODING_7BIT_TEXT:
        {
            _packet[5] = OddParityTable[_packet[5] & 0x7f]; // set parity on first byte
        }
        default:
        {
            break;
        }
    }
    
    switch(_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)
            {
                // allow this substitution to overflow the template
                int len = strlen(VBIT2_VERSION);
                if (off + len > 45)
                    len = 45 - off; // but clamp to the end of the packet
                std::copy_n(VBIT2_VERSION,len,_packet.begin() + off);
                if (len < 6)
                {
                    for (int i = len; i < 6; i++)
                        _packet[off+i] = 0x20; // insert spaces if version string is too short
                }
            }
            
            // first byte parity already set by first switch statement
            Parity(6);
            break;
        }
        case CODING_13_TRIPLETS:
        {
            // Special handler to allow stuffing enhancement packets in as OL rows
            // Each 18 bits of data for a triplet is coded in the input line as
            // three bytes least significant first where each byte contains 6 data
            // bits in b0-b5.
            // designation code is 8/4 hamming coded by first switch statement
            /* 0x0a and 0x00 in the hammed output is causing a problem so disable this until they are fixed (output will be gibberish) */
            int triplet;
            for (int i = 1; i<=13; i++)
            {
                triplet = _packet[i*3+3] & 0x3F;
                triplet |= (_packet[i*3+4] & 0x3F) << 6;
                triplet |= (_packet[i*3+5] & 0x3F) << 12;
                Hamming24EncodeTriplet(i, triplet);
            }
            break;
        }
        case CODING_HAMMING_8_4:
        {
            // first byte already hamming 8/4 coded by first switch statement
            for (int i = 1; i<40; i++)
            {
                _packet[5+i] = Hamming8EncodeTable[_packet[5+i] & 0x0F];
            }
            break;
        }
        case CODING_HAMMING_7BIT_GROUPS:
        {
            // first byte already hamming 8/4 coded by first switch statement
            for (int i = 1; i<8; i++)
            {
                _packet[5+i] = Hamming8EncodeTable[_packet[5+i] & 0x0F];
            }
            for (int i = 8; i<20; i++)
            {
                _packet[5+i] = OddParityTable[(uint8_t)(_packet[5+i]&0x7f)];
            }
            for (int i = 20; i<28; i++)
            {
                _packet[5+i] = Hamming8EncodeTable[_packet[5+i] & 0x0F];
            }
            for (int i = 28; i<40; i++)
            {
                _packet[5+i] = OddParityTable[(uint8_t)(_packet[5+i]&0x7f)];
            }
            break;
        }
        case CODING_8BIT_DATA:
        {
            // do nothing to 8-bit data
            break;
        }
    }
}

void Packet::SetX27CRC(uint16_t crc)
{
    if (Hamming8DecodeTable[_packet[5]] == 0) // only set CRC bytes for packet X/27/0
    {
        _packet[43]=crc >> 8;
        _packet[44]=crc & 0xFF;
    }
}

void Packet::SetPacketRaw(std::vector<uint8_t> data)
{
    data.resize(40, 0x00); // ensure correct length
    std::copy(data.begin(), data.end(), _packet.begin() + 5);
    _coding = CODING_8BIT_DATA; // don't allow this to be re-processed with parity etc
}

// Set CRI and MRAG. Leave the rest of the packet alone
void Packet::SetMRAG(uint8_t mag, uint8_t row)
{
    _packet[0]=0x55; // clock run in
    _packet[1]=0x55; // clock run in
    _packet[2]=0x27; // framing code
    
    _packet[3]=Hamming8EncodeTable[mag%8+((row%2)<<3)]; // mag + bit 3 is the lowest bit of row
    _packet[4]=Hamming8EncodeTable[((row>>1)&0x0f)];
    _isHeader=row==0;
    _row=row;
    _mag=mag;
}

/** get_offset_time.
 * Given a parameter of say %t+02
 * where str[2] is + or -
 * str[4:3] is a two digit of half hour offsets from UTC
 * @return local time at offset from UTC
 */
bool Packet::get_offset_time(time_t t, uint8_t* str)
{
    char strTime[6];
    time_t rawtime = t;
    struct tm *tmGMT;

    // What is our offset in seconds?
    int offset=((str[3]-'0')*10+str[4]-'0')*30*60; // @todo We really ought to validate this

    // Is it negative (west of us?)
    if (str[2]=='-')
        offset=-offset;
    else
        if (str[2]!='+') return false; // Must be + or -

    // Add the offset to the time value
    rawtime+=offset;

    tmGMT = gmtime(&rawtime);

    strftime(strTime, 21, "%H:%M", tmGMT);
    std::copy_n(strTime,5,str);
    return true;
}

int Packet::GetOffsetOfSubstition(std::string string)
{
    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.
 * 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<uint8_t, PACKETSIZE>* Packet::tx()
{
    // get master clock singleton
    MasterClock *mc = mc->Instance();
    time_t t = mc->GetMasterClock().seconds;
    
    // Get local time
    struct tm * timeinfo;
    timeinfo=localtime(&t);
    
    char tmpstr[21];
    int off;
    
    if (_isHeader)
    {
        // substitutions already done in HeaderText
    }
    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 ========
        off = Packet::GetOffsetOfSubstition("%%%T");
        
        if (off > -1)
        {
            #ifdef RASPBIAN
            get_temp(tmpstr);
            std::copy_n(tmpstr,4,_packet.begin() + off);
            #else
            std::copy_n("err ",4,_packet.begin() + off);
            #endif
        }

        // ======= WORLD TIME ========
        // Special case for world time. Put %t<+|-><hh> to get local time HH:MM offset by +/- half hours
        for (;;)
        {
            off = Packet::GetOffsetOfSubstition("%t+");
            if (off == -1)
            {
                off = Packet::GetOffsetOfSubstition("%t-");
            }
            if (off > -1)
            {
                //std::cout << "[test 1]" << _packet << std::endl;
                get_offset_time(t, _packet.data() + off); // TODO: something with return value
                //exit(4);
            }
            else
                break;
        }
        // ======= NETWORK ========
        // Special case for network address. Put %%%%%%%%%%%%%%n to get network address in form xxx.yyy.zzz.aaa with trailing spaces (15 characters total)
        off = Packet::GetOffsetOfSubstition("%%%%%%%%%%%%%%n");
        if (off > -1)
        {
            #ifndef WIN32
            get_net(tmpstr);
            std::copy_n(tmpstr,15,_packet.begin() + off);
            #else
            std::copy_n("not implemented",15,_packet.begin() + off);
            #endif
        }
        // ======= TIME AND DATE ========
        // Special case for system time. Put %%%%%%%%%%%%timedate to get time and date
        off = Packet::GetOffsetOfSubstition("%%%%%%%%%%%%timedate");
        if (off > -1)
        {
            int num = strftime(tmpstr, 21, "\x02%a %d %b\x03%H:%M/%S", timeinfo);
            std::copy_n(tmpstr,num,_packet.begin() + off);
        }
        
        Parity(5); // redo the parity because substitutions will need processing
    }
    
    return &_packet;
}

/** 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, std::string text)
{
    uint8_t cbit;
    SetMRAG(mag,0);
    _packet[5]=Hamming8EncodeTable[page%0x10];
    _packet[6]=Hamming8EncodeTable[page/0x10];
    _packet[7]=Hamming8EncodeTable[(subcode&0x0f)];         // S1 four bits
    subcode>>=4;
    // Map the page settings control bits from MiniTED to actual teletext packet.
    // To find the MiniTED settings look at the tti format document.
    // To find the target bit position these are in reverse order to tx and not hammed.
    // So for each bit in ETSI document, just divide the bit number by 2 to find the target location.
    // Where ETSI says bit 8,6,4,2 this maps to 4,3,2,1 (where the bits are numbered 1 to 8)
    cbit=0;
    if (control & 0x4000) cbit=0x08;                        // C4 Erase page
    _packet[8]=Hamming8EncodeTable[(subcode&0x07) | cbit];  // S2 (3 bits) add C4
    subcode>>=4;
    _packet[9]=Hamming8EncodeTable[(subcode&0x0f)];         // S3 four bits
    subcode>>=4;
    
    cbit=0;
    if (control & 0x0001) cbit=0x04;                        // C5 Newsflash
    if (control & 0x0002) cbit|=0x08;                       // C6 Subtitle
    _packet[10]=Hamming8EncodeTable[(subcode&0x03) | cbit]; // S4 C6, C5
    
    cbit=0;
    if (control & 0x0004)  cbit=0x01;                       // C7 Suppress Header
    if (control & 0x0008) cbit|=0x02;                       // C8 Update
    if (control & 0x0010) cbit|=0x04;                       // C9 Interrupted sequence
    if (control & 0x0020) cbit|=0x08;                       // C10 Inhibit display
    _packet[11]=Hamming8EncodeTable[cbit];                  // C7 to C10
    
    cbit=(control & 0x0380) >> 6;                           // Shift the language bits C12,C13,C14.
    
    // 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)

    _isHeader=true; // Because it must be a header
    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
    MasterClock *mc = mc->Instance();
    time_t t = mc->GetMasterClock().seconds;
    
    // Get local time
    struct tm * timeinfo;
    timeinfo=localtime(&t);
    
    char tmpstr[4];
    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)
    {
        int num = strftime(tmpstr,4,"%a",timeinfo);
        if (num){
            _packet[off]=tmpstr[0];
            _packet[off+1]=(num > 1)?tmpstr[1]:' ';
            _packet[off+2]=(num > 2)?tmpstr[2]:' ';
        }
    }

    // month name - %%b
    off = Packet::GetOffsetOfSubstition("%%b");
    if (off > -1)
    {
        int num = strftime(tmpstr,4,"%b",timeinfo);
        if (num){
            _packet[off]=tmpstr[0];
            _packet[off+1]=(num > 1)?tmpstr[1]:' ';
            _packet[off+2]=(num > 2)?tmpstr[2]:' ';
        }
    }
    
    // day of month with leading zero - %d
    off = Packet::GetOffsetOfSubstition("%d");
    if (off > -1)
    {
        strftime(tmpstr,3,"%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)
    {
        // windows doesn't support %e so just use %d and blank leading zero
        strftime(tmpstr,3,"%d",timeinfo);
        if (tmpstr[0] == '0')
            _packet[off]=' ';
        else
            _packet[off]=tmpstr[0];
        _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
}

/**
 * @brief Set parity bits.
 * \param Offset is normally 5 for text rows, 13 for header
 */
void Packet::Parity(uint8_t offset)
{
    int i;
    //uint8_t c;
    for (i=offset;i<PACKETSIZE;i++)
    {
        _packet[i]=OddParityTable[_packet[i] & 0x7f];
    }
}

void Packet::Fastext(std::array<FastextLink, 6> links, int mag)
{
    uint16_t lp, ls;
    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 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++)
    {
        lp=links[i].page;
        if (lp == 0) lp = 0x8ff; // turn zero into 8FF to be ignored
        ls=links[i].subpage;

        uint8_t m=(lp/0x100 ^ mag); // calculate the relative magazine
        _packet[p++]=Hamming8EncodeTable[lp & 0xF];             // page units
        _packet[p++]=Hamming8EncodeTable[(lp & 0xF0) >> 4];     // page tens
        _packet[p++]=Hamming8EncodeTable[ls & 0xF];             // S1
        _packet[p++]=Hamming8EncodeTable[((m & 1) << 3) | ((ls >> 4) & 0xF)]; // S2 + M1
        _packet[p++]=Hamming8EncodeTable[((ls >> 8) & 0xF)];    // S3
        _packet[p++]=Hamming8EncodeTable[((m & 6) << 1) | ((ls >> 4) & 0x3)]; // S4 + M2, M3
    }
}

int Packet::IDLA(uint8_t datachannel, uint8_t flags, uint8_t ial, uint32_t spa, uint8_t ri, uint8_t ci, std::vector<uint8_t> data)
{
    _coding = CODING_8BIT_DATA; // don't allow this to be re-processed with parity etc
    
    SetMRAG(datachannel & 0x7,((datachannel & 8) >> 3) + 30);
    
    _packet[5]=Hamming8EncodeTable[flags & 0xe]; // Format Type
    _packet[6]=Hamming8EncodeTable[ial&0xf]; // Interpretation and Address Length
    
    uint8_t p = 7;
    
    for (uint8_t i = 0; i < (ial&0x7) && i < 7; i++)
    {
        _packet[p++] = Hamming8EncodeTable[(spa >> (4 * i)) & 0xf]; // variable number of Service Packet Address nibbles
    }
    
    if (flags & IDLA_RI)
        _packet[p++]=ri; // Repeat Indicator
    
    uint8_t startOfCRC = p; // where the scope of CRC begins
    
    int sameCount = 0;
    
    if (flags & IDLA_CI)
    {
        _packet[p++]=ci; // explicit Continuity Indicator
        sameCount = (ci == 0x00 || ci == 0xff) ? 1 : 0;
    }
    
    uint8_t DLoffset = p; // store this position in case it needs to be updated later
    if (flags & IDLA_DL)
    {
        _packet[p++] = 0; // initialise Data Length as zero and update it as we add bytes
        sameCount = 0; // ignore this first zero as any amount of data that would result in a dummy byte would mean DL is > 0
    }
    
    // remaining space is 45 - p - 2 crc bytes
    
    unsigned int bytesSent = 0; // count how much of the payload we fit in packet
    while (p < 43)
    {
        if (bytesSent < data.size())
        {
            _packet[p] = data[bytesSent++];
            if (flags & IDLA_DL)
                _packet[DLoffset]++;
            
            if ((_packet[p] == _packet[p-1]) && ((uint8_t)(_packet[p]) == 0xff || (uint8_t)(_packet[p]) == 0x00))
            {
                sameCount++;
                
                if ((uint8_t)(_packet[p]) == (uint8_t)(_packet[p-1]))
                {
                    if (sameCount >= 7 && p < 42)
                    {
                        sameCount = 0;
                        _packet[++p] = 0xaa; // add a dummy byte
                        
                        if (flags & IDLA_DL)
                            _packet[DLoffset]++;
                    }
                }
            }
            else
                sameCount = 0; // reset the counter for dummy bytes
            
            p++;
        }
        else
        {
            _packet[p++] = 0xaa; // fill unused part of packet with dummy bytes
        }
    }
    
    uint16_t crc = 0;
    
    for (uint8_t i = startOfCRC; i < 43; i++)
    {
        IDLcrc(&crc, _packet[i]); // calculate CRC for user data
    }
    
    if (!(flags & IDLA_CI)) // implicit Continuity Indicator
    {
        // modify the CRC so that both bytes are equal to ci
        
        uint16_t tmpcrc = crc; // stash the crc
        
        crc = (ci << 8) | ci; // initialise crc with ci value in both bytes
        
        ReverseCRC(&crc, tmpcrc>>8); // reverse the crc from desired value with previously calculated crc as the input
        ReverseCRC(&crc, tmpcrc&0xff);
    }
    
    _packet[43]=crc&0xff; // store modified crc in packet
    _packet[44]=crc>>8;
    
    return bytesSent;
}

void Packet::IDLcrc(uint16_t *crc, uint8_t data)
{
    // Perform the IDL A crc
    *crc ^= data;
    
    for (uint8_t i = 0; i < 8; i++)
    {
        *crc = (*crc & 1) ? (*crc >> 1) ^ 0x8940 : (*crc >> 1);
    }
}

void Packet::ReverseCRC(uint16_t *crc, uint8_t byte)
{
    /* reverse the IDL A crc */
    uint8_t bit;
    for (uint8_t i = 0; i < 8; i++)
    {
        bit =  (byte >> (7-i)) & 1;
        *crc = (*crc & 0x8000) ? (((*crc << 1) | bit) ^ 0x1281) : ((*crc << 1) | bit);
    }
}

#ifdef RASPBIAN
/** get_temp
 *  Pinched from raspi-teletext demo.c
 * @return Four character temperature in degrees C eg. "45.7"
 */
bool Packet::get_temp(char* str)
{
    FILE *fp;
    char *pch;
    char tmp[100];

    fp = popen("/usr/bin/vcgencmd measure_temp", "r");
    fgets(tmp, 99, fp);
    pclose(fp);
    pch = strtok (tmp,"=\n");
    pch = strtok (NULL,"=\n");
    strncpy(str,pch,5);
    return true; // @todo
}
#endif

#ifndef WIN32
/** get_net
 *  Pinched from raspi-teletext demo.c
 * @return network address as 20 characters
 * Sample response
 * 3: wlan0    inet 192.168.1.14/24 brd 192.168.1.255 scope global wlan0\       valid_lft forever preferred_lft forever
 */
bool Packet::get_net(char* str)
{
    FILE *fp;
    char *pch;

    int n;
    char temp[100];
    fp = popen("/sbin/ip -o -f inet addr show scope global", "r");
    fgets(temp, 99, fp);
    pclose(fp);
    pch = strtok (temp," \n/");
    for (n=1; n<4; n++)
    {
        pch = strtok (NULL, " \n/");
    }
    // If we don't have a connection established, try not to crash
    if (pch==NULL)
    {
        strcpy(str,"IP address????");
        return false;
    }
    strncpy(str,pch,15);
    return true; // @todo
}
#endif

void Packet::Hamming24EncodeTriplet(uint8_t index, uint32_t triplet)
{
    if (index<1) return;
    
    uint8_t D5_D11;
    uint8_t D12_D18;
    uint8_t P5, P6;
    uint8_t Byte_0;

    Byte_0 = (Hamming24EncodeTable0[(triplet >> 0) & 0xFF] ^ Hamming24EncodeTable1[(triplet >> 8) & 0xFF] ^ Hamming24EncodeTable2[(triplet >> 16) & 0x03]);
    _packet[index*3+3] = Byte_0;

    D5_D11 = (triplet >> 4) & 0x7F;
    D12_D18 = (triplet >> 11) & 0x7F;

    P5 = 0x80 & ~(Hamming24ParityTable[0][D12_D18] << 2);
    _packet[index*3+4] = D5_D11 | P5;

    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);
    }
}


================================================
FILE: packet.h
================================================
#ifndef _PACKET_H_
#define _PACKET_H_

#include <cstdint>
#include <algorithm>
#include <iostream>
#include <vector>
#include <array>
#include <iomanip>
#include <cstring>
#include <ctime>
#include "tables.h"
#include <cassert>
#include "page.h"
#include "masterClock.h"

/**
 * Teletext packet.
 * Deals with teletext packets including header, text rows, enhanced packets, service packets and parity
 */

#define PACKETSIZE 45
namespace vbit
{
    class Packet
    {
        public:
            /** row constructor */
            Packet(int mag, int row);

            /** Default destructor */
            virtual ~Packet();

            inline std::array<uint8_t, PACKETSIZE> Get_packet() { return _packet; }
            
            /** SetPacketRaw
             * Copy the supplied raw binary data into the last 40 bytes of packet
             * \param data New 40 byte binary packet data
             */
            void SetPacketRaw(std::vector<uint8_t> data);
            
            /** tx
             * @return pointer to packet data vector
             * We create transmission ready packets of 45 bytes.
             */
            std::array<uint8_t, PACKETSIZE>* tx();

            /** SetMRAG
             * Sets the first five bytes of the packet
             * Namely Two clock run in, one framing code, and two for magazine/row address group
             * CRI|CRI|FC|MRAG
             */
            void SetMRAG(uint8_t mag, uint8_t row);

            /** Header
             * @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, std::string text);

            /** Parity
             * Sets the parity of the bytes starting from offset
             * @param offset 5 (default) for normal text rows, 13 for headers
             */
            void Parity(uint8_t offset=5);

            /** IsHeader
             * Transmission rule: After a header packet, wait at least one field before transmitting rows.
             * @return true if the last packet out was a header packet.
             */
            bool IsHeader(){return _isHeader;};

            /** Create a Fastext packet
             * Requires a list of six links
             * @param links Array of six link values (0x100 to 0x8FF)
             * @param mag - Magazine number
             *
             */
            void Fastext(std::array<FastextLink, 6>links, int mag);
            
            enum IDLAFormatFlags : uint8_t
            {
                IDLA_NONE   = 0x0,
                IDLA_RI     = 0x2,
                IDLA_CI     = 0x4,
                IDLA_DL     = 0x8
            };
            
            /**
             * Independent data line format A, returns the number of payload bytes which were transmitted.
             * @param datachannel - channel from 0-15 per EN 300 708 6.4.2
             * @param flags - controls which options are in use
             * @param ial - Interpretation and Address Length
             * @param spa - Service Packet Address (0 to 24 bits)
             * @param ri - Repeat Indicator
             * @param ci - Continuity Indicator
             * @param data - payload
             */
            int IDLA(uint8_t datachannel, uint8_t flags, uint8_t ial, uint32_t spa, uint8_t ri, uint8_t ci, std::vector<uint8_t> data);
            
            /**
             * @brief Same as the row constructor, except it doesn't construct
             * @param mag - Magazine number 0..7 where 0 is magazine 8
             * @param row - Row 0..31
             * @param val - The contents of the row text (40 characters)
             * @param coding -
             */
            void SetRow(int mag, int row, std::array<uint8_t, 40> 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:
        
        private:
            std::array<uint8_t, PACKETSIZE> _packet; // 45 byte packet
            bool _isHeader; //<! True if the packet is a header
            uint8_t _mag;//<! The magazine number this packet belongs to 0..7 where 0 is maazine 8
            uint8_t _row; //<! Row number 0 to 31
            PageCoding _coding; // packet coding
            
            int GetOffsetOfSubstition(std::string string);
            
            void IDLcrc(uint16_t *crc, uint8_t data); // calculate a CRC checksum for one byte
            void ReverseCRC(uint16_t *crc, uint8_t byte);

            bool get_offset_time(time_t t, uint8_t* str);
            bool get_net(char* str);
            
            /** Hamming 24/18
             * Hamming 24/18 encode a triplet and place at appropriate index in packet
             * The incoming triplet should be packed 18 bits of an uint32_t representing D1..D18
             * The triplet is repacked with parity bits
             */
            void Hamming24EncodeTriplet(uint8_t index, uint32_t triplet);
            
            void PageCRC(uint16_t *crc, uint8_t byte); // calculate a CRC checksum for one byte
            
            #ifdef RASPBIAN
            bool get_temp(char* str);
            #endif
    };
}

#endif // _PACKET_H_


================================================
FILE: packet830.cpp
================================================
#include <packet830.h>

using namespace vbit;

Packet830::Packet830(Configure *configure) :
    _configure(configure)
{
    //ctor
    ClearEvent(EVENT_P830_FORMAT_1);
    ClearEvent(EVENT_P830_FORMAT_2_LABEL_0 );
    ClearEvent(EVENT_P830_FORMAT_2_LABEL_1 );
    ClearEvent(EVENT_P830_FORMAT_2_LABEL_2 );
    ClearEvent(EVENT_P830_FORMAT_2_LABEL_3 );
}

Packet830::~Packet830()
{
    //dtor
}

Packet* Packet830::GetPacket(Packet* p)
{
    MasterClock *mc = mc->Instance();
    time_t timeRaw = mc->GetMasterClock().seconds;
    time_t timeLocal;
    struct tm *tmLocal;
    struct tm *tmGMT;
    int offsetHalfHours, year, month, day, hour, minute, second;
    uint32_t modifiedJulianDay;

    std::vector<uint8_t> data(40, 0x15); // 40 bytes filled with hamming coded 0

    p->SetMRAG(8, 30); // Packet 8/30

    uint8_t muxed = _configure->GetMultiplexedSignalFlag();

    uint8_t m = _configure->GetInitialMag();
    uint8_t pn = _configure->GetInitialPage();
    uint16_t sc = _configure->GetInitialSubcode();
    data.at(1) = Hamming8EncodeTable[pn & 0xF];
    data.at(2) = Hamming8EncodeTable[(pn & 0xF0) >> 4];
    data.at(3) = Hamming8EncodeTable[sc & 0xF];
    data.at(4) = Hamming8EncodeTable[((sc & 0xF0) >> 4) | ((m & 1) << 3)];
    data.at(5) = Hamming8EncodeTable[(sc & 0xF00) >> 8];
    data.at(6) = Hamming8EncodeTable[((sc & 0xF000) >> 12) | ((m & 6) << 1)];

    std::copy_n(_configure->GetServiceStatusString().begin(), 20, data.begin() + 20); // copy status display from std::string into packet data

    if (GetEvent(EVENT_P830_FORMAT_1))
    {
        ClearEvent(EVENT_P830_FORMAT_1);
        data.at(0) = Hamming8EncodeTable[muxed]; // Format 1 designation code
        
        uint16_t nic = _configure->GetNetworkIdentificationCode();
        data.at(7) = ReverseByteTab[(nic & 0xFF00) >> 8];
        data.at(8) = ReverseByteTab[nic & 0xFF];
        
        /* calculate number of seconds local time is offset from UTC */
        tmLocal = localtime(&timeRaw);
        
        /* convert tmLocal into a timestamp without correcting for timezones and summertime */
        #ifdef WIN32
        timeLocal = _mkgmtime(tmLocal);
        #else
        timeLocal = timegm(tmLocal);
        #endif
        
        offsetHalfHours = difftime(timeLocal, timeRaw) / 1800;
        // time offset code -bits 2-6 half hours offset from UTC, bit 7 sign bit
        // bits 0 and 7 reserved - set to 1
        data.at(9) = ((offsetHalfHours < 0) ? 0xC1 : 0x81) | ((abs(offsetHalfHours) & 0x1F) << 1);
        
        // get the time current UTC time into separate variables
        tmGMT = gmtime(&timeRaw);
        year = tmGMT->tm_year + 1900;
        month = tmGMT->tm_mon + 1;
        day = tmGMT->tm_mday;
        hour = tmGMT->tm_hour;
        minute = tmGMT->tm_min;
        second = tmGMT->tm_sec;
        
        modifiedJulianDay = calculateMJD(year, month, day);
        // generate five decimal digits of modified julian date decimal digits and increment each one.
        data.at(10) = (modifiedJulianDay % 100000 / 10000 + 1);
        data.at(11) = ((modifiedJulianDay % 10000 / 1000 + 1) << 4) | (modifiedJulianDay % 1000 / 100 + 1);
        data.at(12) = ((modifiedJulianDay % 100 / 10 + 1) << 4) | (modifiedJulianDay % 10 + 1);
        
        // generate six decimal digits of UTC time and increment each one before transmission
        data.at(13) = (((hour / 10) + 1) << 4) | ((hour % 10) + 1);
        data.at(14) = (((minute / 10) + 1) << 4) | ((minute % 10) + 1);
        data.at(15) = (((second / 10) + 1) << 4) | ((second % 10) + 1);
        
        // bytes 22-25 of the packet are marked reserved in the spec. Different broadcasters fill them with different values
        std::copy_n(_configure->GetReservedBytes().begin(), 4, data.begin() + 16); // copy from configuration
        
        p->SetPacketRaw(data);
        p->Parity(25); // set correct parity for status display
        return p;
    }

    // PDC labels 8/30/1
    if (GetEvent(EVENT_P830_FORMAT_2_LABEL_0))
    {
        ClearEvent(EVENT_P830_FORMAT_2_LABEL_0);
        data.at(0) = Hamming8EncodeTable[muxed | 2]; // Format 2 designation code
        
        //@todo
    }
    if (GetEvent(EVENT_P830_FORMAT_2_LABEL_1))
    {
        ClearEvent(EVENT_P830_FORMAT_2_LABEL_1);
        data.at(0) = Hamming8EncodeTable[muxed | 2]; // Format 2 designation code
        
        //@todo
    }
    if (GetEvent(EVENT_P830_FORMAT_2_LABEL_2))
    {
        ClearEvent(EVENT_P830_FORMAT_2_LABEL_2);
        data.at(0) = Hamming8EncodeTable[muxed | 2]; // Format 2 designation code
        
        //@todo
    }
    if (GetEvent(EVENT_P830_FORMAT_2_LABEL_3))
    {
        ClearEvent(EVENT_P830_FORMAT_2_LABEL_3 );
        data.at(0) = Hamming8EncodeTable[muxed | 2]; // Format 2 designation code
        
        //@todo
    }
    return nullptr;
}

bool Packet830::IsReady(bool force)
{
    // We will be waiting for 10 fields between becoming true
    // 8/30/1 should go out on the system clock seconds interval.
    (void)force; // silence error about unused parameter
    bool result = false;
    
    if (GetEvent(EVENT_P830_FORMAT_1) || // always ready to generate a format 1 packet
        (GetEvent(EVENT_P830_FORMAT_2_LABEL_0) && _label0) ||
        (GetEvent(EVENT_P830_FORMAT_2_LABEL_1) && _label1) ||
        (GetEvent(EVENT_P830_FORMAT_2_LABEL_2) && _label2) ||
        (GetEvent(EVENT_P830_FORMAT_2_LABEL_3) && _label3)
       )
    {
        result = true;
    }
    return result;
}

long Packet830::calculateMJD(int year, int month, int day)
{
    // calculate modified julian day number
    int a, b, c, d;
    a = (month - 14) / 12;
    b = day - 32075 + (1461 * (year + 4800 + a) / 4);
    c = (367 * (month - 2 - 12 * a) / 12);
    d = 3 * (((year + 4900 + a) / 100) / 4);
    return b + c - d - 2400001;
}


================================================
FILE: packet830.h
================================================
/** Packet source for 8/30/1 and 8/30/2
 */
#ifndef _PACKET830_H_
#define _PACKET830_H_

#include "packetsource.h"
#include "configure.h"

namespace vbit
{


class Packet830 : public PacketSource
{
  public:
    /** Default constructor */
    Packet830(Configure *configure);
    /** Default destructor */
    virtual ~Packet830();

    /** @todo Routines for cni, nic, MJD and station ident
     *  @todo Routines for PDC flag management
     */

    // overrides
    Packet* GetPacket(Packet* p) override;

    /**
     * Packet 830 must always wait for the correct field.
     * @param force is ignored
     */
    bool IsReady(bool force=false);

  protected:

  private:
    Configure* _configure;
    long calculateMJD(int year, int month, int day);
    
    // TODO: some temporary flags
    bool _label0 = false;
    bool _label1 = false;
    bool _label2 = false;
    bool _label3 = false;
};

}

#endif // _PACKET830_H_


================================================
FILE: packetDatacast.cpp
================================================
/* Packet source for datacast channels */

#include "packetDatacast.h"

using namespace vbit;

PacketDatacast::PacketDatacast(uint8_t datachannel, Configure* configure) :
    _datachannel(datachannel)
{
    uint16_t datacastLines = configure->GetDatacastLines();
    if (datacastLines == 0 || datacastLines > 4)
        datacastLines = 4; /* cap at 4 lines */
    _bufferSize = datacastLines*4; /* assign space for around 4 fields */
    
    for (int i=0; i<_bufferSize; i++){
        // build packet buffer
        _packetBuffer.push_back(new Packet(8,25));
    }
    // set head and tail indices
    _head = 0;
    _tail = 0;
}

PacketDatacast::~PacketDatacast()
{
    
}

int PacketDatacast::PushRaw(std::vector<uint8_t> *data)
{
    /* push 40 bytes of raw packet data into the buffer */
    Packet* p = GetFreeBuffer();
    if (p == nullptr)
        return -1;
    
    p->SetPacketRaw(*data); // copy data into buffer packet
    _head = (_head + 1) % _bufferSize; // advance head on circular buffer
    
    return 0;
}

int PacketDatacast::PushIDLA(uint8_t flags, uint8_t ial, uint32_t spa, uint8_t ri, uint8_t ci, std::vector<uint8_t> *data)
{
    /* push a format A datacast packet into the buffer */
    int bytes = 0;
    Packet* p = GetFreeBuffer();
    if (p != nullptr)
    {
        bytes = p->IDLA(_datachannel, flags, ial, spa, ri, ci, *data);
        _head = (_head + 1) % _bufferSize; // advance head on circular buffer
    }
    
    return bytes;
}

Packet* PacketDatacast::GetFreeBuffer()
{
    /* gets a pointer to the next free buffer packet or a null pointer if buffer is full
       Does NOT actually advance the head to avoid a race condition */
    
    if ((_head + 1) % _bufferSize == _tail)
        return nullptr; // buffer full
    else
        return _packetBuffer[_head];
}

Packet* PacketDatacast::GetPacket(Packet* p)
{
    if (_tail == _head)
    {
        // generate some hardcoded datacast filler
        std::string str = "VBIT2 Datacast Service       ";
        std::vector<uint8_t> data(str.begin(), str.end());
        p->IDLA(8, Packet::IDLA_DL, 6, 0xfffffe, 0, 0, data);
        // TODO: make channel, address, and content of filler configurable
    }
    else
    {
        // copy data from buffer to packet
        p->SetMRAG(_datachannel & 0x7,((_datachannel & 8) >> 3) + 30);
        p->SetPacketRaw(std::vector<uint8_t>(_packetBuffer[_tail]->Get_packet().begin()+5, _packetBuffer[_tail]->Get_packet().end()));
        
        _tail = (_tail + 1) % _bufferSize; // advance tail on circular buffer
    }
    
    return p;
}

bool PacketDatacast::IsReady(bool force)
{
    bool result=false;
    
    if (GetEvent(EVENT_DATABROADCAST))
    {
        // Don't clear event, Service::_updateEvents explicitly turns it off for non datacast lines
        
        if (_tail != _head)
            result = true;
        else
            result = force;
    }
    return result;
}


================================================
FILE: packetDatacast.h
================================================
#ifndef PACKETDATACAST_H
#define PACKETDATACAST_H

#include "packetsource.h"
#include "configure.h"

namespace vbit
{
    class PacketDatacast : public PacketSource
    {
        public:
            PacketDatacast(uint8_t datachannel, Configure* configure);
            virtual ~PacketDatacast();
            
            Packet* GetPacket(Packet* p) override;
            bool IsReady(bool force=false);
            
            int PushRaw(std::vector<uint8_t> *data);
            int PushIDLA(uint8_t flags, uint8_t ial, uint32_t spa, uint8_t ri, uint8_t ci, std::vector<uint8_t> *data);
            
        protected:
        private:
            uint8_t _datachannel;
            
            std::vector<Packet*> _packetBuffer;
            uint8_t _bufferSize;
            uint8_t _head;
            uint8_t _tail;
            
            Packet* GetFreeBuffer();
    };
}

#endif // PACKETDATACAST_H


================================================
FILE: packetDebug.cpp
================================================
#include "packetDebug.h"

using namespace vbit;

PacketDebug::PacketDebug(Configure* configure, Debug* debug) :
    _configure(configure),
    _debug(debug),
    _debugPacketCI(0), // continuity counter for debug datacast packets
    _startupTime(time(NULL)),
    _masterClockSeconds(0),
    _masterClockFields(0),
    _systemClock(0),
    _debugType(FORMAT1),
    _clockFlags(CFLAGRESYNC),
    _magFlags(0)
{
    //ctor
    
    // TODO: make channel and address configurable
    _datachannel = 8; 
    _servicePacketAddress = 0xffffff;
}

PacketDebug::~PacketDebug()
{
    //dtor
}

Packet* PacketDebug::GetPacket(Packet* p)
{
    /* Generate simple datacast packets for timing measurement and magazine monitoring.
       The user data payload for each format type is padded to a fixed 26 bytes long.
       
       Format types are documented below.
       The byte offsets listed are the position within the datacast user data payload.
       These offsets are only valid after discarding any dummy bytes per EN 300 708 6.5.7.1!
    */
    
    std::vector<uint8_t> data;
    int mag;
    
    data.push_back(_debugType); // format type in this packet
    
    // all types start with the current master clock
    data.push_back(_masterClockSeconds >> 24);
    data.push_back(_masterClockSeconds >> 16);
    data.push_back(_masterClockSeconds >> 8);
    data.push_back(_masterClockSeconds);
    data.push_back(_masterClockFields);
    
    // 6 of 26 bytes used
    
    switch(_debugType)
    {
        default:
        case NONE: // format type 0
        {
            /* Bytes  Description
                   0: format type
                1-25: reserved
            */
            break;
        }
        case FORMAT1: // format type 1, system time
        {
            /* Bytes  Description
                   0: format type
                 1-4: current master clock seconds
                   5: current master clock field
                   6: debug stream data format version
                   7: clock flags
                        b0: master clock resynchronisation
                8-11: current system clock seconds
                  12: current system clock field
               13-16: program startup timestamp
               17-25: reserved
            */
            
            _debugType = FORMAT2; // cycle to format 2 on next packet
            
            data.push_back(VBIT2_DEBUG_VERSION); // Debug stream format version
            
            // clock flags
            data.push_back(_clockFlags);
            _clockFlags = 0; // clear flags
            
            // current system clock
            data.push_back(_systemClock >> 24);
            data.push_back(_systemClock >> 16);
            data.push_back(_systemClock >> 8);
            data.push_back(_systemClock);
            data.push_back(_systemClockFields);
            
            // vbit2 startup timestamp
            data.push_back(_startupTime >> 24);
            data.push_back(_startupTime >> 16);
            data.push_back(_startupTime >> 8);
            data.push_back(_startupTime);
            
            // 17 of 26 bytes used
            
            break;
        }
        case FORMAT2: // format type 2, magazine data
        {
            /* Bytes  Description
                   0: format type
                 1-4: current master clock seconds
                   5: current master clock field
                   6: magazine flags
                        b0: magazine cycle durations changed
                        b1: magazine page counts changed
                7-14: magazine (0-7) cycle durations in seconds
               15-22: magazine (0-7) page counts
               23-25: reserved
            */
            
            _debugType = FORMAT1; // cycle to format 1 on next packet
            
            // magazine flags
            data.push_back(_magFlags);
            _magFlags = 0; // clear flags
            
            for (mag=0; mag<8; mag++){
                data.push_back(_magDurations[mag]);
            }
            // these 8 bytes may result in a dummy byte!
            
            for (mag=0; mag<8; mag++){
                data.push_back(_magSizes[mag]);
            }
            // these 8 bytes may result in a dummy byte!
            
            // 22 of 26 bytes used
            
            break;
        }
    }
    
    data.insert(data.end(), 26 - data.size(), 0); // pad payload data to 26 bytes long (leave three bytes free for dummy bytes)
    
    // generate format A datacast packet with explicit data length, and implicit continuity indicator
    p->IDLA(_datachannel, Packet::IDLA_DL, 6, _servicePacketAddress, 0, _debugPacketCI++, data); 
    
    return p;
}

bool PacketDebug::IsReady(bool force)
{
    bool result=false;
    
    if (GetEvent(EVENT_DATABROADCAST) && GetEvent(EVENT_FIELD))
    {
        // Don't clear event, Service::_updateEvents explicitly turns it off for non datacast lines
        if (_debugType > FORMAT1) // we are in the middle of sending debug data already
        {
            result = true;
        }
        else
        {
            // update magazine data
            int mag;
            std::array<int, 8> magDurations = _debug->GetMagCycleDurations(); // get magazine durations in number of fields
            for (mag=0; mag<8; mag++){
                if(magDurations[mag] < 0 || magDurations[mag] / 50 > 255)
                    magDurations[mag] = 255; // clamp to 255 seconds
                else
                    magDurations[mag] = magDurations[mag] / 50; // truncate to whole seconds
                
                if (_magDurations[mag] != magDurations[mag])
                {
                    _magFlags |= MFLAGDURATION; // mag durations have changed!
                    _magDurations[mag] = magDurations[mag]; // update our internal variable
                }
            }
            
            std::array<int, 8> magSizes = _debug->GetMagSizes(); // get number of pages in each magazine
            for (mag=0; mag<8; mag++){
                if (_magSizes[mag] != magSizes[mag])
                {
                    _magFlags |= MFLAGPAGES; // mag sizes have changed!
                    _magSizes[mag] = magSizes[mag]; // update our internal variable
                }
            }
            
            if (_clockFlags || _magFlags || force) // generate packets if any of the content has changed, or if we are forcing output to use this source as datacast filler
                result = true;
        }
        
        ClearEvent(EVENT_FIELD); // don't jam all datacast lines in a field
    }
    
    return result;
}

void PacketDebug::TimeAndField(MasterClock::timeStruct masterClock, time_t systemClock, uint8_t systemClockFields, bool resync)
{
    // update the clocks in _debugData struct - called once per field by Service::_updateEvents()
    _masterClockSeconds = masterClock.seconds;
    _masterClockFields = masterClock.fields;
    _systemClock = systemClock;
    _systemClockFields = systemClockFields;
    if (resync)
        _clockFlags |= CFLAGRESYNC; // set flag
}


================================================
FILE: packetDebug.h
================================================
/** Packet source for debugging output
 */
#ifndef _PACKETDEBUG_H_
#define _PACKETDEBUG_H_

#include <list>
#include "packetsource.h"
#include "configure.h"
#include "debug.h"

#define VBIT2_DEBUG_VERSION 0x02   // Debug packet version

namespace vbit
{
    class PacketDebug : public PacketSource
    {
        public:
            /** Default constructor */
            PacketDebug(Configure* configure, Debug* debug);
            /** Default destructor */
            virtual ~PacketDebug();

            // overrides
            Packet* GetPacket(Packet* p) override;
            
            void TimeAndField(MasterClock::timeStruct masterClock, time_t systemClock, uint8_t systemClockFields, bool resync);
            
            bool IsReady(bool force=false);

        protected:

        private:
            Configure* _configure;
            Debug* _debug;
            
            uint8_t _datachannel;
            uint32_t _servicePacketAddress;
            uint8_t _debugPacketCI; // continuity indicator for databroadcast stream
            
            const time_t _startupTime;
            time_t _masterClockSeconds;
            uint8_t _masterClockFields;
            time_t _systemClock;
            uint8_t _systemClockFields;
            
            std::array<uint8_t, 8> _magDurations;
            std::array<uint8_t, 8> _magSizes;
            
            enum DebugTypes {NONE, FORMAT1, FORMAT2};
            DebugTypes _debugType;
            
            enum ClockFlags {CFLAGRESYNC = 1};
            uint8_t _clockFlags;
            
            enum MagFlags {MFLAGDURATION=1, MFLAGPAGES=2};
            uint8_t _magFlags;
    };
}

#endif // _PACKETDEBUG_H_


================================================
FILE: packetServer.cpp
================================================
/* Provide a TCP packet server which sends a whole frame of t42 data at a time to all connected clients */

#include "packetServer.h"

using namespace vbit;

PacketServer::PacketServer(Configure *configure, Debug *debug) :
    _debug(debug),
    _portNumber(configure->GetPacketServerPort()),
    _maxClients(configure->GetPacketServerMaxClients()),
    _isActive(false)
{
    /* initialise sockets */
    _serverSock = -1;
}

PacketServer::~PacketServer()
{
}

void PacketServer::DieWithError(std::string errorMessage)
{
    if (_serverSock >= 0)
    {
        #ifdef WIN32
            closesocket(_serverSock);
        #else
            close(_serverSock);
        #endif
    }
    
    for(std::list<int>::iterator it = _clientSocks.begin(); it != _clientSocks.end();)
    {
        int port = *it;
        it = _clientSocks.erase(it);
        #ifdef WIN32
            closesocket(port);
        #else
            close(port);
        #endif
    }
    
    perror(errorMessage.c_str());
    exit(1);
}

void PacketServer::SendField(std::vector<std::vector<uint8_t>> FrameBuffer)
{
    std::array<uint8_t, 42*32> RawFrameBuffer;
    RawFrameBuffer.fill(0x00); // clear two frames
    unsigned int i, j;
    
    for (i = 0; i < FrameBuffer.size(); i++)
    {
        int field = FrameBuffer[i][0]&1;
        int line = (FrameBuffer[i][1]<<8) + FrameBuffer[i][2];
        if (line < 16) // full field lines not supported in this output
        {
            for (j = 0; j < 42; j++)
            {
                RawFrameBuffer[(field*42*16)+(line*42)+j] = FrameBuffer[i][j+3];
            }
        }
    }
    
    int sock;
    int ret;
    
    _mtx.lock();
    for(std::list<int>::iterator it = _clientSocks.begin(); it != _clientSocks.end(); ++it)
    {
        sock = *it;
        if (sock >= 0)
        {
            ret = send(sock, (char*)RawFrameBuffer.data(), RawFrameBuffer.size(), 0);
            if (ret != RawFrameBuffer.size())
            {
                /*
                    We were unable to send a whole frame to the client. We either sent a partial frame or the send failed entirely.
                    This probably means that either there are network issues, or the client is not consuming data fast enough.
                    Either way trying to handle this adds a lot of complexity and risks getting the client desynchronised or blocking the thread trying to sort it out, so the best thing to do is probably just boot the client off and let it reconnect.
                */
                
                #ifdef WIN32
                    int e = WSAGetLastError();
                #else
                    int e = errno;
                #endif
                
                _debug->Log(Debug::LogLevels::logWARN,"[PacketServer::SendField] send() failed. Closing socket " + std::to_string(sock) + " send error " + std::to_string(e));
                
                #ifdef WIN32
                    closesocket(sock);
                #else
                    close(sock);
                #endif
                *it = -1; // mark this closed so it gets removed in the server thread
            }
        }
    }
    _mtx.unlock();
}

void PacketServer::run()
{
    _debug->Log(Debug::LogLevels::logINFO,"[PacketServer::run] TCP packet server thread started for "+(_maxClients?"max "+std::to_string(_maxClients):"unlimited")+" connections");
    
    int newSock;
    int sock;
    struct sockaddr_in address;
    unsigned short servPort;
    
    char readBuffer[BUFFLEN];
    
    fd_set readfds;
    
#ifdef WIN32
    int addrlen;
    WSADATA wsaData;
    int iResult;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0)
    {
        DieWithError("[PacketServer::run] WSAStartup failed");
    }
#else
    unsigned int addrlen;
#endif
    
    servPort = _portNumber;
    
    /* Create socket */
    if ((_serverSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        DieWithError("[PacketServer::run] socket() failed\n");
    
    int reuse = true;
    
    /* Allow multiple connnections */
    if(setsockopt(_serverSock, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)) < 0)
        DieWithError("[PacketServer::run] setsockopt() SO_REUSEADDR failed");
    
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = htonl(INADDR_ANY);
    address.sin_port = htons(servPort);
    
    /* bind socked */
    if (bind(_serverSock, (struct sockaddr *) &address, sizeof(address)) < 0)
        DieWithError("[PacketServer::run] bind() failed");
    
    /* Listen for incoming connections */
    if (listen(_serverSock, MAXPENDING) < 0)
        DieWithError("[PacketServer::run] listen() failed");
    
    addrlen = sizeof(address);
    
    int iopt = 42*32*25; /* 25 frames worth of t42 */
    
    while(true)
    {
        FD_ZERO(&readfds);
        FD_SET(_serverSock, &readfds);
        
        for(std::list<int>::iterator it = _clientSocks.begin(); it != _clientSocks.end(); ++it)
        {
            if (*it > -1)
                FD_SET(*it , &readfds);
        }
        _isActive = !(_clientSocks.empty());
        
        /* wait for activity on any socket */
        if ((select(FD_SETSIZE, &readfds, NULL, NULL, NULL) < 0) && (errno!=EINTR))
            DieWithError("[PacketServer::run] select() failed");
        
        if (FD_ISSET(_serverSock, &readfds))
        {
            /* incoming connection to server */
            if ((newSock = accept(_serverSock, (struct sockaddr *)&address, &addrlen))<0)
                DieWithError("[PacketServer::run] accept() failed");
            
            #ifdef WIN32
                u_long ul = 1;
                if (ioctlsocket(newSock, FIONBIO, &ul) < 0)
                    DieWithError("[PacketServer::run] ioctlsocket() failed");
            #else
                if (fcntl(newSock, F_SETFL, fcntl(newSock, F_GETFL, 0) | O_NONBLOCK) < 0)
                    DieWithError("[PacketServer::run] fcntl() failed");
            #endif
            
            if (setsockopt(newSock, SOL_SOCKET, SO_SNDBUF, (char *) &iopt, sizeof(iopt)) < 0 )
                DieWithError("[PacketServer::run] setsockopt() failed");
            
            if (_maxClients > 0 && _maxClients == _clientSocks.size())
            {
                /* no more client slots so reject */
                #ifdef WIN32
                    closesocket(newSock);
                #else
                    close(newSock);
                #endif
                _debug->Log(Debug::LogLevels::logWARN,"[PacketServer::run] reject new connection from " + std::string(inet_ntoa(address.sin_addr)) + " (too many connections)");
                break;
            }
                
            /* add to active sockets */
            _clientSocks.push_back(newSock);
            _debug->Log(Debug::LogLevels::logINFO,"[PacketServer::run] new connection from " + std::string(inet_ntoa(address.sin_addr)) + ":" + std::to_string(ntohs(address.sin_port)) + " as socket " + std::to_string(newSock));
        }
        else
        {
            /* a client socket has activity */
            for(std::list<int>::iterator it = _clientSocks.begin(); it != _clientSocks.end();)
            {
                sock = *it;
                
                if (sock >= 0 && FD_ISSET(sock , &readfds))
                {
                    /* socket has activity */
                    
                    int n = recv(sock, readBuffer, BUFFLEN, 0);
                    if (n == 0)
                    {
                        /* client disconnected */
                        getpeername(sock, (struct sockaddr*)&address, &addrlen);
                        
                        _debug->Log(Debug::LogLevels::logINFO,"[PacketServer::run] closing connection from " + std::string(inet_ntoa(address.sin_addr)) + ":" + std::to_string(ntohs(address.sin_port)) + " on socket " + std::to_string(sock));

                        _mtx.lock();
                        it = _clientSocks.erase(it);
                        #ifdef WIN32
                            closesocket(sock);
                        #else
                            close(sock);
                        #endif
                        _mtx.unlock();
                        
                    }
                    else if (n > 0)
                    {
                        // don't care what client sent right now
                        ++it;
                    }
                    else /* n < 0 */
                    {
                        #ifdef WIN32
                            int e = WSAGetLastError();
                        #else
                            int e = errno;
                        #endif
                        
                        getpeername(sock, (struct sockaddr*)&address, &addrlen);
                        
                        _debug->Log(Debug::LogLevels::logWARN,"[PacketServer::run] closing connection from " + std::string(inet_ntoa(address.sin_addr)) + ":" + std::to_string(ntohs(address.sin_port)) + " recv error " + std::to_string(e) + " on socket " + std::to_string(sock));
                        
                        /* close the socket when any error occurs */
                        
              
Download .txt
gitextract_a2zf_04h/

├── .editorconfig
├── .gitattributes
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── carousel.cpp
├── carousel.h
├── configure.cpp
├── configure.h
├── debug.cpp
├── debug.h
├── example-vbit.conf
├── filemonitor.cpp
├── filemonitor.h
├── getvbit2
├── interfaceServer.cpp
├── interfaceServer.h
├── masterClock.h
├── normalpages.cpp
├── normalpages.h
├── packet.cpp
├── packet.h
├── packet830.cpp
├── packet830.h
├── packetDatacast.cpp
├── packetDatacast.h
├── packetDebug.cpp
├── packetDebug.h
├── packetServer.cpp
├── packetServer.h
├── packetmag.cpp
├── packetmag.h
├── packetsource.cpp
├── packetsource.h
├── page.cpp
├── page.h
├── pagelist.cpp
├── pagelist.h
├── postupdate.sh
├── scripts/
│   ├── config.py
│   ├── known_services.json
│   ├── runvbit2.py
│   ├── teletext-update.py
│   └── vbit-config.py
├── service.cpp
├── service.h
├── specialpages.cpp
├── specialpages.h
├── tables.cpp
├── tables.h
├── teletext-update.service
├── teletext-update.timer
├── ttxline.cpp
├── ttxline.h
├── ttxpagestream.cpp
├── ttxpagestream.h
├── update.sh
├── updatedpages.cpp
├── updatedpages.h
├── vbit2.cpp
├── vbit2.h
├── vbit2.service
└── version.h
Download .txt
SYMBOL INDEX (64 symbols across 37 files)

FILE: carousel.h
  function namespace (line 14) | namespace vbit

FILE: configure.cpp
  type stat (line 9) | struct stat

FILE: configure.h
  function namespace (line 23) | namespace vbit

FILE: debug.h
  function namespace (line 9) | namespace vbit

FILE: filemonitor.cpp
  type timespec (line 276) | struct timespec
  type dirent (line 288) | struct dirent
  type stat (line 289) | struct stat

FILE: filemonitor.h
  function namespace (line 17) | namespace vbit

FILE: interfaceServer.cpp
  type sockaddr_in (line 94) | struct sockaddr_in
  type sockaddr (line 135) | struct sockaddr
  type sockaddr (line 169) | struct sockaddr
  type sockaddr (line 218) | struct sockaddr

FILE: interfaceServer.h
  function namespace (line 59) | namespace vbit

FILE: masterClock.h
  function namespace (line 7) | namespace vbit

FILE: normalpages.h
  function namespace (line 13) | namespace vbit

FILE: packet.cpp
  type tm (line 175) | struct tm
  type tm (line 217) | struct tm
  type tm (line 338) | struct tm

FILE: packet.h
  function namespace (line 23) | namespace vbit

FILE: packet830.cpp
  function Packet (line 21) | Packet* Packet830::GetPacket(Packet* p)

FILE: packet830.h
  function namespace (line 9) | namespace vbit

FILE: packetDatacast.cpp
  function Packet (line 56) | Packet* PacketDatacast::GetFreeBuffer()
  function Packet (line 67) | Packet* PacketDatacast::GetPacket(Packet* p)

FILE: packetDatacast.h
  function namespace (line 7) | namespace vbit

FILE: packetDebug.cpp
  function Packet (line 29) | Packet* PacketDebug::GetPacket(Packet* p)

FILE: packetDebug.h
  function namespace (line 13) | namespace vbit

FILE: packetServer.cpp
  type sockaddr_in (line 110) | struct sockaddr_in
  type sockaddr (line 149) | struct sockaddr
  type sockaddr (line 179) | struct sockaddr
  type sockaddr (line 225) | struct sockaddr
  type sockaddr (line 252) | struct sockaddr

FILE: packetServer.h
  function namespace (line 19) | namespace vbit

FILE: packetmag.cpp
  function Packet (line 47) | Packet* PacketMag::GetPacket(Packet* p)

FILE: packetmag.h
  function namespace (line 17) | namespace vbit

FILE: packetsource.h
  function namespace (line 13) | namespace vbit

FILE: page.cpp
  function PageCoding (line 223) | PageCoding Page::ReturnPageCoding(int pageCoding)

FILE: page.h
  type PageCoding (line 36) | enum PageCoding {CODING_7BIT_TEXT,CODING_8BIT_DATA,CODING_13_TRIPLETS,CO...
  type PageFunction (line 37) | enum PageFunction {LOP, DATABROADCAST, GPOP, POP, GDRCS, DRCS, MOT, MIP,...
  function namespace (line 39) | namespace vbit

FILE: pagelist.h
  function namespace (line 15) | namespace vbit

FILE: scripts/config.py
  function load (line 9) | def load():
  function save (line 46) | def save(configData):
  function getInstalledServices (line 53) | def getInstalledServices():
  function getSelectedService (line 57) | def getSelectedService():
  function selectService (line 71) | def selectService(name):
  function uninstallService (line 80) | def uninstallService(name):
  function installService (line 100) | def installService(service):
  function doServiceInstall (line 147) | def doServiceInstall(type, path, url):

FILE: scripts/runvbit2.py
  function signalHandler (line 9) | def signalHandler(_signo, _stack_frame):

FILE: scripts/teletext-update.py
  function updateService (line 14) | def updateService(service):

FILE: scripts/vbit-config.py
  function selectServiceMenu (line 25) | def selectServiceMenu():
  function installServiceMenu (line 38) | def installServiceMenu(servicesData, isGroup=False):
  function chooseSubservicesMenu (line 97) | def chooseSubservicesMenu(subservices):
  function customServiceMenu (line 120) | def customServiceMenu():
  function uninstallService (line 197) | def uninstallService():
  function optionsMenu (line 216) | def optionsMenu():
  function mainMenu (line 284) | def mainMenu():

FILE: service.h
  function namespace (line 22) | namespace vbit

FILE: specialpages.h
  function namespace (line 12) | namespace vbit

FILE: ttxline.h
  function class (line 15) | class TTXLine : public std::enable_shared_from_this<TTXLine>

FILE: ttxpagestream.h
  function namespace (line 16) | namespace vbit

FILE: updatedpages.h
  function namespace (line 12) | namespace vbit

FILE: vbit2.cpp
  function main (line 12) | int main(int argc, char** argv)

FILE: vbit2.h
  function namespace (line 20) | namespace vbit
Condensed preview — 65 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (358K chars).
[
  {
    "path": ".editorconfig",
    "chars": 209,
    "preview": "# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\nin"
  },
  {
    "path": ".gitattributes",
    "chars": 378,
    "preview": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Custom for Visual Studio\n*.cs     diff=csharp\n\n# St"
  },
  {
    "path": ".gitignore",
    "chars": 38,
    "preview": "# g++ temporary files\n*.o\n*.bak\n*.bak\n"
  },
  {
    "path": "Dockerfile",
    "chars": 501,
    "preview": "# make the buil container\nFROM alpine as build-env\nRUN apk add --no-cache build-base\nWORKDIR /vbit2\n# copy everything fr"
  },
  {
    "path": "LICENSE",
    "chars": 993,
    "preview": "Copyright (C) 2025, Peter Kwan, Alistair Cree, Alistair Buxton\n\nPermission to use, copy, modify, and distribute this sof"
  },
  {
    "path": "Makefile",
    "chars": 666,
    "preview": "CXX=g++\n\nCXXFLAGS = -g -O2 -Wall -MMD -MP -std=gnu++11 -fstack-protector-all -Wextra -I.\n\nLIBS = -lpthread -fstack-prote"
  },
  {
    "path": "README.md",
    "chars": 1475,
    "preview": "# VBIT2\n\nAn installation guide and more can be found in the [github wiki](https://github.com/peterkvt80/vbit2/wiki).\n\n##"
  },
  {
    "path": "carousel.cpp",
    "chars": 2719,
    "preview": "\n#include \"carousel.h\"\n\nusing namespace vbit;\n\nCarousel::Carousel(int mag, PageList *pageList, Debug *debug) :\n    _mag("
  },
  {
    "path": "carousel.h",
    "chars": 1010,
    "preview": "#ifndef _CAROUSEL_H\n#define _CAROUSEL_H\n\n#include <list>\n\n#include \"debug.h\"\n#include \"ttxpagestream.h\"\n#include \"pageli"
  },
  {
    "path": "configure.cpp",
    "chars": 23858,
    "preview": "/** Configure\n */\n#include \"configure.h\"\n\nusing namespace vbit;\n\nint Configure::DirExists(std::string *path)\n{\n    struc"
  },
  {
    "path": "configure.h",
    "chars": 4018,
    "preview": "#ifndef _CONFIGURE_H_\n#define _CONFIGURE_H_\n/** Configure processes settings related to the teletext service and vbit2 c"
  },
  {
    "path": "debug.cpp",
    "chars": 991,
    "preview": "#include <debug.h>\n\nusing namespace vbit;\n\nDebug::Debug() :\n    _debugLevel(logNONE)\n{\n    //ctor\n    _magDurations.fill"
  },
  {
    "path": "debug.h",
    "chars": 1143,
    "preview": "/** debugging class\n */\n#ifndef _DEBUG_H_\n#define _DEBUG_H_\n\n#include <iostream>\n#include <array>\n\nnamespace vbit\n{\n    "
  },
  {
    "path": "example-vbit.conf",
    "chars": 2321,
    "preview": "; example vbit config file\n; lines beginning with a semicolon are comment lines\n; settings are in the form name=value an"
  },
  {
    "path": "filemonitor.cpp",
    "chars": 20084,
    "preview": "#include \"filemonitor.h\"\n\nusing namespace vbit;\n\nFile::File(std::string filename) :\n    _page(new TTXPageStream()),\n    "
  },
  {
    "path": "filemonitor.h",
    "chars": 2776,
    "preview": "#ifndef _FILEMONITOR_H_\n#define _FILEMONITOR_H_\n\n#include <iostream>\n#include <sstream>\n#include <thread>\n#include <list"
  },
  {
    "path": "getvbit2",
    "chars": 2200,
    "preview": "#!/bin/bash\n\nif [ ! -f /etc/rpi-issue ]; then\n    echo \"This installer is intended for Raspberry Pi OS only\"\n    exit 1\n"
  },
  {
    "path": "interfaceServer.cpp",
    "chars": 65162,
    "preview": "/** A TCP server for various configuration and teletext input interfaces\n    A network interface for the injection of da"
  },
  {
    "path": "interfaceServer.h",
    "chars": 3872,
    "preview": "#ifndef _INTERFACESERVER_H_\n#define _INTERFACESERVER_H_\n\n#include \"configure.h\"\n#include \"debug.h\"\n#include \"pagelist.h\""
  },
  {
    "path": "masterClock.h",
    "chars": 836,
    "preview": "#ifndef _MASTERCLOCK_H_\n#define _MASTERCLOCK_H_\n\n#include <cstdint>\n#include <ctime>\n\nnamespace vbit\n{\n    class MasterC"
  },
  {
    "path": "normalpages.cpp",
    "chars": 3489,
    "preview": "\n#include \"normalpages.h\"\n\nusing namespace vbit;\n\nNormalPages::NormalPages(int mag, PageList *pageList, Debug *debug) :\n"
  },
  {
    "path": "normalpages.h",
    "chars": 812,
    "preview": "#ifndef _NORMALPAGES_H\n#define _NORMALPAGES_H\n\n#include <list>\n#include <mutex>\n\n#include \"debug.h\"\n#include \"ttxpagestr"
  },
  {
    "path": "packet.cpp",
    "chars": 22339,
    "preview": "#include \"packet.h\"\n#include \"version.h\"\n\nusing namespace vbit;\n\nPacket::Packet(int mag, int row) : _isHeader(false), _c"
  },
  {
    "path": "packet.h",
    "chars": 5701,
    "preview": "#ifndef _PACKET_H_\n#define _PACKET_H_\n\n#include <cstdint>\n#include <algorithm>\n#include <iostream>\n#include <vector>\n#in"
  },
  {
    "path": "packet830.cpp",
    "chars": 5840,
    "preview": "#include <packet830.h>\n\nusing namespace vbit;\n\nPacket830::Packet830(Configure *configure) :\n    _configure(configure)\n{\n"
  },
  {
    "path": "packet830.h",
    "chars": 930,
    "preview": "/** Packet source for 8/30/1 and 8/30/2\n */\n#ifndef _PACKET830_H_\n#define _PACKET830_H_\n\n#include \"packetsource.h\"\n#incl"
  },
  {
    "path": "packetDatacast.cpp",
    "chars": 2927,
    "preview": "/* Packet source for datacast channels */\n\n#include \"packetDatacast.h\"\n\nusing namespace vbit;\n\nPacketDatacast::PacketDat"
  },
  {
    "path": "packetDatacast.h",
    "chars": 909,
    "preview": "#ifndef PACKETDATACAST_H\n#define PACKETDATACAST_H\n\n#include \"packetsource.h\"\n#include \"configure.h\"\n\nnamespace vbit\n{\n  "
  },
  {
    "path": "packetDebug.cpp",
    "chars": 7137,
    "preview": "#include \"packetDebug.h\"\n\nusing namespace vbit;\n\nPacketDebug::PacketDebug(Configure* configure, Debug* debug) :\n    _con"
  },
  {
    "path": "packetDebug.h",
    "chars": 1693,
    "preview": "/** Packet source for debugging output\n */\n#ifndef _PACKETDEBUG_H_\n#define _PACKETDEBUG_H_\n\n#include <list>\n#include \"pa"
  },
  {
    "path": "packetServer.cpp",
    "chars": 10027,
    "preview": "/* Provide a TCP packet server which sends a whole frame of t42 data at a time to all connected clients */\n\n#include \"pa"
  },
  {
    "path": "packetServer.h",
    "chars": 1259,
    "preview": "#ifndef _PACKETSERVER_H_\n#define _PACKETSERVER_H_\n\n#include \"configure.h\"\n#include \"debug.h\"\n#include <mutex>\n#include <"
  },
  {
    "path": "packetmag.cpp",
    "chars": 22006,
    "preview": "/** Implements a packet source for magazines\n */\n\n#include \"packetmag.h\"\n\nusing namespace vbit;\n\nPacketMag::PacketMag(ui"
  },
  {
    "path": "packetmag.h",
    "chars": 3803,
    "preview": "#ifndef PACKETMAG_H\n#define PACKETMAG_H\n#include <list>\n#include <mutex>\n#include <memory>\n#include <packetsource.h>\n#in"
  },
  {
    "path": "packetsource.cpp",
    "chars": 531,
    "preview": "#include \"packetsource.h\"\n\nusing namespace vbit;\n\nPacketSource::PacketSource()\n{\n    //ctor\n    // This could be in the "
  },
  {
    "path": "packetsource.h",
    "chars": 2169,
    "preview": "/**\n * Anything that generates teletext packets is derived from this interface.\n * Functions defined by this interface a"
  },
  {
    "path": "page.cpp",
    "chars": 14439,
    "preview": " #include \"page.h\"\n\nusing namespace vbit;\n\nPage::Page() :\n    _carouselPage(nullptr)\n{\n    ClearPage(); // initialises v"
  },
  {
    "path": "page.h",
    "chars": 5624,
    "preview": "#ifndef PAGE_H\n#define PAGE_H\n#include <stdlib.h>\n#include \"string.h\"\n#include <iostream>\n#include <sstream>\n#include <m"
  },
  {
    "path": "pagelist.cpp",
    "chars": 7430,
    "preview": "/** PageList\n */\n#include \"pagelist.h\"\n#include \"packetmag.h\"\n\nusing namespace vbit;\n\nPageList::PageList(Configure *conf"
  },
  {
    "path": "pagelist.h",
    "chars": 1744,
    "preview": "#ifndef _PAGELIST_H_\n#define _PAGELIST_H_\n\n#include <iostream>\n#include <string>\n#include <dirent.h>\n#include <errno.h>\n"
  },
  {
    "path": "postupdate.sh",
    "chars": 4336,
    "preview": "#!/bin/bash\n\n# Perform any script actions which need to happen after switching to the latest\n# tagged release\n\nmain(){\n "
  },
  {
    "path": "scripts/config.py",
    "chars": 6890,
    "preview": "import os\nimport json\nimport subprocess\nimport shutil\n\n# absolute path of installed services\nSERVICESDIR = os.path.join("
  },
  {
    "path": "scripts/known_services.json",
    "chars": 2583,
    "preview": "{\n    \"services\":[\n        {\"name\":\"Artfax\",\"type\":\"git\",\"url\":\"https://github.com/teletexx/service-artfax\",\"path\":\"Artf"
  },
  {
    "path": "scripts/runvbit2.py",
    "chars": 2696,
    "preview": "#!/usr/bin/env python3\n# run vbit2 using configuration from config.json\n\nimport config\nimport os\nimport subprocess\nimpor"
  },
  {
    "path": "scripts/teletext-update.py",
    "chars": 1264,
    "preview": "#!/usr/bin/env python3\n# Update the active service and any ancillary sub-services\n\nimport config\nimport os\nimport subpro"
  },
  {
    "path": "scripts/vbit-config.py",
    "chars": 13100,
    "preview": "#!/usr/bin/env python3\n\nimport config\nimport json\nimport sys\nimport os\nimport shutil\nimport subprocess\nimport re\nfrom di"
  },
  {
    "path": "service.cpp",
    "chars": 19371,
    "preview": "/** Service\n */\n#include \"service.h\"\n\nusing namespace vbit;\n\nService::Service(Configure *configure, Debug *debug, PageLi"
  },
  {
    "path": "service.h",
    "chars": 3509,
    "preview": "#ifndef _SERVICE_H_\n#define _SERVICE_H_\n\n#include <iostream>\n#include <iomanip>\n#include <thread>\n#include <ctime>\n#incl"
  },
  {
    "path": "specialpages.cpp",
    "chars": 4138,
    "preview": "\n#include \"specialpages.h\"\n\nusing namespace vbit;\n\nSpecialPages::SpecialPages(int mag, PageList *pageList, Debug *debug)"
  },
  {
    "path": "specialpages.h",
    "chars": 838,
    "preview": "#ifndef _SPECIALPAGES_H\n#define _SPECIALPAGES_H\n\n#include <list>\n\n#include \"debug.h\"\n#include \"ttxpagestream.h\"\n#include"
  },
  {
    "path": "tables.cpp",
    "chars": 12153,
    "preview": "#include \"tables.h\"\n\n/*-------------------------------------------\n* Reverse bytes\n*/\nconst uint8_t ReverseByteTab[256] "
  },
  {
    "path": "tables.h",
    "chars": 443,
    "preview": "#ifndef _TABLES_H_\n#define _TABLES_H_\n#include <cstdint>\n\nextern const uint8_t ReverseByteTab[256];\n\nextern const uint8_"
  },
  {
    "path": "teletext-update.service",
    "chars": 139,
    "preview": "[Unit]\nDescription=Automatically check active teletext service for updates\n\n[Service]\nType=oneshot\nExecStart=%h/.local/b"
  },
  {
    "path": "teletext-update.timer",
    "chars": 141,
    "preview": "[Unit]\nDescription=Run update-teletext.service every 5 minutes\n\n[Timer]\nOnUnitActiveSec=5min\nOnBootSec=10s\n\n[Install]\nWa"
  },
  {
    "path": "ttxline.cpp",
    "chars": 4150,
    "preview": "#include \"ttxline.h\"\n\n// create a blank line\nTTXLine::TTXLine():\n    _nextLine(nullptr)\n{\n    _line.fill(0x20); // initi"
  },
  {
    "path": "ttxline.h",
    "chars": 1530,
    "preview": "#ifndef TTXLINE_H\n#define TTXLINE_H\n#include <cstdint>\n#include <iostream>\n#include <iomanip>\n#include <string>\n#include"
  },
  {
    "path": "ttxpagestream.cpp",
    "chars": 1676,
    "preview": "#include \"ttxpagestream.h\"\n\nusing namespace vbit;\n\nTTXPageStream::TTXPageStream() :\n    _transitionTime(0),\n    _loadedP"
  },
  {
    "path": "ttxpagestream.h",
    "chars": 3775,
    "preview": "#ifndef _TTXPAGESTREAM_H_\n#define _TTXPAGESTREAM_H_\n\n#include <mutex>\n#include <sys/stat.h>\n#include <memory>\n\n#include "
  },
  {
    "path": "update.sh",
    "chars": 434,
    "preview": "#!/bin/sh\nset -e\ncd `dirname \"$(readlink -f \"$0\")\"` # switch to vbit2 directory\necho Updating to latest stable version o"
  },
  {
    "path": "updatedpages.cpp",
    "chars": 1715,
    "preview": "\n#include \"updatedpages.h\"\n\nusing namespace vbit;\n\nUpdatedPages::UpdatedPages(int mag, PageList *pageList, Debug *debug)"
  },
  {
    "path": "updatedpages.h",
    "chars": 885,
    "preview": "#ifndef _UPDATEDPAGES_H\n#define _UPDATEDPAGES_H\n\n#include <list>\n\n#include \"debug.h\"\n#include \"ttxpagestream.h\"\n#include"
  },
  {
    "path": "vbit2.cpp",
    "chars": 1844,
    "preview": "#include \"vbit2.h\"\n\nusing namespace vbit;\n\nMasterClock *MasterClock::instance = 0; // initialise MasterClock singleton\n\n"
  },
  {
    "path": "vbit2.h",
    "chars": 357,
    "preview": "#ifndef _VBIT2_H_\n#define _VBIT2_H_\n\n#include <iostream>\n#include <thread>\n#include <clocale>\n#include \"service.h\"\n#incl"
  },
  {
    "path": "vbit2.service",
    "chars": 131,
    "preview": "[Unit]\nDescription=VBIT2\n\n[Service]\nType=simple\nExecStart=%h/.local/bin/runvbit2\nRestart=always\n\n[Install]\nWantedBy=defa"
  },
  {
    "path": "version.h",
    "chars": 79,
    "preview": "#ifndef _VERSION_H_\n#define _VERSION_H_\n\n#define VBIT2_VERSION \"2.8.2\"\n\n#endif\n"
  }
]

About this extraction

This page contains the full source code of the peterkvt80/vbit2 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 65 files (336.2 KB), approximately 81.5k tokens, and a symbol index with 64 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!