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 */
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
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.