Initial import: clean snapshot from /home/olafn/infoscreen-dev (2025-10-25)

This commit is contained in:
RobbStarkAustria
2025-10-25 17:42:27 +02:00
commit 8ca9f69f6f
111 changed files with 8612 additions and 0 deletions

Submodule .git.legacy_backup.1761406947 added at 2a4701fc5d

View File

@@ -0,0 +1 @@
Initial import: clean snapshot from /home/olafn/infoscreen-dev (2025-10-25)

1
.git.legacy_backup/HEAD Normal file
View File

@@ -0,0 +1 @@
ref: refs/heads/master

View File

@@ -0,0 +1,5 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true

View File

@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -0,0 +1,15 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}

View File

@@ -0,0 +1,174 @@
#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open2;
# An example hook script to integrate Watchman
# (https://facebook.github.io/watchman/) with git to speed up detecting
# new and modified files.
#
# The hook is passed a version (currently 2) and last update token
# formatted as a string and outputs to stdout a new update token and
# all files that have been modified since the update token. Paths must
# be relative to the root of the working tree and separated by a single NUL.
#
# To enable this hook, rename this file to "query-watchman" and set
# 'git config core.fsmonitor .git/hooks/query-watchman'
#
my ($version, $last_update_token) = @ARGV;
# Uncomment for debugging
# print STDERR "$0 $version $last_update_token\n";
# Check the hook interface version
if ($version ne 2) {
die "Unsupported query-fsmonitor hook version '$version'.\n" .
"Falling back to scanning...\n";
}
my $git_work_tree = get_working_dir();
my $retry = 1;
my $json_pkg;
eval {
require JSON::XS;
$json_pkg = "JSON::XS";
1;
} or do {
require JSON::PP;
$json_pkg = "JSON::PP";
};
launch_watchman();
sub launch_watchman {
my $o = watchman_query();
if (is_work_tree_watched($o)) {
output_result($o->{clock}, @{$o->{files}});
}
}
sub output_result {
my ($clockid, @files) = @_;
# Uncomment for debugging watchman output
# open (my $fh, ">", ".git/watchman-output.out");
# binmode $fh, ":utf8";
# print $fh "$clockid\n@files\n";
# close $fh;
binmode STDOUT, ":utf8";
print $clockid;
print "\0";
local $, = "\0";
print @files;
}
sub watchman_clock {
my $response = qx/watchman clock "$git_work_tree"/;
die "Failed to get clock id on '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
return $json_pkg->new->utf8->decode($response);
}
sub watchman_query {
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
or die "open2() failed: $!\n" .
"Falling back to scanning...\n";
# In the query expression below we're asking for names of files that
# changed since $last_update_token but not from the .git folder.
#
# To accomplish this, we're using the "since" generator to use the
# recency index to select candidate nodes and "fields" to limit the
# output to file names only. Then we're using the "expression" term to
# further constrain the results.
my $last_update_line = "";
if (substr($last_update_token, 0, 1) eq "c") {
$last_update_token = "\"$last_update_token\"";
$last_update_line = qq[\n"since": $last_update_token,];
}
my $query = <<" END";
["query", "$git_work_tree", {$last_update_line
"fields": ["name"],
"expression": ["not", ["dirname", ".git"]]
}]
END
# Uncomment for debugging the watchman query
# open (my $fh, ">", ".git/watchman-query.json");
# print $fh $query;
# close $fh;
print CHLD_IN $query;
close CHLD_IN;
my $response = do {local $/; <CHLD_OUT>};
# Uncomment for debugging the watch response
# open ($fh, ">", ".git/watchman-response.json");
# print $fh $response;
# close $fh;
die "Watchman: command returned no output.\n" .
"Falling back to scanning...\n" if $response eq "";
die "Watchman: command returned invalid output: $response\n" .
"Falling back to scanning...\n" unless $response =~ /^\{/;
return $json_pkg->new->utf8->decode($response);
}
sub is_work_tree_watched {
my ($output) = @_;
my $error = $output->{error};
if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
$retry--;
my $response = qx/watchman watch "$git_work_tree"/;
die "Failed to make watchman watch '$git_work_tree'.\n" .
"Falling back to scanning...\n" if $? != 0;
$output = $json_pkg->new->utf8->decode($response);
$error = $output->{error};
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
# Uncomment for debugging watchman output
# open (my $fh, ">", ".git/watchman-output.out");
# close $fh;
# Watchman will always return all files on the first query so
# return the fast "everything is dirty" flag to git and do the
# Watchman query just to get it over with now so we won't pay
# the cost in git to look up each individual file.
my $o = watchman_clock();
$error = $output->{error};
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
output_result($o->{clock}, ("/"));
$last_update_token = $o->{clock};
eval { launch_watchman() };
return 0;
}
die "Watchman: $error.\n" .
"Falling back to scanning...\n" if $error;
return 1;
}
sub get_working_dir {
my $working_dir;
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
$working_dir = Win32::GetCwd();
$working_dir =~ tr/\\/\//;
} else {
require Cwd;
$working_dir = Cwd::cwd();
}
return $working_dir;
}

View File

@@ -0,0 +1,8 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info

View File

@@ -0,0 +1,14 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:

View File

@@ -0,0 +1,49 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

View File

@@ -0,0 +1,13 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git merge" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message to
# stderr if it wants to stop the merge commit.
#
# To enable this hook, rename this file to "pre-merge-commit".
. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit"
:

View File

@@ -0,0 +1,53 @@
#!/bin/sh
# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local oid> <remote ref> <remote oid>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
remote="$1"
url="$2"
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
while read local_ref local_oid remote_ref remote_oid
do
if test "$local_oid" = "$zero"
then
# Handle delete
:
else
if test "$remote_oid" = "$zero"
then
# New branch, examine all commits
range="$local_oid"
else
# Update to existing branch, examine new commits
range="$remote_oid..$local_oid"
fi
# Check for WIP commit
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
if test -n "$commit"
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
fi
fi
done
exit 0

View File

@@ -0,0 +1,169 @@
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
<<\DOC_END
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
DOC_END

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".
if test -n "$GIT_PUSH_OPTION_COUNT"
then
i=0
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
esac
i=$((i + 1))
done
fi

View File

@@ -0,0 +1,42 @@
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source. The hook's purpose is to edit the commit
# message file. If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".
# This hook includes three examples. The first one removes the
# "# Please enter the commit message..." help message.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output. It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited. This is rarely a good idea.
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
# case "$COMMIT_SOURCE,$SHA1" in
# ,|template,)
# /usr/bin/perl -i.bak -pe '
# print "\n" . `git diff --cached --name-status -r`
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
# *) ;;
# esac
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
# if test -z "$COMMIT_SOURCE"
# then
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
# fi

View File

@@ -0,0 +1,78 @@
#!/bin/sh
# An example hook script to update a checked-out tree on a git push.
#
# This hook is invoked by git-receive-pack(1) when it reacts to git
# push and updates reference(s) in its repository, and when the push
# tries to update the branch that is currently checked out and the
# receive.denyCurrentBranch configuration variable is set to
# updateInstead.
#
# By default, such a push is refused if the working tree and the index
# of the remote repository has any difference from the currently
# checked out commit; when both the working tree and the index match
# the current commit, they are updated to match the newly pushed tip
# of the branch. This hook is to be used to override the default
# behaviour; however the code below reimplements the default behaviour
# as a starting point for convenient modification.
#
# The hook receives the commit with which the tip of the current
# branch is going to be updated:
commit=$1
# It can exit with a non-zero status to refuse the push (when it does
# so, it must not modify the index or the working tree).
die () {
echo >&2 "$*"
exit 1
}
# Or it can make any necessary changes to the working tree and to the
# index to bring them to the desired state when the tip of the current
# branch is updated to the new commit, and exit with a zero status.
#
# For example, the hook can simply run git read-tree -u -m HEAD "$1"
# in order to emulate git fetch that is run in the reverse direction
# with git push, as the two-tree form of git read-tree -u -m is
# essentially the same as git switch or git checkout that switches
# branches while keeping the local changes in the working tree that do
# not interfere with the difference between the branches.
# The below is a more-or-less exact translation to shell of the C code
# for the default behaviour for git's push-to-checkout hook defined in
# the push_to_deploy() function in builtin/receive-pack.c.
#
# Note that the hook will be executed from the repository directory,
# not from the working tree, so if you want to perform operations on
# the working tree, you will have to adapt your code accordingly, e.g.
# by adding "cd .." or using relative paths.
if ! git update-index -q --ignore-submodules --refresh
then
die "Up-to-date check failed"
fi
if ! git diff-files --quiet --ignore-submodules --
then
die "Working directory has unstaged changes"
fi
# This is a rough translation of:
#
# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
if git cat-file -e HEAD 2>/dev/null
then
head=HEAD
else
head=$(git hash-object -t tree --stdin </dev/null)
fi
if ! git diff-index --quiet --cached --ignore-submodules $head --
then
die "Working directory has staged changes"
fi
if ! git read-tree -u -m "$commit"
then
die "Could not update working tree to new HEAD"
fi

View File

@@ -0,0 +1,128 @@
#!/bin/sh
#
# An example hook script to block unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
# hooks.allowdeletetag
# This boolean sets whether deleting tags will be allowed in the
# repository. By default they won't be.
# hooks.allowmodifytag
# This boolean sets whether a tag may be modified after creation. By default
# it won't be.
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
# hooks.denycreatebranch
# This boolean sets whether remotely creating branches will be denied
# in the repository. By default this is allowed.
#
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
# --- Config
allowunannotated=$(git config --type=bool hooks.allowunannotated)
allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
echo "*** Project description file hasn't been set" >&2
exit 1
;;
esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git cat-file -t $newrev)
fi
case "$refname","$newrev_type" in
refs/tags/*,commit)
# un-annotated tag
short_refname=${refname##refs/tags/}
if [ "$allowunannotated" != "true" ]; then
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
exit 1
fi
;;
refs/tags/*,delete)
# delete tag
if [ "$allowdeletetag" != "true" ]; then
echo "*** Deleting a tag is not allowed in this repository" >&2
exit 1
fi
;;
refs/tags/*,tag)
# annotated tag
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
then
echo "*** Tag '$refname' already exists." >&2
echo "*** Modifying a tag is not allowed in this repository." >&2
exit 1
fi
;;
refs/heads/*,commit)
# branch
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
echo "*** Creating a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/heads/*,delete)
# delete branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/remotes/*,commit)
# tracking branch
;;
refs/remotes/*,delete)
# delete tracking branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
exit 1
fi
;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
exit 1
;;
esac
# --- Finished
exit 0

BIN
.git.legacy_backup/index Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

View File

@@ -0,0 +1,2 @@
x­“ßjÛ0Åwí§8SkX·ÅNF¯2:m …m-u.VƲ-Ç"²$%YÆnvÑ»^m·e¯Ö'è#L¶óÇË 0cƒAßwÎù~b¡bôº‡‡<E280A1>ö‡1—aLMîEǃÁ»hx6Ÿœ^‘ý')× d¿K “&WÖ¯˜ºutfnùÏVây{H3× '§Ñù›×—à†YdJc'„ά«òx†è|vJëz<7A>/as&=¸‡}š)m7ZGý®—ñÒcÄŒ½OŽ%¹)W¹œÜS€%·ùVnëYÆpá”mMZ§5v@¨exæ_ú…ŸŽý¡ÿÖ<C3BF>‚™œ¼x¦lʹÍàU<C3A0>ÛWˆ¶ä6C§0ó$aÆd®gEêA«ú»ï_
†.X

View File

@@ -0,0 +1,3 @@
x]P_K1 χω>EΘ“Β86† "<>>ϋ(γθzΉ­®m<C2AE>¦' Ωw7mβϊhςϋ—μ=οaϋτxχΣ`0ΡM$yψ¦$<24>#>ΓvUΡ<04>ΎΗ‰Ε&ΆoKζ›Ψ{¬<>?
nϊuλ<EFBFBD>¤`7η&……LvΦx)fs <20><13>Λ F'³7g<37>ϋΝZ`¤Ι,>?τMp¦<14>”hΆq>Q2'εγ
πκΈ«y<C2AB>¬{άβ_ΤyXΧ+ΚrΜσΠΨUTχ(η(OOν<CEBD>Ϋ-»ΎΪμΏP―έ£B<C2A3>“ΛύF¶K(~nτ„ΥγΆuΧ]Ί_)2yθ

View File

@@ -0,0 +1,3 @@
x…SÍjã0î¹O1·HÔMÜCCÙl
](´‡@ ½ÅžlL)Hã4!øÝ;l'YØ]c°¤™ïgFã•¶+x<1A>oĺ6<18>`QmwAÕd}á¬Ö? }¡´_ì<ä>Ú <C39A>•*><3E>,¿» îPÓ<>;¶½rPÖN~˜Â8çg4ƹçpJð„»™çðc>éÉ’Ò‹(Ï™¢Íp«¢´E½ECÃnñª1î“Ã7¬~o(c£mÚÊÇáeLÂ=|U†+ Kˆ¤[­Å¥ìÏ)äK¢Ú™³±à6˜í-=diílmJÑU;JUIy<49>œz!Óüug´<67>`C­×å²2ëpˆfy¾ §Ø1€B£rï†Ðí•þ*éA׈¿<CB86>³Sk<53>¨óo7 ðH½‰ëÁ
·ÝÂÓ}ü:Š<ƒÐ¦ù¡íÄíÝ]ç<>ëñçilzœÏ@óчe¢¼§H$ç&$ïMù6³°Æ[<5B><k+ï}{/Ÿí(GX"¤)<1B>òÔAÑ9ëÄà¥ÿS ž 2@9ià¶ß<E28098>8

View File

@@ -0,0 +1,2 @@
x<01><>AOÃ0 …9÷W˜°Ã&Ôi‡I<><E284A2>Z)½OMæ­8$Î~? „8r³Ÿýž?+K
ÖõÕÍu§ŒïÔ”æ&QŽA,ýäjy'V<><EFBFBD>DMƒz&#&6þ/¯ãš¼G͆<0Á¢jû‡a÷ü8Ü_š~7ŒâËÙgeMš«™K8Li:¡”R4ŽÒ{6Ì´YA;—ã¿Â´á[ùL„A¤¤#¢ïj^Yr žÐZc$½<>žÑRpèr?o¼eU¼¦¼_hèÄŒ†eˆ íí¶ÎRÑW(Ó¿)ÐÝy†

View File

@@ -0,0 +1,2 @@
x%ŽAŠÃ0 Eg­Sº+Ô¦…Þ ˜ÇVS×2ÒÛWI7ÞXit·ûõï䔞ȮÌ<C2AE>ÝTª<54>Øu¦¼&-Ôܸ–šü\ôÀ¯
gÿÊà±mgðÀgÜ5<>¥!°ty?§Jo/ ôrÙWºö=ƒ½Ç

View File

@@ -0,0 +1 @@
xĄŹANĂ0EY糡<>±;v,Ů śÔnFŤ=•3‰Ó“ž<E2809C>í[ü÷ß,9łuĂťÖ

View File

@@ -0,0 +1 @@
da43f2b29ecf071e9b4ffc2fa03482e25d7cf0c6

393
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,393 @@
# Copilot Instructions - Infoscreen Client
## Project Overview
This is an **Infoscreen Client** system for Raspberry Pi that creates digital signage displays. The client communicates with a server via MQTT to display presentations, videos, and web content in kiosk mode. It's designed for educational/research environments where multiple displays need to be centrally managed.
## Architecture & Technology Stack
### Core Technologies
- **Python 3.x** - Main application language
- **MQTT (paho-mqtt)** - Real-time messaging with server
- **Impressive** - PDF presenter with native auto-advance and loop support
- **LibreOffice** - PPTX to PDF conversion (headless mode)
- **Environment Variables** - Configuration management via `.env` files
- **JSON** - Data exchange format for events and configuration
- **Base64** - Screenshot transmission encoding
- **Threading** - Background services (screenshot monitoring)
### System Components
- **Main Client** (`simclient.py`) - Core MQTT client and event processor
- **Display Manager** (`display_manager.py`) - Controls display applications (presentations, videos, web)
- **Discovery System** - Automatic client registration with server
- **Heartbeat Monitoring** - Regular status updates and keepalive
- **Event Processing** - Handles presentation/content switching commands
- **Screenshot Service** - Dashboard monitoring via image capture
- **File Management** - Downloads and manages presentation files
- **Group Management** - Supports organizing clients into groups
## Key Features & Functionality
### MQTT Communication Patterns
- **Discovery**: `infoscreen/discovery``infoscreen/{client_id}/discovery_ack`
- **Heartbeat**: Regular `infoscreen/{client_id}/heartbeat` messages
- **Dashboard**: Screenshot transmission via `infoscreen/{client_id}/dashboard`
- **Group Assignment**: Server sends group via `infoscreen/{client_id}/group_id`
- **Events**: Content commands via `infoscreen/events/{group_id}`
### Event Types Supported
```json
{
"presentation": {
"files": [{"url": "https://server/file.pptx", "filename": "file.pptx"}],
"auto_advance": true,
"slide_interval": 10,
"loop": true
},
"web": {
"url": "https://example.com"
},
"video": {
"url": "https://server/video.mp4",
"loop": false,
"autoplay": true,
"volume": 0.8
}
}
```
### Presentation System (Impressive-Based)
- **PDF files** are displayed natively with Impressive PDF presenter (no conversion needed)
- **PPTX files** are automatically converted to PDF using LibreOffice headless
- **Auto-advance**: Native Impressive `--auto` parameter (no xdotool needed)
- **Loop mode**: Impressive `--wrap` parameter for infinite looping
- **Auto-quit**: Impressive `--autoquit` parameter to exit after last slide
- **Virtual Environment**: Uses venv with pygame + pillow for reliable operation
- **Reliable**: Works consistently on Raspberry Pi without window focus issues
### Client Identification
- **Hardware Token**: SHA256 hash of serial number + MAC addresses
- **Persistent UUID**: Stored in `config/client_uuid.txt`
- **Group Membership**: Persistent group assignment in `config/last_group_id.txt`
## Directory Structure
```
~/infoscreen-dev/
├── .env # Environment configuration
├── README.md # Complete project documentation
├── IMPRESSIVE_INTEGRATION.md # Presentation system details
├── QUICK_REFERENCE.md # Quick command reference
├── .github/ # GitHub configuration
│ └── copilot-instructions.md
├── src/ # Source code
│ ├── simclient.py # MQTT client (event management)
│ ├── display_manager.py # Display controller (Impressive integration)
│ ├── current_event.json # Current active event (runtime)
│ ├── config/ # Persistent client data
│ │ ├── client_uuid.txt
│ │ └── last_group_id.txt
│ ├── presentation/ # Downloaded presentation files & PDFs
│ └── screenshots/ # Screenshot captures for monitoring
├── scripts/ # Production & testing utilities
│ ├── start-dev.sh # Start development client
│ ├── start-display-manager.sh # Start Display Manager
│ ├── test-display-manager.sh # Interactive testing menu
│ ├── test-impressive.sh # Test Impressive (auto-quit)
│ ├── test-impressive-loop.sh # Test Impressive (loop mode)
│ ├── test-mqtt.sh # MQTT connectivity test
│ ├── test-screenshot.sh # Screenshot capture test
│ └── present-pdf-auto-advance.sh # PDF presentation wrapper
├── logs/ # Application logs
│ ├── simclient.log
│ └── display_manager.log
└── venv/ # Python virtual environment
```
## Configuration & Environment Variables
### Development vs Production
- **Development**: `ENV=development`, verbose logging, frequent heartbeats
- **Production**: `ENV=production`, minimal logging, longer intervals
### Key Environment Variables
```bash
# Environment
ENV=development|production
DEBUG_MODE=1|0
LOG_LEVEL=DEBUG|INFO|WARNING|ERROR
# MQTT Configuration
MQTT_BROKER=192.168.1.100 # Primary MQTT broker
MQTT_PORT=1883 # MQTT port
MQTT_BROKER_FALLBACKS=host1,host2 # Fallback brokers
# Timing (seconds)
HEARTBEAT_INTERVAL=10 # Status update frequency
SCREENSHOT_INTERVAL=30 # Dashboard screenshot frequency
# File/API Server (used to download presentation files)
# Defaults to the same host as MQTT_BROKER, port 8000, scheme http.
# If incoming event URLs use host 'server' (or are host-less), simclient rewrites them to this server.
FILE_SERVER_HOST= # optional; if empty, defaults to MQTT_BROKER
FILE_SERVER_PORT=8000 # default API port
FILE_SERVER_SCHEME=http # http or https
# FILE_SERVER_BASE_URL= # optional full override, e.g., http://192.168.1.100:8000
```
### File Server URL Resolution
- The MQTT client (`simclient.py`) downloads presentation files listed in events.
- To avoid DNS issues when event URLs use `http://server:8000/...`, the client normalizes such URLs to the configured file server.
- By default, the file server host is the same as `MQTT_BROKER`, with port `8000` and scheme `http`.
- You can override behavior using `.env` variables above; `FILE_SERVER_BASE_URL` takes precedence over individual host/port/scheme.
- Inline comments in `.env` are supported; keep comments after a space and `#` so values stay clean.
## Development Patterns & Best Practices
### Error Handling
- Robust MQTT connection with fallbacks and retries
- Graceful degradation when services unavailable
- Comprehensive logging with rotating file handlers
- Exception handling for all external operations
### State Management
- Event state persisted in `current_event.json`
- Client configuration persisted across restarts
- Group membership maintained with server synchronization
- Clean state transitions (delete old events on group changes)
### Threading Architecture
- Main thread: MQTT communication and heartbeat
- Background thread: Screenshot monitoring service
- Thread-safe operations for shared resources
### File Operations
- Automatic directory creation for all output paths
- Safe file operations with proper exception handling
- Atomic writes for configuration files
- Automatic cleanup of temporary/outdated files
## Development Workflow
### Local Development Setup
1. Clone repository to `~/infoscreen-dev`
2. Create virtual environment: `python3 -m venv venv`
3. Install dependencies: `pip install -r src/requirements.txt` (includes pygame + pillow for PDF slideshows)
4. Configure `.env` file with MQTT broker settings
5. Use `./scripts/start-dev.sh` for MQTT client or `./scripts/start-display-manager.sh` for display manager
6. **Important**: Virtual environment must include pygame and pillow for PDF auto-advance to work
### Testing Components
- `./scripts/test-mqtt.sh` - MQTT connectivity
- `./scripts/test-screenshot.sh` - Screenshot capture
- `./scripts/test-display-manager.sh` - Interactive testing menu
- `./scripts/test-impressive.sh` - Test auto-quit presentation mode
- `./scripts/test-impressive-loop.sh` - Test loop presentation mode
- `./scripts/test-utc-timestamps.sh` - Event timing validation
- Manual event testing via mosquitto_pub or test-display-manager.sh
### Production Deployment
- Docker containerization available (`docker-compose.production.yml`)
- Systemd service integration for auto-start
- Resource limits and health checks configured
- Persistent volume mounts for data
## Code Style & Conventions
### Python Code Standards
- Environment variable parsing with fallback defaults
- Comprehensive docstrings for complex functions
- Logging at appropriate levels (DEBUG/INFO/WARNING/ERROR)
- Type hints where beneficial for clarity
- Exception handling with specific error types
### Configuration Management
- Environment-first configuration (12-factor app principles)
- Support for inline comments in environment values
- Graceful handling of missing/invalid configuration
- Multiple environment file locations for flexibility
### MQTT Message Handling
- JSON message format validation
- Robust payload parsing with fallbacks
- Topic-specific message handlers
- Retained message support where appropriate
## Hardware Considerations
### Target Platform
- **Primary**: Raspberry Pi 4/5 with desktop environment
- **Storage**: SSD recommended for performance
- **Display**: HDMI output for presentation display
- **Network**: WiFi or Ethernet connectivity required
### System Dependencies
- Python 3.x runtime
- Network connectivity for MQTT
- Display server (X11) for screenshot capture
- **Impressive** - PDF presenter with auto-advance (primary presentation tool)
- **pygame** - Required for Impressive (installed in venv)
- **Pillow/PIL** - Required for Impressive PDF rendering (installed in venv)
- **LibreOffice** - PPTX to PDF conversion (headless mode)
- Chromium/Chrome - Web content display (kiosk mode)
- VLC or MPV - Video playbook
### Video playback details (python-vlc)
- The Display Manager now prefers using python-vlc (libvlc) when available for video playback. This enables programmatic control (autoplay, loop, volume) and cleaner termination/cleanup. If python-vlc is not available, the external `vlc` binary is used as a fallback.
- Supported video event fields: `url`, `autoplay` (boolean), `loop` (boolean), `volume` (float 0.0-1.0). The manager converts `volume` to VLC's 0-100 scale.
- URLs using the placeholder host `server` (for example `http://server:8000/...`) are rewritten to the configured file server before playback. The resolution priority is: `FILE_SERVER_BASE_URL` > `FILE_SERVER_HOST` (or `MQTT_BROKER`) + `FILE_SERVER_PORT` + `FILE_SERVER_SCHEME`.
- Hardware-accelerated decoding errors (e.g., `h264_v4l2m2m`) may appear when the platform does not expose a V4L2 M2M device. To avoid these errors the Display Manager can be configured to disable hw-decoding (see README env var `VLC_HW_ACCEL`). By default the manager will attempt hw-acceleration when libvlc supports it.
- Fullscreen / kiosk: the manager will attempt to make libVLC windows fullscreen (remove decorations) when using python-vlc, and the README contains recommended system-level kiosk/session setup for a truly panel-free fullscreen experience.
## Security & Privacy
### Data Protection
- Hardware identification via cryptographic hash
- No sensitive data in plain text logs
- Local storage of minimal required data only
- Secure MQTT communication (configurable)
### Network Security
- Configurable MQTT authentication (if broker requires)
- Firewall-friendly design (outbound connections only)
- Multiple broker fallback for reliability
## Presentation System Architecture
### How It Works
1. **PDF File Received** → Event contains PDF file reference → Use directly (no conversion)
2. **PPTX File Received** → Event contains PPTX file reference
3. **Convert to PDF** → LibreOffice headless: `libreoffice --headless --convert-to pdf`
4. **Cache PDF** → Converted PDF stored in `presentation/` directory
5. **Display with Impressive** → Launch with venv environment and parameters:
- `--fullscreen` - Full screen mode
- `--nooverview` - No slide overview
- `--auto N` - Auto-advance every N seconds
- `--wrap` - Loop infinitely (if `loop: true`)
- `--autoquit` - Exit after last slide (if `loop: false`)
### Key Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `auto_advance` | boolean | `false` | Enable automatic slide advancement |
| `slide_interval` | integer | `10` | Seconds between slides |
| `loop` | boolean | `false` | Loop presentation vs. quit after last slide |
### Why Impressive?
-**Native auto-advance** - No xdotool or window management hacks
-**Built-in loop support** - Reliable `--wrap` parameter
-**Works on Raspberry Pi** - No focus/window issues
-**Simple integration** - Clean command-line interface
-**Maintainable** - ~50 lines of code vs. 200+ with xdotool approaches
### Implementation Location
- **File**: `src/display_manager.py`
- **Method**: `start_presentation()`
- **Key Logic**:
1. Check if PDF file (use directly) or PPTX (needs conversion)
2. Convert PPTX to PDF if needed (cached for reuse)
3. Set up virtual environment for Impressive (pygame + pillow)
4. Build Impressive command with appropriate parameters
5. Launch process and monitor
## Common Development Tasks
When working on this codebase:
1. **Adding new event types**: Extend the event processing logic in `display_manager.py``start_display_for_event()`
2. **Modifying presentation behavior**: Update `display_manager.py``start_presentation()`
3. **Configuration changes**: Update environment variable parsing and validation
4. **MQTT topics**: Follow the established `infoscreen/` namespace pattern
5. **Error handling**: Always include comprehensive logging and graceful fallbacks
6. **State persistence**: Use the established `config/` directory pattern
7. **Testing**: Use `./scripts/test-display-manager.sh` for interactive testing
8. **Presentation testing**: Use `./scripts/test-impressive*.sh` scripts
9. **File download host resolution**: If the API server differs from the MQTT broker or uses HTTPS, set `FILE_SERVER_*` in `.env` or adjust `resolve_file_url()` in `src/simclient.py`.
## Troubleshooting Guidelines
### Common Issues
- **MQTT Connection**: Check broker reachability, try fallback brokers
- **Screenshots**: Verify display environment and permissions
- **File Downloads**: Check network connectivity and disk space
- If event URLs use host `server` and DNS fails, the client rewrites to `MQTT_BROKER` by default.
- Ensure `MQTT_BROKER` points to the correct server IP; if the API differs, set `FILE_SERVER_HOST` or `FILE_SERVER_BASE_URL`.
- Match scheme/port via `FILE_SERVER_SCHEME`/`FILE_SERVER_PORT` for HTTPS or non-default ports.
- **Group Changes**: Monitor log for group assignment messages
- **Service Startup**: Check systemd logs and environment configuration
### Debugging Tools
- Log files in `logs/simclient.log` and `logs/display_manager.log` with rotation
- MQTT message monitoring with mosquitto_sub
- Interactive testing menu: `./scripts/test-display-manager.sh`
- Component test scripts: `test-impressive*.sh`, `test-mqtt.sh`, etc.
- Process monitoring: Check for `impressive`, `libreoffice`, `chromium`, `vlc` processes
### File download URL troubleshooting
- Symptoms:
- `Failed to resolve 'server'` or `NameResolutionError` when downloading files
- `Invalid URL 'http # http or https://...'` in `simclient.log`
- What to check:
- Look for lines like `Lade Datei herunter von:` in `logs/simclient.log` to see the effective URL used
- Ensure the URL host is the MQTT broker IP (or your configured file server), not `server`
- Verify `.env` values dont include inline comments as part of the value (e.g., keep `FILE_SERVER_SCHEME=http` on its own line)
- Fixes:
- If your API is on the same host as the broker: leave `FILE_SERVER_HOST` empty (defaults to `MQTT_BROKER`), keep `FILE_SERVER_PORT=8000`, and set `FILE_SERVER_SCHEME=http` or `https`
- To override fully, set `FILE_SERVER_BASE_URL` (e.g., `http://192.168.1.100:8000`); this takes precedence
- After changing `.env`, restart the simclient process
- Expected healthy log sequence:
- `Lade Datei herunter von: http://<broker-ip>:8000/...`
- Followed by `"GET /... HTTP/1.1" 200` and `Datei erfolgreich heruntergeladen:`
## Important Notes for AI Assistants
### Virtual Environment Requirements (Critical)
- **pygame and pillow MUST be installed in venv** - Required for Impressive to work
- **Display manager uses venv context** - Ensures Impressive has access to dependencies
- **Installation command**: `pip install pygame pillow` (already in requirements.txt)
- **If pygame missing**: Impressive will fail with "No module named 'pygame'" error
### Presentation System
- **ALWAYS use Impressive** for PDF presentations (primary solution)
- **DO NOT suggest xdotool approaches** - they failed on Raspberry Pi due to window focus issues
- **DO NOT suggest video conversion** - adds complexity, had black screen issues
- **PDF files are supported natively** - no conversion needed
- **PPTX conversion is automatic** - LibreOffice headless handles PPTX→PDF
- **Virtual environment is required** - pygame + pillow must be available for Impressive
- **Loop mode uses `--wrap`** - not custom scripts or workarounds
- **Auto-quit uses `--autoquit`** - native Impressive parameter
### Testing Approach
- Use `./scripts/test-display-manager.sh` for interactive testing
- Use `./scripts/test-impressive-loop.sh` to verify loop functionality
- Test individual components with specific test scripts
- Always check logs in `logs/` directory for debugging
### Code Changes
- Display logic is in `src/display_manager.py`, not `simclient.py`
- MQTT client (`simclient.py`) writes events to `current_event.json`
- Display Manager reads `current_event.json` and launches appropriate applications
- Two separate processes: simclient.py (MQTT) + display_manager.py (display control)
### Documentation
- **README.md** - Start here for comprehensive overview
- **IMPRESSIVE_INTEGRATION.md** - Deep dive into presentation system
- **QUICK_REFERENCE.md** - Quick commands and examples
- Source code has extensive comments and logging
This system is designed for reliability and ease of maintenance in educational environments with multiple deployed clients. The Impressive-based presentation solution provides native auto-advance and loop support without complex window management hacks.
## Recent changes (Oct 2025)
Summary of additions since the last Copilot instructions update:
- Added `event_type` handling for scheduler events: `presentation`, `webuntis`, `webpage`, `website`. The display manager now uses `event_type` when available to pick the correct display path.
- Implemented auto-scroll for long websites. Two strategies are available:
- CDP injection via DevTools websocket using `websocket-client` and `requests`. The injector attempts to evaluate an auto-scroll script inside the page context. Default duration: 60s.
- Local Chrome extension fallback (`src/chrome_autoscroll`) loaded with `--load-extension` when CDP handshake is blocked (403), ensuring reliable autoscroll operation.
- New helper scripts for debugging DevTools handshake: `scripts/test_cdp.py` and `scripts/test_cdp_origins.py`.
- Updated `requirements.txt` to include `websocket-client`.
Notes for Copilot:
- When adding or modifying display logic, prefer Impressive for PDF presentations and avoid xdotool approaches for slideshow control.
- For web events, ensure autoscroll is only activated for `event_type: "website"` and keep the CDP injection optional/fallback-only when feasible.

64
.gitignore vendored Normal file
View File

@@ -0,0 +1,64 @@
# .gitignore for infoscreen-dev workspace
# Ignore common python and workspace artifacts while keeping source files tracked
# Byte-compiled / cache
__pycache__/
*.py[cod]
*$py.class
# Virtual environments
venv/
.venv/
env/
ENV/
# dotenv local overrides (leave .env if intentionally committed)
.env.local
.env.*.local
.env
# Logs and runtime files
logs/
*.log
simclient.log
simclient.log.*
# Screenshots and large runtime artifacts
screenshots/
presentation/*.cache
# Backup of nested src git metadata
src/.git.backup/
# Editor/IDE
.vscode/
.idea/
*.sublime-workspace
*.sublime-project
*.swp
# OS files
.DS_Store
Thumbs.db
# Test / coverage
.pytest_cache/
.coverage
htmlcov/
# Build / packaging
build/
dist/
*.egg-info/
# Archives
*.tar.gz
*.zip
# Database files
*.sqlite3
*.db
# Misc
*.bak
*.old

195
CLEANUP_SUMMARY.md Normal file
View File

@@ -0,0 +1,195 @@
# Workspace Cleanup Summary
## 🧹 Latest Cleanup Completed - October 2, 2025
This workspace has been cleaned after implementing PDF slideshow support with auto-advance functionality. All debug and temporary files from the optimization process have been removed.
## ✅ PDF Slideshow Implementation Cleanup
### Latest Changes (October 2, 2025):
#### Files Removed:
- `scripts/test-pdf-auto-advance.sh` - Comprehensive PDF auto-advance test
- `scripts/test-pdf-loop-modes.sh` - Loop vs auto-quit behavior test
- `scripts/test-pdf-slideshow.sh` - Basic PDF slideshow test
- `scripts/verify-pdf-slideshow.sh` - Final verification script
- `src/test_pdf_event.json` - Temporary event file for testing
- `src/current_event.json.backup` - Backup created during testing
- All `__pycache__/` directories and `*.pyc` files
#### Files Updated:
- `src/requirements.txt` - Added pygame>=2.0.0 and pillow>=8.0.0
- Log files cleaned (removed debug traces)
## ✅ Final Complete Solution Overview
**Presentation System:**
- **PDF files:** Native support with auto-advance
- **PPTX files:** PPTX → PDF (LibreOffice) → Impressive (with auto-advance & loop)
- **Virtual Environment:** pygame + pillow installed for impressive support
**Why This Works:**
- ✅ Native auto-advance (no xdotool hacks)
- ✅ Reliable loop support (`--wrap` parameter)
- ✅ Clean auto-quit (`--autoquit` parameter)
- ✅ Works consistently on Raspberry Pi
- ✅ Simple, maintainable code
## 📁 Files Retained
### Core Application
- `src/simclient.py` - MQTT client (event management, heartbeat, discovery)
- `src/display_manager.py` - Display controller (✨ **Updated for Impressive**)
- `src/requirements.txt` - Python dependencies
### Documentation
- `README.md` - Complete project documentation (✨ **New comprehensive guide**)
- `IMPRESSIVE_INTEGRATION.md` - Detailed presentation system documentation
- `src/DISPLAY_MANAGER.md` - Display Manager architecture
- `src/IMPLEMENTATION_SUMMARY.md` - Implementation overview
- `src/README.md` - MQTT client documentation
- `src/CONTAINER_TRANSITION.md` - Docker deployment guide
### Scripts (Production & Testing)
- `scripts/start-dev.sh` - Start development client
- `scripts/start-display-manager.sh` - Start Display Manager
- `scripts/test-display-manager.sh` - Interactive testing menu
- `scripts/test-impressive.sh` - Test Impressive (auto-quit mode)
- `scripts/test-impressive-loop.sh` - Test Impressive (loop mode)
- `scripts/test-mqtt.sh` - Test MQTT connectivity
- `scripts/test-screenshot.sh` - Test screenshot capture
- `scripts/test-utc-timestamps.sh` - Test event timing
- `scripts/present-pdf-auto-advance.sh` - PDF presentation wrapper
- `scripts/infoscreen-display.service` - Systemd service file
### Configuration
- `.env` - Environment variables
- `.github/copilot-instructions.md` - AI assistant context
## 🗑️ Files Removed
### Obsolete Auto-Advance Scripts (xdotool approach - FAILED)
-`scripts/auto-advance-slides.sh` (v1)
-`scripts/auto-advance-slides-v2.sh` (enhanced window management)
-`scripts/auto-advance-slides-v3.sh` (end detection)
-`scripts/auto-advance-slides-v4.sh` (active window approach)
-`scripts/auto-advance-slides-v5.sh` (hybrid approach)
### Obsolete Test Scripts
-`scripts/quick-test-autoadvance.sh`
-`scripts/quick-test-autoadvance-v3.sh`
-`scripts/quick-test-v4.sh`
-`scripts/quick-test-v5.sh`
-`scripts/quick-start.sh`
-`scripts/test-auto-advance.sh`
-`scripts/test-slideshow-start.sh`
-`scripts/test-pdf-approach.sh`
-`scripts/test-aggressive-focus.sh`
-`scripts/test-manual-vs-xdotool.sh`
-`scripts/test-show-parameter.sh`
-`scripts/test-presentation.sh`
-`scripts/test-modified-presentation.sh`
-`scripts/test-option1-modified-pptx.sh`
-`scripts/test-video-conversion.sh`
### Obsolete Diagnostic Scripts
-`scripts/debug-window-focus.sh`
-`scripts/diagnose-evince-keyboard.sh`
-`scripts/diagnose-f5-issue.sh`
-`scripts/diagnose-libreoffice-show.sh`
-`scripts/compare-v2-v3.sh`
### Failed Alternative Approaches
-`scripts/add_slide_timings.py` (modify PPTX timings - FAILED)
-`scripts/present-as-video.sh` (video conversion - FAILED)
-`scripts/play-test-video.sh`
-`scripts/present-with-pympress.sh` (alternative presenter - FAILED)
-`scripts/present-with-impressive.sh` (redundant, replaced by present-pdf-auto-advance.sh)
### Obsolete Documentation
-`AUTO_ADVANCE_QUICKREF.txt`
-`AUTO_ADVANCE_V3_QUICKREF.txt`
-`SLIDESHOW_FIX_QUICKREF.txt`
-`UTC_FIX_QUICKREF.txt`
-`IMPRESSIVE_SOLUTION.txt` (merged into IMPRESSIVE_INTEGRATION.md)
-`QUICK_START_GUIDE.md` (replaced by comprehensive README.md)
-`src/AUTO_ADVANCE_V3.md`
-`src/SLIDES_NOT_ADVANCING_FIX.md`
-`src/FULLSCREEN_AUTOADVANCE_FIX.md`
-`src/ALTERNATIVE_APPROACHES.md`
-`src/UTC_FIX.md`
-`src/AUTO_ADVANCE_FIX.md`
-`src/PDF_PRESENTATION_SOLUTION.md`
-`src/SLIDESHOW_FIX.md`
## 📊 Statistics
- **Total files removed:** ~50+ obsolete files
- **Scripts removed:** ~35 scripts
- **Documentation removed:** ~15 files
- **Final script count:** 10 essential scripts
- **Final documentation:** 6 comprehensive guides
## 🎯 Result
The workspace now contains:
-**Clean, production-ready code**
-**Working Impressive integration**
-**Comprehensive documentation**
-**Essential testing scripts only**
-**No obsolete or failed approaches**
## 🚀 Next Steps
1. **Test the solution:**
```bash
./scripts/test-display-manager.sh
```
2. **Start production services:**
```bash
# Terminal 1: MQTT client
cd src && python3 simclient.py
# Terminal 2: Display Manager
cd src && python3 display_manager.py
```
3. **Deploy to production:**
- Review systemd service: `scripts/infoscreen-display.service`
- Enable auto-start on boot
- Configure `.env` for production
## 📚 Documentation Structure
```
README.md ← Start here (comprehensive guide)
├── IMPRESSIVE_INTEGRATION.md ← Presentation system details
└── src/
├── DISPLAY_MANAGER.md ← Display Manager architecture
├── IMPLEMENTATION_SUMMARY.md ← Implementation overview
├── README.md ← MQTT client details
└── CONTAINER_TRANSITION.md ← Docker deployment
```
## ✨ Key Improvements in Final Solution
### Before (Failed Approaches)
- ❌ xdotool + LibreOffice (5 versions, all failed)
- ❌ Window focus management issues
- ❌ Complex wrapper scripts
- ❌ Unreliable slide advancement
- ❌ No native loop support
### After (Impressive Solution)
- ✅ Native auto-advance (no hacks)
- ✅ Built-in loop support
- ✅ Clean codebase
- ✅ Reliable on Raspberry Pi
- ✅ Simple maintenance
---
**Cleanup Date:** October 1, 2025
**Status:** ✅ Complete
**Result:** Production-ready workspace with final solution only

272
IMPRESSIVE_INTEGRATION.md Normal file
View File

@@ -0,0 +1,272 @@
# Impressive Integration - Display Manager
## Overview
The Display Manager now uses **Impressive** as the primary presentation tool for auto-advancing slideshows. This provides a robust, reliable solution for kiosk-mode presentations with native loop and auto-quit support.
## Why Impressive?
After testing multiple approaches (xdotool + LibreOffice, video conversion, etc.), Impressive proved to be the best solution:
**Native auto-advance** - No need for xdotool hacks or window management
**Loop support** - Built-in `--wrap` parameter for infinite looping
**Auto-quit** - Built-in `--autoquit` parameter to exit after last slide
**Reliable** - Works consistently on Raspberry Pi without focus issues
**Simple** - Clean command-line interface, no complex scripting needed
## How It Works
### 1. File Conversion (PPTX → PDF)
When a PPTX/PPT/ODP file is detected:
```bash
libreoffice --headless --convert-to pdf --outdir presentation/ file.pptx
```
The converted PDF is cached and reused if the source file hasn't changed.
### 2. Impressive Presentation
For loop mode (most events):
```bash
impressive --fullscreen --nooverview --auto 5 --wrap presentation.pdf
```
For single playthrough:
```bash
impressive --fullscreen --nooverview --auto 5 --autoquit presentation.pdf
```
## Event JSON Format
### Looping Presentation (Typical for Events)
```json
{
"id": "event_123",
"start": "2025-10-01 14:00:00",
"end": "2025-10-01 16:00:00",
"presentation": {
"files": [
{
"name": "slides.pptx",
"url": "https://server/files/slides.pptx"
}
],
"auto_advance": true,
"slide_interval": 10,
"loop": true
}
}
```
**Result:** Slides advance every 10 seconds, presentation loops infinitely until event end time.
### Single Playthrough
```json
{
"id": "event_456",
"presentation": {
"files": [{"name": "welcome.pptx"}],
"auto_advance": true,
"slide_interval": 5,
"loop": false
}
}
```
**Result:** Slides advance every 5 seconds, Impressive exits after last slide.
### Manual Advance (No Auto-Advance)
```json
{
"presentation": {
"files": [{"name": "manual.pptx"}],
"auto_advance": false
}
}
```
**Result:** Presentation displays but doesn't auto-advance (manual control only).
## Parameters Reference
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `auto_advance` | boolean | `false` | Enable automatic slide advancement |
| `slide_interval` | integer | `10` | Seconds between slides (when auto_advance=true) |
| `loop` | boolean | `false` | Loop presentation (vs. quit after last slide) |
## Display Manager Implementation
### Code Location
`src/display_manager.py` - `start_presentation()` method
### Key Features
1. **Smart Caching**: Checks if PDF exists and is newer than source PPTX
2. **Automatic Conversion**: Uses LibreOffice headless mode for PPTX→PDF
3. **Fallback Support**: Falls back to evince/okular if Impressive not installed
4. **Comprehensive Logging**: Logs all operations for debugging
### Process Flow
```
1. Receive presentation event
2. Check file type (.pptx, .pdf, etc.)
3. If PPTX:
a. Check if PDF cache exists and is current
b. Convert to PDF if needed (LibreOffice headless)
4. Build Impressive command:
- Always: --fullscreen --nooverview
- If auto_advance: --auto <interval>
- If loop: --wrap
- If not loop: --autoquit
5. Start Impressive process
6. Monitor process and event timing
```
## Testing
### Test Scripts Available
1. **test-impressive.sh** - Test Impressive with auto-quit (single playthrough)
```bash
./scripts/test-impressive.sh
```
2. **test-impressive-loop.sh** - Test Impressive with loop mode
```bash
./scripts/test-impressive-loop.sh
```
3. **test-display-manager.sh** - Interactive testing with Display Manager
```bash
./scripts/test-display-manager.sh
```
Then select option 2 (Create PRESENTATION test event)
### Manual Testing
Create a test event:
```bash
cat > src/current_event.json <<EOF
{
"id": "test_001",
"start": "2025-01-01 00:00:00",
"end": "2025-12-31 23:59:59",
"presentation": {
"files": [{"name": "LPUV4I_Folien_Nowitzki_Bewertungskriterien.pptx"}],
"auto_advance": true,
"slide_interval": 5,
"loop": true
}
}
EOF
```
Start Display Manager:
```bash
cd src && python3 display_manager.py
```
## Installation Requirements
### Impressive (Required)
```bash
sudo apt-get install impressive
```
### LibreOffice (For PPTX Conversion)
```bash
sudo apt-get install libreoffice
```
### Python Dependencies
```bash
pip install python-dotenv paho-mqtt
```
## Troubleshooting
### Presentation doesn't convert
- Check LibreOffice is installed: `which libreoffice`
- Check conversion logs in `logs/display_manager.log`
- Verify presentation file exists in `presentation/` directory
### Impressive doesn't start
- Check installation: `which impressive`
- Install if missing: `sudo apt-get install impressive`
- Check X11 DISPLAY variable: `echo $DISPLAY` (should be `:0`)
### Slides don't advance
- Verify `auto_advance: true` in event JSON
- Check `slide_interval` is set (defaults to 10 seconds)
- Review logs for Impressive command being executed
### Presentation doesn't loop
- Verify `loop: true` in event JSON
- Check logs confirm `--wrap` parameter is used
- Test with test-impressive-loop.sh script
### Conversion fails with timeout
- Large PPTX files may take >60s to convert
- Check disk space: `df -h`
- Monitor conversion: `tail -f logs/display_manager.log`
## Abandoned Approaches
For historical context, these approaches were tried but didn't work reliably on Raspberry Pi:
❌ **xdotool + LibreOffice** (5 versions tested)
- Problem: Window focus issues prevented reliable slide advancement
- Versions: v1 (basic), v2 (enhanced focus), v3 (end detection), v4 (active window), v5 (hybrid)
❌ **Video Conversion**
- Problem: Black screen issues, complex conversion process
- Tool tested: LibreOffice export to video
❌ **evince + xdotool**
- Problem: Same focus issues as LibreOffice approach
❌ **Modified PPTX Timings**
- Problem: LibreOffice --show parameter doesn't work properly in fullscreen
## Architecture Benefits
### Separation of Concerns
- **Display Manager**: Event timing, process management, file management
- **LibreOffice**: PPTX→PDF conversion (headless, no GUI)
- **Impressive**: Presentation display with native features
### Reliability
- No window management hacks (xdotool)
- No timing-dependent scripts
- Native features = fewer failure points
### Maintainability
- Simple, clean code in Display Manager
- Well-documented Impressive parameters
- Easy to debug with comprehensive logging
## Future Enhancements
Possible improvements for future versions:
1. **Slide Timing Metadata**: Extract slide timings from PPTX and pass to Impressive
2. **Multi-Screen Support**: Extend for multiple display outputs
3. **Transition Effects**: Utilize Impressive's transition capabilities
4. **Remote Control**: Add MQTT commands to control presentation (pause/resume)
5. **Thumbnail Cache**: Pre-generate thumbnails for dashboard previews
## References
- **Impressive Homepage**: http://impressive.sourceforge.net/
- **Impressive Manual**: `man impressive` or `impressive --help`
- **LibreOffice Headless**: https://help.libreoffice.org/Common/Starting_in_Headless_Mode
- **Display Manager Code**: `src/display_manager.py`
- **Test Scripts**: `scripts/test-impressive*.sh`
---
**Last Updated:** October 2025
**Status:** ✅ Production Ready
**Tested On:** Raspberry Pi 5, Raspberry Pi OS (Bookworm)

View File

@@ -0,0 +1,258 @@
# Progress Bar Implementation Summary
## Overview
The Display Manager now supports the scheduler's `page_progress` and `auto_progress` fields to control Impressive's progress bar features during presentations.
## Implementation Details
### Fields Supported
| Field | Type | Impressive Option | Description |
|-------|------|-------------------|-------------|
| `page_progress` | boolean | `--page-progress` (or `-q`) | Shows overall progress bar indicating position in presentation |
| `auto_progress` | boolean | `--auto-progress` (or `-k`) | Shows per-page countdown progress during auto-advance |
### Event Flow
1. **Scheduler sends event** with `page_progress` and `auto_progress` fields
2. **simclient.py receives** and stores complete event in `current_event.json` (no filtering)
3. **display_manager.py reads** event and extracts progress settings
4. **Impressive launches** with appropriate `--page-progress` and/or `--auto-progress` options
## Code Changes
### simclient.py
Enhanced documentation in `save_event_to_json()` to explicitly mention progress bar fields are preserved:
```python
def save_event_to_json(event_data):
"""Speichert eine Event-Nachricht in der Datei current_event.json
This function preserves ALL fields from the incoming event data,
including scheduler-specific fields like:
- page_progress: Show overall progress bar in presentation
- auto_progress: Show per-page auto-advance countdown
- And any other fields sent by the scheduler
"""
```
### display_manager.py
Added support for reading and using progress fields in `start_presentation()`:
```python
# Get scheduler-specific progress display settings
page_progress = event.get('page_progress', False) # Show overall progress bar
auto_progress = event.get('auto_progress', False) # Show per-page auto-advance progress
# Later in Impressive command building:
if page_progress:
cmd.append('--page-progress')
logging.info("Page progress bar enabled (shows overall position in presentation)")
if auto_progress:
cmd.append('--auto-progress')
logging.info("Auto-progress bar enabled (shows per-page countdown during auto-advance)")
```
## Example Events
### With Page Progress Only
```json
{
"id": 100,
"group_id": 2,
"page_progress": true,
"auto_progress": false,
"presentation": {
"auto_advance": true,
"slide_interval": 10,
"loop": true,
"files": [{"name": "slides.pdf"}]
}
}
```
**Result:** Impressive shows overall progress bar at bottom: `[=====> ] 50%`
### With Auto-Progress Only
```json
{
"id": 101,
"group_id": 2,
"page_progress": false,
"auto_progress": true,
"presentation": {
"auto_advance": true,
"slide_interval": 10,
"files": [{"name": "slides.pdf"}]
}
}
```
**Result:** Impressive shows countdown bar for each slide during auto-advance
### With Both Progress Bars
```json
{
"id": 102,
"group_id": 2,
"page_progress": true,
"auto_progress": true,
"presentation": {
"auto_advance": true,
"slide_interval": 10,
"loop": true,
"files": [{"name": "slides.pdf"}]
}
}
```
**Result:** Both progress indicators visible simultaneously
### No Progress Bars (Default)
```json
{
"id": 103,
"group_id": 2,
"presentation": {
"auto_advance": true,
"slide_interval": 10,
"files": [{"name": "slides.pdf"}]
}
}
```
**Result:** Clean presentation without progress indicators (fields default to `false`)
## Testing
### Interactive Test Script
```bash
cd ~/infoscreen-dev/scripts
./test-progress-bars.sh
```
This interactive script allows you to:
1. Select a progress bar configuration (none, page only, auto only, or both)
2. Sends test event to MQTT
3. Verifies Display Manager processes it correctly
4. Shows expected behavior
### Manual Testing with mosquitto_pub
```bash
# Test with both progress bars
mosquitto_pub -h localhost -t "infoscreen/events/2" -m '{
"id": 999,
"page_progress": true,
"auto_progress": true,
"presentation": {
"auto_advance": true,
"slide_interval": 5,
"loop": true,
"files": [{"name": "test.pdf"}]
}
}'
```
### Verification
1. **Check Display Manager Log:**
```bash
tail -f ~/infoscreen-dev/logs/display_manager.log
```
Look for:
```
Page progress bar enabled (shows overall position in presentation)
Auto-progress bar enabled (shows per-page countdown during auto-advance)
```
2. **Check Impressive Command:**
```bash
ps aux | grep impressive
```
Should show command with appropriate options:
```
impressive --fullscreen --nooverview --auto 5 --wrap --page-progress --auto-progress /path/to/file.pdf
```
3. **Visual Verification:**
- Watch the presentation on the display
- Page progress: Bar at bottom showing position
- Auto-progress: Countdown overlay on each slide
## Impressive Command Examples
```bash
# No progress bars (default)
impressive --fullscreen --auto 10 --wrap presentation.pdf
# Page progress only
impressive --fullscreen --auto 10 --wrap --page-progress presentation.pdf
impressive --fullscreen --auto 10 --wrap -q presentation.pdf # Short form
# Auto-progress only
impressive --fullscreen --auto 10 --wrap --auto-progress presentation.pdf
impressive --fullscreen --auto 10 --wrap -k presentation.pdf # Short form
# Both progress bars
impressive --fullscreen --auto 10 --wrap --page-progress --auto-progress presentation.pdf
impressive --fullscreen --auto 10 --wrap -q -k presentation.pdf # Short form
```
## Benefits
1. **User Feedback**: Viewers know where they are in the presentation
2. **Time Awareness**: Auto-progress shows how long until next slide
3. **Professional Look**: Progress indicators enhance presentation quality
4. **Flexible Control**: Scheduler can enable/disable per event
5. **Backward Compatible**: Fields default to `false`, existing events work unchanged
## Compatibility
- ✅ **Impressive**: Full support for both progress bar features
- ⚠️ **Evince/Okular**: Fallback viewers don't support progress bars (features ignored)
- ✅ **PDF Files**: Native support with no conversion needed
- ✅ **PPTX Files**: Automatic conversion to PDF, then progress bars work
## Documentation Updates
- ✅ README.md - Added progress bar field documentation
- ✅ SCHEDULER_FIELDS_SUPPORT.md - Updated with implementation details
- ✅ Code comments - Enhanced docstrings
- ✅ Test scripts - Created test-progress-bars.sh
- ✅ This summary document
## Logging
With `LOG_LEVEL=DEBUG`, you'll see:
```
[INFO] Event page_progress = true
[INFO] Event auto_progress = true
[INFO] Starting presentation: slides.pdf
[INFO] Auto-advance enabled (interval: 10s)
[INFO] Loop mode enabled (presentation will restart after last slide)
[INFO] Page progress bar enabled (shows overall position in presentation)
[INFO] Auto-progress bar enabled (shows per-page countdown during auto-advance)
[DEBUG] Full command: impressive --fullscreen --nooverview --auto 10 --wrap --page-progress --auto-progress /path/to/slides.pdf
```
## Summary
**Implementation complete** - Progress bar fields are now fully supported
**Backward compatible** - Existing events without these fields work unchanged
**Well documented** - README, code comments, and test scripts updated
**Tested** - Interactive test script available
**Flexible** - Both options can be used independently or together
**Clean code** - Minimal changes, follows existing patterns

192
QUICK_REFERENCE.md Normal file
View File

@@ -0,0 +1,192 @@
# 🚀 Infoscreen Client - Quick Reference
## One-Line Summary
**PPTX → PDF (LibreOffice) → Impressive (auto-advance + loop) = Perfect Kiosk Presentations**
## Essential Commands
### Start Services
```bash
# MQTT Client
cd ~/infoscreen-dev/src && python3 simclient.py
# Display Manager
cd ~/infoscreen-dev/src && python3 display_manager.py
```
### Testing
```bash
# Interactive testing menu
./scripts/test-display-manager.sh
# Test Impressive (single playthrough)
./scripts/test-impressive.sh
# Test Impressive (loop mode)
./scripts/test-impressive-loop.sh
# Test MQTT
./scripts/test-mqtt.sh
```
### Logs
```bash
tail -f ~/infoscreen-dev/logs/display_manager.log
tail -f ~/infoscreen-dev/logs/simclient.log
```
## Event JSON Examples
### Loop Presentation (Most Common)
```json
{
"presentation": {
"files": [{"name": "slides.pptx"}],
"auto_advance": true,
"slide_interval": 10,
"loop": true
}
}
```
### Single Playthrough
```json
{
"presentation": {
"files": [{"name": "welcome.pptx"}],
"auto_advance": true,
"slide_interval": 5,
"loop": false
}
}
```
### Webpage
```json
{
"web": {
"url": "https://dashboard.example.com"
}
}
```
### Video
```json
{
"video": {
"url": "https://server/video.mp4",
"loop": true
}
}
```
## Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `auto_advance` | bool | `false` | Enable auto-advance |
| `slide_interval` | int | `10` | Seconds per slide |
| `loop` | bool | `false` | Loop vs. quit |
## Impressive Commands
```bash
# Auto-advance + loop
impressive --fullscreen --nooverview --auto 10 --wrap file.pdf
# Auto-advance + quit after last slide
impressive --fullscreen --nooverview --auto 10 --autoquit file.pdf
```
## Troubleshooting
### Presentation doesn't start
```bash
which impressive # Should show path
which libreoffice # Should show path
tail -f logs/display_manager.log # Check errors
```
### Slides don't advance
- Verify `auto_advance: true` in event JSON
- Check `slide_interval` is set
- Test: `./scripts/test-impressive.sh`
### Presentation doesn't loop
- Verify `loop: true` in event JSON
- Test: `./scripts/test-impressive-loop.sh`
### MQTT issues
```bash
./scripts/test-mqtt.sh # Test connectivity
```
## File Locations
```
~/infoscreen-dev/
├── src/
│ ├── simclient.py # MQTT client
│ ├── display_manager.py # Display controller
│ ├── current_event.json # Current event
│ └── presentation/ # Downloaded files
├── logs/
│ ├── simclient.log
│ └── display_manager.log
├── scripts/
│ └── *.sh # All test/start scripts
└── .env # Configuration
```
## Installation
```bash
# System dependencies
sudo apt-get install python3 python3-pip python3-venv \
libreoffice impressive chromium-browser vlc
# Python dependencies
cd ~/infoscreen-dev
python3 -m venv venv
source venv/bin/activate
pip install -r src/requirements.txt
```
## Configuration (.env)
```bash
ENV=production
MQTT_BROKER=192.168.1.100
MQTT_PORT=1883
HEARTBEAT_INTERVAL=30
SCREENSHOT_INTERVAL=60
DISPLAY_CHECK_INTERVAL=5
```
## Documentation
- **README.md** - Complete guide (start here)
- **IMPRESSIVE_INTEGRATION.md** - Presentation details
- **CLEANUP_SUMMARY.md** - What changed
- **WORKSPACE_STATUS.txt** - Visual summary
## Key Features
✅ Auto-advance presentations
✅ Loop mode for events
✅ MQTT-controlled display
✅ Group management
✅ Heartbeat monitoring
✅ Multi-content (presentations, video, web)
✅ Kiosk mode
## Status
**Version:** Final Solution (October 2025)
**Status:** ✅ Production Ready
**Tested:** Raspberry Pi 5, Pi OS Bookworm
**Solution:** Impressive PDF Presenter
---
**Need help?** Read README.md or check logs/

612
README.md Normal file
View File

@@ -0,0 +1,612 @@
# Infoscreen Client - Display Manager
Digital signage system for Raspberry Pi that displays presentations, videos, and web content in kiosk mode. Centrally managed via MQTT with automatic client discovery and heartbeat monitoring.
## 🎯 Key Features
- **Automatic Presentation Display** - PPTX files converted to PDF and displayed with Impressive
- **Auto-Advance Slideshows** - Configurable timing for automatic slide progression
- **Loop Mode** - Presentations can loop infinitely or quit after last slide
- **MQTT Integration** - Real-time event management from central server
- **Group Management** - Organize clients into groups for targeted content
- **Heartbeat Monitoring** - Regular status updates and screenshot dashboard
- **Multi-Content Support** - Presentations, videos, and web pages
- **Kiosk Mode** - Full-screen display with automatic startup
## 📋 System Requirements
### Hardware
- Raspberry Pi 4/5 (or compatible)
- HDMI display
- Network connectivity (WiFi or Ethernet)
- SSD storage recommended
### Software
- Raspberry Pi OS (Bookworm or newer)
- Python 3.x
- LibreOffice (for PPTX→PDF conversion)
- Impressive (PDF presenter with auto-advance)
- Chromium browser (for web content)
- VLC or MPV (for video playback)
## 🚀 Quick Start
### 1. Installation
```bash
# Clone repository
cd ~/
git clone <repository-url> infoscreen-dev
cd infoscreen-dev
# Install system dependencies
sudo apt-get update
sudo apt-get install -y \
python3 python3-pip python3-venv \
libreoffice impressive \
chromium-browser vlc
# Create Python virtual environment
python3 -m venv venv
source venv/bin/activate
# Install Python dependencies
pip install -r src/requirements.txt
```
### 2. Configuration
Create `.env` file in project root:
```bash
# Environment
ENV=production
LOG_LEVEL=INFO
# MQTT Configuration
MQTT_BROKER=192.168.1.100
MQTT_PORT=1883
MQTT_BROKER_FALLBACKS=192.168.1.101,192.168.1.102
# Timing (seconds)
HEARTBEAT_INTERVAL=30
SCREENSHOT_INTERVAL=60
DISPLAY_CHECK_INTERVAL=5
# File/API Server (used to download presentation files)
# Defaults to the same host as MQTT_BROKER, port 8000, scheme http.
# If incoming event URLs use host 'server' (or are host-less), simclient rewrites them to this server.
FILE_SERVER_HOST= # optional; if empty, defaults to MQTT_BROKER
FILE_SERVER_PORT=8000 # default API port
# http or https
FILE_SERVER_SCHEME=http
# FILE_SERVER_BASE_URL= # optional full override, e.g., http://192.168.1.100:8000
```
### 3. Start Services
```bash
# Start MQTT client (handles events, heartbeat, discovery)
cd ~/infoscreen-dev/src
python3 simclient.py
# In another terminal: Start Display Manager
cd ~/infoscreen-dev/src
python3 display_manager.py
```
Or use the startup script:
```bash
./scripts/start-display-manager.sh
```
## 📊 Presentation System
### How It Works
The system uses **Impressive** as the PDF presenter with native auto-advance and loop support:
1. **PPTX files** are automatically converted to PDF using LibreOffice headless
2. **PDF files** are displayed directly with Impressive
3. **Auto-advance** uses Impressive's built-in `--auto` parameter
4. **Loop mode** uses Impressive's `--wrap` parameter (infinite loop)
5. **Auto-quit** uses Impressive's `--autoquit` parameter (exit after last slide)
### Event JSON Format
#### Looping Presentation (Typical for Events)
```json
{
"id": "event_123",
"start": "2025-10-01 14:00:00",
"end": "2025-10-01 16:00:00",
"presentation": {
"files": [
{
"name": "slides.pptx",
"url": "https://server/files/slides.pptx"
}
],
"auto_advance": true,
"slide_interval": 10,
"loop": true
}
}
```
**Result:** Slides advance every 10 seconds, presentation loops infinitely until event ends.
#### Single Playthrough
```json
{
"presentation": {
"files": [{"name": "welcome.pptx"}],
"auto_advance": true,
"slide_interval": 5,
"loop": false
}
}
```
**Result:** Slides advance every 5 seconds, exits after last slide.
### Presentation Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `auto_advance` | boolean | `false` | Enable automatic slide advancement |
| `slide_interval` | integer | `10` | Seconds between slides |
| `loop` | boolean | `false` | Loop presentation vs. quit after last slide |
### Scheduler-Specific Fields
The scheduler may send additional fields that are preserved in `current_event.json`:
| Field | Type | Description |
|-------|------|-------------|
| `page_progress` | boolean | Show overall progress bar in presentation (Impressive `--page-progress`). Can be provided at `presentation.page_progress` (preferred) or top-level. |
| `auto_progress` | boolean | Show per-page auto-advance countdown (Impressive `--auto-progress`). Can be provided at `presentation.auto_progress` (preferred) or top-level. |
| `occurrence_of_id` | integer | Original event ID for recurring events |
| `recurrence_rule` | string | iCal recurrence rule (RRULE format) |
| `recurrence_end` | string | End date for recurring events |
**Note:** All fields from the scheduler are automatically preserved when events are stored in `current_event.json`. The client does not filter or modify scheduler-specific metadata.
#### Progress Bar Display
When using Impressive PDF presenter:
- `page_progress: true` - Shows a progress bar at the bottom indicating position in the presentation
- `auto_progress: true` - Shows a countdown progress bar for each slide during auto-advance
- Both options can be enabled simultaneously for maximum visual feedback
## 🎥 Video Events
```json
{
"video": {
"url": "https://server/videos/intro.mp4",
"loop": true,
"autoplay": true,
"volume": 0.8
}
}
```
Notes:
- The Display Manager prefers `python-vlc` (libvlc) when available. This gives programmatic control over playback (autoplay, loop, volume) and ensures the player is cleanly stopped and released when events end.
- Supported video event fields:
- `url` (string): HTTP/HTTPS or streaming URL. URLs using the placeholder host `server` are rewritten to the configured file server (see File/API Server configuration).
- `autoplay` (boolean): start playback automatically when the event becomes active (default: true).
- `loop` (boolean): loop playback indefinitely.
- `volume` (float): 0.01.0 (mapped internally to VLC's 0100 volume scale).
- If `python-vlc` is not installed, the Display Manager will fall back to launching the external `vlc` binary.
- The manager attempts to make the player window fullscreen and remove window decorations. For a truly panel-free fullscreen (no taskbar), run the Display Manager inside a minimal kiosk X session or a dedicated user session without a desktop panel (see the kiosk notes below).
## 🌐 Web Events
```json
{
"web": {
"url": "https://dashboard.example.com"
}
}
```
Opens webpage in Chromium kiosk mode (fullscreen, no UI).
## 🗂️ Project Structure
```
infoscreen-dev/
├── .env # Environment configuration
├── README.md # This file
├── IMPRESSIVE_INTEGRATION.md # Detailed presentation system docs
├── src/
│ ├── simclient.py # MQTT client (events, heartbeat, discovery)
│ ├── display_manager.py # Display controller (manages applications)
│ ├── requirements.txt # Python dependencies
│ ├── current_event.json # Current active event (runtime)
│ ├── config/ # Persistent client data
│ │ ├── client_uuid.txt
│ │ └── last_group_id.txt
│ ├── presentation/ # Downloaded presentation files
│ └── screenshots/ # Dashboard screenshots
├── scripts/
│ ├── start-dev.sh # Start development client
│ ├── start-display-manager.sh # Start Display Manager
│ ├── test-display-manager.sh # Interactive testing
│ ├── test-impressive.sh # Test Impressive (auto-quit mode)
│ ├── test-impressive-loop.sh # Test Impressive (loop mode)
│ ├── test-mqtt.sh # Test MQTT connectivity
│ ├── test-screenshot.sh # Test screenshot capture
│ ├── test-utc-timestamps.sh # Test event timing
│ └── present-pdf-auto-advance.sh # PDF presentation wrapper
└── logs/ # Application logs
```
## 🧪 Testing
### Test Display Manager
```bash
./scripts/test-display-manager.sh
```
Interactive menu for testing:
- Check Display Manager status
- Create test events (presentation, video, webpage)
- View active processes
- Cycle through different event types
### Test Impressive Presentation
**Single playthrough (auto-quit):**
```bash
./scripts/test-impressive.sh
```
**Loop mode (infinite):**
```bash
./scripts/test-impressive-loop.sh
```
### Test MQTT Communication
```bash
./scripts/test-mqtt.sh
```
Verifies MQTT broker connectivity and topics.
### Test Screenshot Capture
```bash
./scripts/test-screenshot.sh
```
Captures test screenshot for dashboard monitoring.
## 🔧 Configuration Details
### Environment Variables
#### Environment
- `ENV` - `development` or `production`
- `DEBUG_MODE` - Enable debug output (1=on, 0=off)
- `LOG_LEVEL` - `DEBUG`, `INFO`, `WARNING`, `ERROR`
#### MQTT
- `MQTT_BROKER` - Primary MQTT broker IP/hostname
- `MQTT_PORT` - MQTT port (default: 1883)
- `MQTT_BROKER_FALLBACKS` - Comma-separated fallback brokers
- `MQTT_USERNAME` - Optional authentication
- `MQTT_PASSWORD` - Optional authentication
#### Timing
- `HEARTBEAT_INTERVAL` - Status update frequency (seconds)
- `SCREENSHOT_INTERVAL` - Dashboard screenshot frequency (seconds)
- `DISPLAY_CHECK_INTERVAL` - Event check frequency (seconds)
#### File/API Server
- `FILE_SERVER_HOST` - Host/IP of the file server; defaults to `MQTT_BROKER` when empty
- `FILE_SERVER_PORT` - Port of the file server (default: 8000)
- `FILE_SERVER_SCHEME` - `http` or `https` (default: http)
- `FILE_SERVER_BASE_URL` - Optional full override, e.g., `https://api.example.com:443`
### File Server URL Resolution
- The MQTT client (`src/simclient.py`) downloads presentation files listed in events.
- If an event contains URLs with host `server` (e.g., `http://server:8000/...`) or a missing host, the client rewrites them to the configured file server.
- By default, the file server host is the same as `MQTT_BROKER`, with port `8000` and scheme `http`.
- You can override this behavior using the `.env` variables above; `FILE_SERVER_BASE_URL` takes precedence over the individual host/port/scheme.
- Tip: When editing `.env`, keep comments after a space and `#` so values stay clean.
### MQTT Topics
#### Client → Server
- `infoscreen/discovery` - Initial client announcement
- `infoscreen/{client_id}/heartbeat` - Regular status updates
- `infoscreen/{client_id}/dashboard` - Screenshot images (base64)
#### Server → Client
- `infoscreen/{client_id}/discovery_ack` - Server response with client ID
- `infoscreen/{client_id}/group_id` - Group assignment
- `infoscreen/events/{group_id}` - Event commands for group
### Client Identification
**Hardware Token:** SHA256 hash of:
- CPU serial number
- MAC addresses (all network interfaces)
**Persistent UUID:** Stored in `src/config/client_uuid.txt`
**Group Membership:** Stored in `src/config/last_group_id.txt`
## 🔍 Troubleshooting
### Display Manager doesn't start presentations
**Check Impressive installation:**
```bash
which impressive
# If not found: sudo apt-get install impressive
```
**Check LibreOffice installation:**
```bash
which libreoffice
# If not found: sudo apt-get install libreoffice
```
**Check logs:**
```bash
tail -f logs/display_manager.log
```
### Presentations don't convert from PPTX
**Verify LibreOffice headless:**
```bash
libreoffice --headless --convert-to pdf --outdir /tmp presentation.pptx
ls -l /tmp/*.pdf
```
**Check disk space:**
```bash
df -h
```
### Slides don't auto-advance
**Verify event JSON:**
- `auto_advance: true` is set
- `slide_interval` is specified (default: 10)
**Test Impressive directly:**
```bash
./scripts/test-impressive.sh
```
### Presentation doesn't loop
**Verify event JSON:**
- `loop: true` is set
**Test loop mode:**
```bash
./scripts/test-impressive-loop.sh
```
### File downloads fail
Symptoms:
- `Failed to resolve 'server'` or `NameResolutionError` when downloading files
- `Invalid URL 'http # http or https://...'` in `logs/simclient.log`
What to check:
- Look for lines like `Lade Datei herunter von:` in `logs/simclient.log` to see the effective URL used
- Ensure the URL host is the MQTT broker IP (or your configured file server), not `server`
- Verify `.env` values dont include inline comments as part of the value (e.g., keep `FILE_SERVER_SCHEME=http` on its own line)
Fixes:
- If your API is on the same host as the broker: leave `FILE_SERVER_HOST` empty (defaults to `MQTT_BROKER`), keep `FILE_SERVER_PORT=8000`, and set `FILE_SERVER_SCHEME=http` or `https`
- To override fully, set `FILE_SERVER_BASE_URL` (e.g., `http://192.168.1.100:8000`); this takes precedence over host/port/scheme
- After changing `.env`, restart the simclient process
Expected healthy log sequence:
- `Lade Datei herunter von: http://<broker-ip>:8000/...`
- Followed by `"GET /... HTTP/1.1" 200` and `Datei erfolgreich heruntergeladen:`
### VLC hardware decode / renderer issues
If you see messages like:
```
[h264_v4l2m2m @ ...] Could not find a valid device
[h264_v4l2m2m @ ...] can't configure decoder
[... ] avcodec decoder error: cannot start codec (h264_v4l2m2m)
```
that indicates libVLC / ffmpeg attempted to use the platform V4L2 M2M hardware decoder but the kernel/device isn't available. Options to resolve:
- Enable the V4L2 M2M codec driver on the system (platform-specific; on Raspberry Pi ensure correct kernel/firmware and codec modules are loaded). Check `v4l2-ctl --list-devices` and `ls /dev/video*` after installing `v4l-utils`.
- Disable hardware decoding so libVLC/ffmpeg uses software decoding (reliable but higher CPU). You can test this by launching the `vlc` binary with:
```bash
vlc --avcodec-hw=none 'http://<your-video-url>'
```
Or modify `src/display_manager.py` to create the libVLC instance with software-decoding forced:
```python
instance = vlc.Instance('--avcodec-hw=none', '--no-video-title-show', '--no-video-deco')
```
This is the fastest workaround if hardware decode is not required or not available on the device.
### MQTT connection issues
**Test broker connectivity:**
```bash
./scripts/test-mqtt.sh
```
**Check broker status:**
```bash
# On server
sudo systemctl status mosquitto
```
**Try fallback brokers:**
Edit `.env` and add `MQTT_BROKER_FALLBACKS`
### Screenshots not uploading
**Test screenshot capture:**
```bash
./scripts/test-screenshot.sh
ls -l screenshots/
```
**Check DISPLAY variable:**
```bash
echo $DISPLAY # Should be :0
```
## 📚 Documentation
- **IMPRESSIVE_INTEGRATION.md** - Detailed presentation system documentation
- **src/DISPLAY_MANAGER.md** - Display Manager architecture
- **src/IMPLEMENTATION_SUMMARY.md** - Implementation overview
- **src/README.md** - MQTT client documentation
## 🔐 Security
- Hardware-based client identification (non-spoofable)
- Configurable MQTT authentication
- Local-only file storage
- No sensitive data in logs
## 🚢 Production Deployment
### Systemd Service
Create `/etc/systemd/system/infoscreen-display.service`:
```ini
[Unit]
Description=Infoscreen Display Manager
After=network.target
[Service]
Type=simple
User=olafn
WorkingDirectory=/home/olafn/infoscreen-dev/src
Environment="DISPLAY=:0"
Environment="XAUTHORITY=/home/olafn/.Xauthority"
ExecStart=/home/olafn/infoscreen-dev/venv/bin/python3 /home/olafn/infoscreen-dev/src/display_manager.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
Enable and start:
```bash
sudo systemctl daemon-reload
sudo systemctl enable infoscreen-display
sudo systemctl start infoscreen-display
sudo systemctl status infoscreen-display
```
### Auto-start on Boot
Both services (simclient.py and display_manager.py) should start automatically:
1. **simclient.py** - MQTT communication, event management
2. **display_manager.py** - Display application controller
Create similar systemd service for simclient.py.
### Docker Deployment (Alternative)
```bash
docker-compose -f src/docker-compose.production.yml up -d
```
See `src/CONTAINER_TRANSITION.md` for details.
## 📝 Development
### Development Mode
Set in `.env`:
```bash
ENV=development
DEBUG_MODE=1
LOG_LEVEL=DEBUG
HEARTBEAT_INTERVAL=10
```
### Start Development Client
```bash
./scripts/start-dev.sh
```
### View Logs
```bash
# Display Manager
tail -f logs/display_manager.log
# MQTT Client
tail -f logs/simclient.log
# Both
tail -f logs/*.log
```
## 🤝 Contributing
1. Test changes with `./scripts/test-display-manager.sh`
2. Verify MQTT communication with `./scripts/test-mqtt.sh`
3. Update documentation
4. Submit pull request
## 📄 License
[Add your license here]
## 🆘 Support
For issues or questions:
1. Check logs in `logs/` directory
2. Review troubleshooting section
3. Test individual components with test scripts
4. Check MQTT broker connectivity
---
**Last Updated:** October 2025
**Status:** ✅ Production Ready
**Tested On:** Raspberry Pi 5, Raspberry Pi OS (Bookworm)
## Recent changes (Oct 2025)
The following notable changes were added after the previous release and are included in this branch:
- Event handling: support for scheduler-provided `event_type` values (new types: `presentation`, `webuntis`, `webpage`, `website`). The display manager now prefers `event_type` when selecting which renderer to start.
- Web display: Chromium is launched in kiosk mode for web events. `website` events (scheduler) and legacy `web` keys are both supported and normalized.
- Auto-scroll feature: automatic scrolling for long websites implemented. Two mechanisms are available:
- CDP injection: The display manager attempts to inject a small auto-scroll script via Chrome DevTools Protocol (DevTools websocket) when possible (uses `websocket-client` and `requests`). Default injection duration: 60s.
- Extension fallback: When DevTools websocket handshakes are blocked (403), a tiny local Chrome extension (`src/chrome_autoscroll`) is loaded via `--load-extension` to run a content script that performs the auto-scroll reliably.
- Autoscroll enabled only for scheduler events with `event_type: "website"` (not for general `web` or `webpage`). The extension and CDP injection are only used when autoscroll is requested for that event type.
- New test utilities:
- `scripts/test_cdp.py` — quick DevTools JSON listing + Runtime.evaluate tester
- `scripts/test_cdp_origins.py` — tries several Origin headers to diagnose 403 handshakes
- Dependencies: `src/requirements.txt` updated to include `websocket-client` (used by the CDP injector).
- Small refactors and improved logging in `src/display_manager.py` to make event dispatch and browser injection more robust.
If you rely on autoscroll in production, review the security considerations around `--remote-debugging-port` (DevTools) and prefer the extension fallback if your Chromium build enforces strict websocket Origin policies.

202
SCHEDULER_FIELDS_SUPPORT.md Normal file
View File

@@ -0,0 +1,202 @@
# Scheduler Fields Support
## Overview
The infoscreen client automatically preserves all fields sent by the scheduler in the event database, including scheduler-specific metadata fields like `page_progress` and `auto_progress`.
## How It Works
### Event Storage (`simclient.py`)
When an event is received via MQTT on the `infoscreen/events/{group_id}` topic:
1. **Full Preservation**: The `save_event_to_json()` function stores the complete event data using `json.dump()` without filtering
2. **No Data Loss**: All fields from the scheduler are preserved, including:
- `page_progress` - Current page/slide progress tracking
- `auto_progress` - Auto-progression state
- `occurrence_of_id` - Original event ID for recurring events
- `recurrence_rule` - iCal recurrence rule (RRULE format)
- `recurrence_end` - End date for recurring events
- Any other scheduler-specific fields
3. **Debug Logging**: When `LOG_LEVEL=DEBUG`, the system logs the presence of `page_progress` and `auto_progress` fields
### Event Processing (`display_manager.py`)
The Display Manager:
- Uses scheduler-specific fields to control presentation behavior:
- `page_progress`: Controls Impressive's `--page-progress` option (overall progress bar)
- `auto_progress`: Controls Impressive's `--auto-progress` option (per-page countdown)
- Other scheduler-specific fields are preserved but not actively used by display logic
## Example Event with Scheduler Fields
```json
{
"id": 57,
"occurrence_of_id": 57,
"group_id": 2,
"title": "2. Wiederholung",
"start": "2025-10-19T05:00:00+00:00",
"end": "2025-10-19T05:30:00+00:00",
"recurrence_rule": "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SA,SU;UNTIL=20251101T225959Z",
"recurrence_end": "2025-11-01T22:59:59",
"page_progress": true,
"auto_progress": true,
"presentation": {
"type": "slideshow",
"auto_advance": true,
"slide_interval": 10,
"files": [
{
"name": "slides.pdf",
"url": "http://server:8000/api/files/slides.pdf",
"checksum": null,
"size": null
}
]
}
}
```
## Implementation Details
### Storage Function (simclient.py)
```python
def save_event_to_json(event_data):
"""Speichert eine Event-Nachricht in der Datei current_event.json
This function preserves ALL fields from the incoming event data,
including scheduler-specific fields like:
- page_progress: Current page/slide progress tracking
- auto_progress: Auto-progression state
- And any other fields sent by the scheduler
"""
```
### Display Manager Implementation (display_manager.py)
The Display Manager reads `page_progress` and `auto_progress` fields and translates them to Impressive command-line options:
```python
# In start_presentation() method:
# Get scheduler-specific progress display settings
page_progress = event.get('page_progress', False) # Show overall progress bar
auto_progress = event.get('auto_progress', False) # Show per-page auto-advance progress
# Later, when building Impressive command:
if page_progress:
cmd.append('--page-progress')
logging.info("Page progress bar enabled (shows overall position in presentation)")
if auto_progress:
cmd.append('--auto-progress')
logging.info("Auto-progress bar enabled (shows per-page countdown during auto-advance)")
```
### Impressive Command Line Options
| Event Field | Impressive Option | Short Form | Description |
|-------------|-------------------|------------|-------------|
| `page_progress: true` | `--page-progress` | `-q` | Shows a progress bar at the bottom indicating position in presentation |
| `auto_progress: true` | `--auto-progress` | `-k` | Shows a countdown progress bar for each page during auto-advance |
**Example Commands:**
```bash
# With page progress only
impressive --fullscreen --auto 10 --wrap --page-progress presentation.pdf
# With auto progress only
impressive --fullscreen --auto 10 --wrap --auto-progress presentation.pdf
# With both progress bars
impressive --fullscreen --auto 10 --wrap --page-progress --auto-progress presentation.pdf
```
### Storage Function (simclient.py)
```python
def save_event_to_json(event_data):
"""Speichert eine Event-Nachricht in der Datei current_event.json
This function preserves ALL fields from the incoming event data,
including scheduler-specific fields like:
- page_progress: Show overall progress bar in presentation
- auto_progress: Show per-page auto-advance countdown
- And any other fields sent by the scheduler
"""
try:
json_path = os.path.join(os.path.dirname(__file__), "current_event.json")
with open(json_path, "w", encoding="utf-8") as f:
json.dump(event_data, f, ensure_ascii=False, indent=2)
logging.info(f"Event-Nachricht in {json_path} gespeichert")
# Log if scheduler-specific fields are present (DEBUG level)
if isinstance(event_data, list):
for idx, event in enumerate(event_data):
if isinstance(event, dict):
if 'page_progress' in event:
logging.debug(f"Event {idx}: page_progress = {event['page_progress']}")
if 'auto_progress' in event:
logging.debug(f"Event {idx}: auto_progress = {event['auto_progress']}")
elif isinstance(event_data, dict):
if 'page_progress' in event_data:
logging.debug(f"Event page_progress = {event_data['page_progress']}")
if 'auto_progress' in event_data:
logging.debug(f"Event auto_progress = {event_data['auto_progress']}")
except Exception as e:
logging.error(f"Fehler beim Speichern der Event-Nachricht: {e}")
```
## Verification
To verify that scheduler fields are being stored correctly:
1. **Check the log file** (with `LOG_LEVEL=DEBUG`):
```bash
tail -f ~/infoscreen-dev/logs/simclient.log
```
Look for:
```
Event page_progress = 0
Event auto_progress = true
```
2. **Inspect current_event.json directly**:
```bash
cat ~/infoscreen-dev/src/current_event.json | jq
```
All scheduler fields should be present in the output.
3. **Test with MQTT message**:
```bash
mosquitto_pub -h localhost -t "infoscreen/events/2" -m '{
"id": 99,
"page_progress": 5,
"auto_progress": true,
"presentation": {
"files": [{"name": "test.pdf"}],
"auto_advance": true,
"slide_interval": 10
}
}'
```
Then check that `page_progress` and `auto_progress` appear in `current_event.json`.
## Future Use
If the Display Manager needs to use these scheduler fields in the future (e.g., to resume presentations at a specific page), the fields are already available in the event data without any code changes needed in `simclient.py`.
## Summary
✅ **No changes required** - The current implementation already preserves all scheduler fields
✅ **Automatic preservation** - Uses `json.dump()` which saves complete data structure
✅ **Debug logging added** - Logs presence of `page_progress` and `auto_progress` fields
**Documentation updated** - README now documents scheduler-specific fields
**Future-proof** - Any new scheduler fields will be automatically preserved

40
TODO.md Normal file
View File

@@ -0,0 +1,40 @@
# Project TODOs
This file tracks higher-level todos and design notes for the infoscreen client.
## Video playback (Raspberry Pi)
- Remove taskbar / window decorations in VLC window
- Goal: show video truly fullscreen without window title bar or OS panel/taskbar overlapping.
- Ideas / approaches:
- Use libVLC options from python-vlc: `--no-video-deco`, `--no-video-title-show`, `--video-on-top`, and call `player.set_fullscreen(True)` after playback starts.
- Run the Display Manager in a dedicated kiosk X session (no panel/desktop environment) or minimal window manager (openbox/matchbox) to avoid taskbar.
- Use `wmctrl` as a fallback to force fullscreen/above: `wmctrl -r <title> -b add,fullscreen,above`.
- Add an env var toggle, e.g. `VLC_KIOSK=1`, to enable these options from runtime.
- Acceptance criteria:
- Video occupies the full display area with no visible window controls or panels on top.
- Behaviour toggleable via env var.
- Add possibility to adjust sound level by HDMI-CEC using Python
- Goal: allow remote/automatic volume control over HDMI using CEC-capable hardware.
- Ideas / approaches:
- Use `libcec` bindings (e.g. `pycec` / `cec` packages) or call `cec-client` from shell to send volume commands to the TV/AVR.
- Map event volume (0.0-1.0) to CEC volume commands (some devices support absolute volume or key presses like `VOLUME_UP`/`VOLUME_DOWN`).
- Provide a small adapter module `src/hdmi_cec.py` that exposes `set_volume(level: float)` and `volume_step(up: bool)` used by `display_manager.py` when starting/stopping videos or on explicit volume events.
- Acceptance criteria:
- `set_volume()` issues appropriate CEC commands and returns success/failure.
- Document any platform limitations (some TVs don't support absolute volume via CEC).
## Next-high-level items
- Add environment-controlled libVLC hw-accel toggle (`VLC_HW_ACCEL=1|0`) to `display_manager.py` so software decode can be forced when necessary.
- Add automated tests for video start/stop lifecycle (mock python-vlc) to ensure resources are released on event end.
## Notes
- Keep all changes backward-compatible: external `vlc` binary fallback should still work.
- Document any new env vars in `README.md` and `.github/copilot-instructions.md` if added.
---
Generated: 2025-10-25

193
WORKSPACE_STATUS.txt Normal file
View File

@@ -0,0 +1,193 @@
╔════════════════════════════════════════════════════════════════╗
║ WORKSPACE CLEANUP COMPLETE ║
║ October 1, 2025 ║
╚════════════════════════════════════════════════════════════════╝
✅ FINAL SOLUTION: Impressive PDF Presenter with Auto-Advance & Loop
╔════════════════════════════════════════════════════════════════╗
║ WHAT REMAINS ║
╚════════════════════════════════════════════════════════════════╝
📂 CORE APPLICATION (2 files)
├── src/simclient.py ................. MQTT client & event manager
└── src/display_manager.py ........... Display controller (Impressive)
📂 DOCUMENTATION (6 files)
├── README.md ........................ Complete project guide ⭐
├── IMPRESSIVE_INTEGRATION.md ........ Presentation system details
├── CLEANUP_SUMMARY.md ............... This cleanup report
├── src/DISPLAY_MANAGER.md ........... Architecture documentation
├── src/IMPLEMENTATION_SUMMARY.md .... Implementation overview
└── src/README.md .................... MQTT client details
📂 SCRIPTS (10 files)
Production:
├── start-dev.sh ..................... Start development mode
├── start-display-manager.sh ......... Start Display Manager
└── present-pdf-auto-advance.sh ...... PDF presentation wrapper
Testing:
├── test-display-manager.sh .......... Interactive test menu
├── test-impressive.sh ............... Test auto-quit mode
├── test-impressive-loop.sh .......... Test loop mode
├── test-mqtt.sh ..................... Test MQTT connectivity
├── test-screenshot.sh ............... Test screenshot capture
└── test-utc-timestamps.sh ........... Test event timing
Deployment:
└── infoscreen-display.service ....... Systemd service file
╔════════════════════════════════════════════════════════════════╗
║ WHAT WAS REMOVED ║
╚════════════════════════════════════════════════════════════════╝
🗑️ REMOVED: ~50 obsolete files
❌ Failed xdotool Approaches (5 versions)
• auto-advance-slides.sh (v1, v2, v3, v4, v5)
• All had window focus issues on Raspberry Pi
❌ Alternative Approaches That Failed
• Video conversion (present-as-video.sh)
• Modified PPTX timings (add_slide_timings.py)
• PyMpress presenter (present-with-pympress.sh)
• evince + xdotool approach
❌ Diagnostic & Debug Scripts (~15 files)
• debug-window-focus.sh
• diagnose-evince-keyboard.sh
• diagnose-f5-issue.sh
• diagnose-libreoffice-show.sh
• test-aggressive-focus.sh
• And more...
❌ Old Documentation (~15 files)
• AUTO_ADVANCE_QUICKREF.txt
• AUTO_ADVANCE_V3_QUICKREF.txt
• SLIDESHOW_FIX_QUICKREF.txt
• UTC_FIX_QUICKREF.txt
• IMPRESSIVE_SOLUTION.txt
• src/AUTO_ADVANCE_V3.md
• src/SLIDES_NOT_ADVANCING_FIX.md
• And more...
╔════════════════════════════════════════════════════════════════╗
║ HOW THE SOLUTION WORKS ║
╚════════════════════════════════════════════════════════════════╝
1⃣ PPTX FILE RECEIVED
2⃣ LibreOffice (headless) → Converts to PDF
3⃣ Impressive Presenter → Displays with:
• --auto 10 ............... Auto-advance (10s per slide)
• --wrap .................. Loop infinitely OR
• --autoquit .............. Exit after last slide
• --fullscreen ............ Fullscreen kiosk mode
• --nooverview ............ No slide overview
📊 EVENT JSON EXAMPLE (Loop Mode):
{
"presentation": {
"files": [{"name": "slides.pptx"}],
"auto_advance": true,
"slide_interval": 10,
"loop": true
}
}
╔════════════════════════════════════════════════════════════════╗
║ WHY THIS WORKS ║
╚════════════════════════════════════════════════════════════════╝
✅ Native auto-advance (no xdotool hacks needed)
✅ Built-in loop support (--wrap parameter)
✅ Built-in auto-quit (--autoquit parameter)
✅ Works reliably on Raspberry Pi
✅ Simple, maintainable code (~50 lines vs. 200+ with xdotool)
✅ No window management issues
✅ No timing dependencies
✅ Clean architecture
╔════════════════════════════════════════════════════════════════╗
║ QUICK START ║
╚════════════════════════════════════════════════════════════════╝
🧪 TEST THE SOLUTION:
./scripts/test-display-manager.sh
→ Choose option 2 (Create PRESENTATION test event)
🚀 START PRODUCTION:
Terminal 1: cd src && python3 simclient.py
Terminal 2: cd src && python3 display_manager.py
📖 READ DOCUMENTATION:
Start with: README.md (comprehensive guide)
Deep dive: IMPRESSIVE_INTEGRATION.md
╔════════════════════════════════════════════════════════════════╗
║ STATISTICS ║
╚════════════════════════════════════════════════════════════════╝
Before Cleanup:
• ~60 scripts (including 5 failed versions of auto-advance)
• ~20 documentation files (fragmented, outdated)
• Multiple failed approaches mixed with working code
After Cleanup:
• 10 essential scripts (tested, working)
• 6 comprehensive documentation files
• 1 proven solution (Impressive)
Result:
📉 83% fewer scripts
📉 70% less documentation (but more comprehensive)
📈 100% focused on working solution
╔════════════════════════════════════════════════════════════════╗
║ STATUS ║
╚════════════════════════════════════════════════════════════════╝
Cleanup Date: October 1, 2025
Status: ✅ COMPLETE
Solution: ✅ PRODUCTION READY
Tested On: ✅ Raspberry Pi 5, Pi OS (Bookworm)
Documentation: ✅ COMPREHENSIVE
Code Quality: ✅ CLEAN & MAINTAINABLE
╔════════════════════════════════════════════════════════════════╗
║ JOURNEY TO SOLUTION ║
╚════════════════════════════════════════════════════════════════╝
Attempt 1: xdotool + LibreOffice v1 ................ ❌ Failed
Attempt 2: xdotool + LibreOffice v2 (focus fix) .... ❌ Failed
Attempt 3: xdotool + LibreOffice v3 (end detect) ... ❌ Failed
Attempt 4: xdotool + LibreOffice v4 (active win) ... ❌ Failed
Attempt 5: xdotool + LibreOffice v5 (hybrid) ....... ❌ Failed
Attempt 6: Modified PPTX timings ................... ❌ Failed
Attempt 7: Video conversion ........................ ❌ Failed
Attempt 8: PDF + evince + xdotool .................. ❌ Failed
Attempt 9: PDF + Impressive ........................ ✅ SUCCESS!
Lessons Learned:
• Don't fight the system (window focus is hard)
• Use native features when available
• Simpler is better
• The right tool makes all the difference
╔════════════════════════════════════════════════════════════════╗
║ FINAL CHECKLIST ║
╚════════════════════════════════════════════════════════════════╝
✅ All obsolete files removed
✅ Working solution documented
✅ Test scripts validated
✅ README.md comprehensive and up-to-date
✅ IMPRESSIVE_INTEGRATION.md detailed
✅ Cleanup summary created
✅ Production ready
🎉 WORKSPACE IS CLEAN AND PRODUCTION-READY! 🎉
For next steps, see: README.md

View File

@@ -0,0 +1,36 @@
[Unit]
Description=Infoscreen Display Manager
Documentation=https://github.com/RobbStarkAustria/infoscreen_client_2025
After=network.target graphical.target
Wants=network-online.target
[Service]
Type=simple
User=olafn
Group=olafn
WorkingDirectory=/home/olafn/infoscreen-dev
Environment="DISPLAY=:0"
Environment="XAUTHORITY=/home/olafn/.Xauthority"
Environment="ENV=production"
# Start display manager
ExecStart=/home/olafn/infoscreen-dev/scripts/start-display-manager.sh
# Restart on failure
Restart=on-failure
RestartSec=10
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=infoscreen-display
# Security settings
NoNewPrivileges=true
PrivateTmp=true
# Resource limits
LimitNOFILE=65536
[Install]
WantedBy=graphical.target

View File

@@ -0,0 +1,163 @@
#!/bin/bash
# PDF Presentation with Auto-Advance
# Works for both native PDF and converted PPTX files
PDF_FILE="$1"
INTERVAL="${2:-10}"
LOOP="${3:-false}"
if [ -z "$PDF_FILE" ]; then
echo "Usage: $0 <file.pdf> [interval-seconds] [loop:true|false]"
exit 1
fi
if [ ! -f "$PDF_FILE" ]; then
echo "Error: File not found: $PDF_FILE"
exit 1
fi
echo "=========================================="
echo " PDF Presentation Mode"
echo "=========================================="
echo ""
echo "File: $(basename "$PDF_FILE")"
echo "Auto-advance: ${INTERVAL}s per slide"
echo "Loop: $LOOP"
echo ""
# Count pages in PDF
PAGE_COUNT=$(pdfinfo "$PDF_FILE" 2>/dev/null | grep "Pages:" | awk '{print $2}')
if [ -z "$PAGE_COUNT" ] || [ "$PAGE_COUNT" -eq 0 ]; then
echo "Warning: Could not determine page count, assuming 10 pages"
PAGE_COUNT=10
else
echo "Detected $PAGE_COUNT pages"
fi
echo ""
# Check for required tools
if ! command -v xdotool &> /dev/null; then
echo "Error: xdotool not installed"
echo "Install with: sudo apt-get install xdotool"
exit 1
fi
# Choose best PDF viewer (prefer Impressive for auto-advance)
PDF_VIEWER=""
PDF_VIEWER_CMD=""
if command -v impressive &> /dev/null; then
PDF_VIEWER="impressive"
PDF_VIEWER_CMD="impressive --fullscreen --nooverview --auto $INTERVAL"
if [ "$LOOP" = "true" ]; then
PDF_VIEWER_CMD="$PDF_VIEWER_CMD --wrap"
else
PDF_VIEWER_CMD="$PDF_VIEWER_CMD --autoquit"
fi
# Impressive handles auto-advance natively, no xdotool needed!
echo "Using Impressive (built-in auto-advance)..."
$PDF_VIEWER_CMD "$PDF_FILE"
exit 0
elif command -v evince &> /dev/null; then
PDF_VIEWER="evince"
PDF_VIEWER_CMD="evince --presentation"
elif command -v okular &> /dev/null; then
PDF_VIEWER="okular"
PDF_VIEWER_CMD="okular --presentation"
else
echo "Error: No suitable PDF viewer found"
echo "Install impressive: sudo apt-get install impressive"
exit 1
fi
echo "Using PDF viewer: $PDF_VIEWER"
echo "Starting presentation mode..."
echo ""
# Start PDF viewer in presentation mode
$PDF_VIEWER_CMD "$PDF_FILE" &
VIEWER_PID=$!
echo "Viewer PID: $VIEWER_PID"
echo "Waiting for viewer to start..."
sleep 5
# Verify it's still running
if ! ps -p $VIEWER_PID > /dev/null 2>&1; then
echo "Error: PDF viewer exited unexpectedly"
exit 1
fi
echo "Starting auto-advance..."
echo "Press Ctrl+C to stop"
echo ""
# Auto-advance loop
CURRENT_PAGE=1
MAX_LOOPS=0
if [ "$LOOP" = "false" ]; then
# Calculate total slides to advance (pages - 1, since we start on page 1)
MAX_ADVANCES=$((PAGE_COUNT - 1))
else
# Infinite loop
MAX_ADVANCES=999999
fi
ADVANCE_COUNT=0
while ps -p $VIEWER_PID > /dev/null 2>&1 && [ $ADVANCE_COUNT -lt $MAX_ADVANCES ]; do
sleep $INTERVAL
# Find the PDF viewer window
VIEWER_WINDOW=$(xdotool search --pid $VIEWER_PID 2>/dev/null | tail -1)
if [ -z "$VIEWER_WINDOW" ]; then
# Fallback: search by window name
VIEWER_WINDOW=$(xdotool search --name "$(basename "$PDF_FILE")" 2>/dev/null | tail -1)
fi
if [ -n "$VIEWER_WINDOW" ]; then
# Ensure window is focused
xdotool windowactivate --sync "$VIEWER_WINDOW" 2>/dev/null
sleep 0.2
# Send Right Arrow or Page Down (both work in most PDF viewers)
xdotool key --clearmodifiers --window "$VIEWER_WINDOW" Right
CURRENT_PAGE=$((CURRENT_PAGE + 1))
ADVANCE_COUNT=$((ADVANCE_COUNT + 1))
echo "[$(date '+%H:%M:%S')] Advanced to page $CURRENT_PAGE"
# Check if we've reached the end
if [ $CURRENT_PAGE -gt $PAGE_COUNT ]; then
if [ "$LOOP" = "true" ]; then
echo "Reached end, looping back to start..."
CURRENT_PAGE=1
else
echo ""
echo "Reached end of presentation (page $PAGE_COUNT)"
echo "Keeping viewer open..."
# Keep viewer running, just stop advancing
wait $VIEWER_PID
exit 0
fi
fi
else
# Fallback: send key to active window
xdotool key --clearmodifiers Right
CURRENT_PAGE=$((CURRENT_PAGE + 1))
ADVANCE_COUNT=$((ADVANCE_COUNT + 1))
echo "[$(date '+%H:%M:%S')] Advanced (fallback) #$ADVANCE_COUNT"
fi
done
echo ""
echo "Presentation ended"
exit 0

5
scripts/start-dev.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
cd "$(dirname "$0")/.."
source venv/bin/activate
export $(cat .env | xargs)
python3 src/simclient.py

View File

@@ -0,0 +1,43 @@
#!/bin/bash
# Start Display Manager - Controls display software for infoscreen events
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
VENV_PATH="$PROJECT_ROOT/venv"
DISPLAY_MANAGER="$PROJECT_ROOT/src/display_manager.py"
echo "🖥️ Starting Display Manager..."
echo "Project root: $PROJECT_ROOT"
# Check if virtual environment exists
if [ ! -d "$VENV_PATH" ]; then
echo "❌ Virtual environment not found at: $VENV_PATH"
echo "Please create it with: python3 -m venv venv"
exit 1
fi
# Activate virtual environment
source "$VENV_PATH/bin/activate"
# Check if display_manager.py exists
if [ ! -f "$DISPLAY_MANAGER" ]; then
echo "❌ Display manager not found at: $DISPLAY_MANAGER"
exit 1
fi
# Make sure DISPLAY is set (required for GUI applications)
if [ -z "$DISPLAY" ]; then
export DISPLAY=:0
echo "📺 DISPLAY not set, using: $DISPLAY"
fi
# Check if we're in development or production
ENV="${ENV:-development}"
echo "Environment: $ENV"
# Start display manager
echo "Starting display manager..."
echo "---"
python3 "$DISPLAY_MANAGER"

210
scripts/test-display-manager.sh Executable file
View File

@@ -0,0 +1,210 @@
#!/bin/bash
# Test Display Manager functionality
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
EVENT_FILE="$PROJECT_ROOT/src/current_event.json"
echo "🧪 Testing Display Manager"
echo "========================="
echo ""
# Function to create test event
create_test_event() {
local event_type=$1
echo "📝 Creating test event: $event_type"
case $event_type in
"presentation")
cat > "$EVENT_FILE" <<EOF
{
"id": 999,
"title": "Test Presentation with Impressive",
"start": "2025-01-01 00:00:00",
"end": "2025-12-31 23:59:59",
"presentation": {
"type": "slideshow",
"files": [
{
"name": "LPUV4I_Folien_Nowitzki_Bewertungskriterien.pptx",
"url": "http://example.com/test.pptx"
}
],
"auto_advance": true,
"slide_interval": 5,
"loop": true
}
}
EOF
;;
"webpage")
cat > "$EVENT_FILE" <<EOF
{
"id": 998,
"title": "Test Webpage",
"start": "2025-01-01 00:00:00",
"end": "2025-12-31 23:59:59",
"web": {
"url": "https://www.wikipedia.org"
}
}
EOF
;;
"video")
cat > "$EVENT_FILE" <<EOF
{
"id": 997,
"title": "Test Video",
"start": "2025-01-01 00:00:00",
"end": "2025-12-31 23:59:59",
"video": {
"url": "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
"loop": false
}
}
EOF
;;
"none")
echo "📝 Removing event file (no active event)"
rm -f "$EVENT_FILE"
;;
esac
if [ -f "$EVENT_FILE" ]; then
echo "✅ Event file created:"
cat "$EVENT_FILE"
fi
echo ""
}
# Function to check if display manager is running
check_display_manager() {
if pgrep -f "display_manager.py" > /dev/null; then
echo "✅ Display Manager is running"
echo " PID: $(pgrep -f display_manager.py)"
return 0
else
echo "❌ Display Manager is NOT running"
return 1
fi
}
# Function to check for display processes
check_display_processes() {
echo "🔍 Active display processes:"
# Check for LibreOffice
if pgrep -f "libreoffice.*impress" > /dev/null; then
echo " 📊 LibreOffice Impress: PID $(pgrep -f 'libreoffice.*impress')"
fi
# Check for browsers
if pgrep -f "chromium.*kiosk" > /dev/null; then
echo " 🌐 Chromium (kiosk): PID $(pgrep -f 'chromium.*kiosk')"
fi
# Check for video players
if pgrep -f "vlc" > /dev/null; then
echo " 🎬 VLC: PID $(pgrep -f 'vlc')"
fi
if pgrep -f "mpv" > /dev/null; then
echo " 🎬 MPV: PID $(pgrep -f 'mpv')"
fi
# Check for PDF viewers
if pgrep -f "evince" > /dev/null; then
echo " 📄 Evince: PID $(pgrep -f 'evince')"
fi
# Check for Impressive
if pgrep -f "impressive" > /dev/null; then
echo " 📊 Impressive: PID $(pgrep -f 'impressive')"
fi
echo ""
}
# Main menu
echo "Display Manager Test Menu"
echo "========================="
echo ""
echo "What would you like to test?"
echo "1) Check Display Manager status"
echo "2) Create PRESENTATION test event"
echo "3) Create WEBPAGE test event"
echo "4) Create VIDEO test event"
echo "5) Remove event (no display)"
echo "6) Check active display processes"
echo "7) View current event file"
echo "8) Interactive test (cycle through events)"
echo "9) Exit"
echo ""
read -p "Enter choice [1-9]: " choice
case $choice in
1)
check_display_manager
check_display_processes
;;
2)
create_test_event "presentation"
echo "⏱️ Display Manager will pick this up within 5 seconds..."
;;
3)
create_test_event "webpage"
echo "⏱️ Display Manager will pick this up within 5 seconds..."
;;
4)
create_test_event "video"
echo "⏱️ Display Manager will pick this up within 5 seconds..."
;;
5)
create_test_event "none"
echo "⏱️ Display Manager will stop display within 5 seconds..."
;;
6)
check_display_manager
check_display_processes
;;
7)
if [ -f "$EVENT_FILE" ]; then
echo "📄 Current event file:"
cat "$EVENT_FILE"
else
echo "❌ No event file found"
fi
;;
8)
echo "🔄 Interactive test - cycling through event types"
echo " Display Manager must be running for this test!"
echo ""
check_display_manager || exit 1
echo "1⃣ Testing PRESENTATION (10 seconds)..."
create_test_event "presentation"
sleep 10
echo "2⃣ Testing WEBPAGE (10 seconds)..."
create_test_event "webpage"
sleep 10
echo "3⃣ Testing NO EVENT (5 seconds)..."
create_test_event "none"
sleep 5
echo "4⃣ Back to PRESENTATION..."
create_test_event "presentation"
echo "✅ Interactive test complete!"
;;
9)
echo "👋 Goodbye!"
exit 0
;;
*)
echo "❌ Invalid choice"
exit 1
;;
esac

92
scripts/test-impressive-loop.sh Executable file
View File

@@ -0,0 +1,92 @@
#!/bin/bash
# Test Impressive with LOOP mode (for events/kiosks)
echo "=========================================="
echo " Test: Impressive with LOOP"
echo "=========================================="
echo ""
echo "This tests kiosk/event mode where the"
echo "presentation loops continuously."
echo ""
cd ~/infoscreen-dev
PPTX=$(find src/presentation -name "*.pptx" | head -1)
if [ -z "$PPTX" ]; then
echo "Error: No PPTX file found"
exit 1
fi
# Convert to PDF
PDF="/tmp/impressive_loop_test.pdf"
echo "Converting PPTX to PDF..."
libreoffice --headless --convert-to pdf --outdir /tmp "$PPTX" > /dev/null 2>&1
PPTX_BASE=$(basename "$PPTX" .pptx)
ACTUAL_PDF="/tmp/${PPTX_BASE}.pdf"
if [ -f "$ACTUAL_PDF" ]; then
cp "$ACTUAL_PDF" "$PDF"
fi
if [ ! -f "$PDF" ]; then
echo "Error: PDF conversion failed"
exit 1
fi
PAGE_COUNT=$(pdfinfo "$PDF" 2>/dev/null | grep "Pages:" | awk '{print $2}')
echo "[OK] PDF ready: $PAGE_COUNT pages"
echo ""
echo "Starting Impressive in LOOP mode..."
echo ""
echo "Settings:"
echo " - Auto-advance: 3 seconds per slide"
echo " - Loop: YES (--wrap)"
echo " - Will go: Slide 1 → 2 → 3 → 4 → 5 → 1 → 2 → ..."
echo ""
echo "What to watch for:"
echo " ✅ Advances through all $PAGE_COUNT slides"
echo " ✅ After slide $PAGE_COUNT, goes back to slide 1"
echo " ✅ Continues looping forever"
echo ""
echo "Press 'Q' or Escape to quit"
echo ""
sleep 2
# Start with --wrap for loop mode
impressive \
--fullscreen \
--nooverview \
--auto 3 \
--wrap \
--nologo \
"$PDF"
echo ""
echo "=========================================="
echo " Test Complete"
echo "=========================================="
echo ""
read -p "Did it loop back to slide 1 after slide $PAGE_COUNT? (y/n): " worked
if [ "$worked" = "y" ]; then
echo ""
echo "✅ Perfect! Impressive loop mode works!"
echo ""
echo "This is ideal for:"
echo " - Event displays (loop presentation during event)"
echo " - Kiosk mode (continuous display)"
echo " - Information screens (repeat content)"
echo ""
echo "Event JSON should use:"
echo ' "loop": true'
else
echo ""
echo "Something unexpected?"
read -p "What happened?: " issue
echo "Issue: $issue"
fi
rm -f "$PDF"

115
scripts/test-impressive.sh Executable file
View File

@@ -0,0 +1,115 @@
#!/bin/bash
# Test Impressive - Python PDF presenter with native auto-advance
echo "=========================================="
echo " Test: Impressive PDF Presenter"
echo "=========================================="
echo ""
echo "Impressive is a Python-based PDF presenter with"
echo "BUILT-IN auto-advance. No xdotool needed!"
echo ""
# Check if impressive is installed
if ! command -v impressive &> /dev/null; then
echo "Impressive not installed. Installing..."
echo ""
sudo apt-get update
sudo apt-get install -y impressive
if [ $? -ne 0 ]; then
echo "Error: Could not install impressive"
exit 1
fi
fi
echo "Impressive installed: $(which impressive)"
echo ""
# Convert PPTX to PDF
cd ~/infoscreen-dev
PPTX=$(find src/presentation -name "*.pptx" | head -1)
if [ -z "$PPTX" ]; then
echo "Error: No PPTX file found"
exit 1
fi
PDF="/tmp/impressive_test.pdf"
echo "Converting PPTX to PDF..."
libreoffice --headless --convert-to pdf --outdir /tmp "$PPTX" > /dev/null 2>&1
PPTX_BASE=$(basename "$PPTX" .pptx)
ACTUAL_PDF="/tmp/${PPTX_BASE}.pdf"
if [ -f "$ACTUAL_PDF" ]; then
cp "$ACTUAL_PDF" "$PDF"
fi
if [ ! -f "$PDF" ]; then
echo "Error: PDF conversion failed"
exit 1
fi
PAGE_COUNT=$(pdfinfo "$PDF" 2>/dev/null | grep "Pages:" | awk '{print $2}')
echo "[OK] PDF ready: $PAGE_COUNT pages"
echo ""
echo "Starting Impressive with 3-second auto-advance..."
echo ""
echo "Impressive features:"
echo " - Native auto-advance (no xdotool!)"
echo " - Fullscreen by default"
echo " - Professional transitions"
echo " - Loop support"
echo ""
echo "Controls:"
echo " Right Arrow / Space = Next slide"
echo " Left Arrow = Previous slide"
echo " Q / Escape = Quit"
echo ""
sleep 2
# Start Impressive with auto-advance
# --auto 3 = auto-advance every 3 seconds
# --fullscreen = fullscreen mode
# --nooverview = skip overview at start
# --autoquit = quit after last slide (for non-loop mode)
impressive \
--fullscreen \
--auto 3 \
--nooverview \
--autoquit \
"$PDF"
echo ""
echo "=========================================="
echo " Test Complete"
echo "=========================================="
echo ""
read -p "Did it work perfectly? (y/n): " worked
if [ "$worked" = "y" ]; then
echo ""
echo "✅ EXCELLENT! Impressive is your solution!"
echo ""
echo "Why Impressive is perfect:"
echo " ✅ Built-in auto-advance (no xdotool hacks)"
echo " ✅ Reliable on Raspberry Pi"
echo " ✅ Professional presenter tool"
echo " ✅ Supports loop mode natively"
echo " ✅ Fast and lightweight"
echo ""
echo "Next steps:"
echo " 1. Update Display Manager to use Impressive"
echo " 2. Simple command: impressive --auto N --fullscreen file.pdf"
echo " 3. Works for both PPTX (convert to PDF) and PDF files"
else
echo ""
echo "What didn't work?"
read -p "Describe the issue: " issue
echo "Issue: $issue"
fi
rm -f "$PDF"

9
scripts/test-mqtt.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
source "$(dirname "$0")/../.env"
echo "Testing MQTT connection to $MQTT_BROKER:$MQTT_PORT"
echo "Publishing test message..."
mosquitto_pub -h "$MQTT_BROKER" -p "$MQTT_PORT" -t "infoscreen/test" -m "Hello from Pi development setup"
echo "Subscribing to test topic (press Ctrl+C to stop)..."
mosquitto_sub -h "$MQTT_BROKER" -p "$MQTT_PORT" -t "infoscreen/test"

174
scripts/test-progress-bars.sh Executable file
View File

@@ -0,0 +1,174 @@
#!/bin/bash
# Test script for page_progress and auto_progress features in presentations
echo "=========================================="
echo "Progress Bar Features Test"
echo "=========================================="
echo ""
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Check if MQTT broker is configured
if [ -f ../.env ]; then
source ../.env
MQTT_HOST=${MQTT_BROKER:-localhost}
else
MQTT_HOST=localhost
fi
echo -e "${YELLOW}Using MQTT broker: ${MQTT_HOST}${NC}"
echo ""
# Menu for testing different progress bar configurations
echo "Select a test configuration:"
echo ""
echo "1. No progress bars (default)"
echo "2. Page progress only (overall position)"
echo "3. Auto-progress only (per-page countdown)"
echo "4. Both progress bars (maximum feedback)"
echo ""
read -p "Enter choice (1-4): " choice
case $choice in
1)
PAGE_PROG=false
AUTO_PROG=false
DESC="No progress bars"
;;
2)
PAGE_PROG=true
AUTO_PROG=false
DESC="Page progress only (--page-progress)"
;;
3)
PAGE_PROG=false
AUTO_PROG=true
DESC="Auto-progress only (--auto-progress)"
;;
4)
PAGE_PROG=true
AUTO_PROG=true
DESC="Both progress bars"
;;
*)
echo "Invalid choice"
exit 1
;;
esac
echo ""
echo -e "${BLUE}Configuration: $DESC${NC}"
echo " page_progress: $PAGE_PROG"
echo " auto_progress: $AUTO_PROG"
echo ""
# Create test event with progress bar settings
TEST_EVENT=$(cat <<EOF
{
"id": 888,
"group_id": 2,
"title": "Progress Bar Test Event",
"start": "2025-01-01T00:00:00+00:00",
"end": "2025-12-31T23:59:59+00:00",
"presentation": {
"type": "slideshow",
"auto_advance": true,
"slide_interval": 5,
"loop": true,
"page_progress": $PAGE_PROG,
"auto_progress": $AUTO_PROG,
"files": [
{
"name": "Wissenschaftliches Arbeiten Literaturrecherche.pdf",
"url": "http://server:8000/api/files/test.pdf"
}
]
}
}
EOF
)
echo "Sending test event to MQTT..."
echo ""
mosquitto_pub -h "$MQTT_HOST" -t "infoscreen/events/2" -m "$TEST_EVENT"
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ Event sent successfully${NC}"
else
echo -e "${RED}✗ Failed to send event${NC}"
exit 1
fi
echo ""
echo "Waiting for Display Manager to process event..."
sleep 3
echo ""
echo "Checking Display Manager log for progress bar settings..."
echo ""
LOG_FILE="../logs/display_manager.log"
if [ -f "$LOG_FILE" ]; then
echo -e "${YELLOW}Recent log entries:${NC}"
tail -30 "$LOG_FILE" | grep -E "(progress|Progress|Impressive)" | tail -10
else
echo -e "${YELLOW}Log file not found: $LOG_FILE${NC}"
fi
echo ""
echo "=========================================="
echo "Expected Behavior"
echo "=========================================="
echo ""
case $choice in
1)
echo "• No progress indicators visible"
echo "• Clean presentation display"
;;
2)
echo "• Progress bar at bottom of screen"
echo "• Shows current position: [=====> ] 50%"
echo "• Updates as slides change"
;;
3)
echo "• Countdown bar for each slide"
echo "• Shows time remaining until next slide"
echo "• Resets on each slide transition"
;;
4)
echo "• Overall progress bar at bottom"
echo "• Per-slide countdown overlay"
echo "• Both indicators update in real-time"
;;
esac
echo ""
echo "=========================================="
echo "Impressive Command Options"
echo "=========================================="
echo ""
echo "The Display Manager translates event fields to Impressive options:"
echo ""
echo "Event Field → Impressive Option"
echo "-------------------- → ------------------"
echo "page_progress: true → --page-progress (or -q)"
echo "auto_progress: true → --auto-progress (or -k)"
echo ""
echo "Check the impressive.out.log for the exact command used:"
echo " tail -20 ../logs/impressive.out.log"
echo ""
echo -e "${GREEN}Test complete!${NC}"
echo ""
echo "Tips:"
echo " • Press 'q' to quit the presentation"
echo " • Run this script again to test different configurations"
echo " • Check logs/display_manager.log for detailed output"
echo ""

143
scripts/test-scheduler-fields.sh Executable file
View File

@@ -0,0 +1,143 @@
#!/bin/bash
# Test script to verify scheduler fields (page_progress, auto_progress) are preserved
echo "=========================================="
echo "Scheduler Fields Preservation Test"
echo "=========================================="
echo ""
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Check if MQTT broker is configured
if [ -f ../.env ]; then
source ../.env
MQTT_HOST=${MQTT_BROKER:-localhost}
else
MQTT_HOST=localhost
fi
echo -e "${YELLOW}Using MQTT broker: ${MQTT_HOST}${NC}"
echo ""
# Test event with scheduler fields
TEST_EVENT='{
"id": 999,
"occurrence_of_id": 999,
"group_id": 2,
"title": "Test Event with Scheduler Fields",
"start": "2025-01-01T10:00:00+00:00",
"end": "2025-12-31T23:59:59+00:00",
"recurrence_rule": "FREQ=DAILY",
"recurrence_end": "2025-12-31T23:59:59",
"presentation": {
"type": "slideshow",
"auto_advance": true,
"slide_interval": 10,
"page_progress": true,
"auto_progress": true,
"files": [
{
"name": "test.pdf",
"url": "http://server:8000/api/files/test.pdf"
}
]
}
}'
echo "1. Sending test event with scheduler fields to MQTT..."
echo ""
echo "Event contains:"
echo " - page_progress: true (show overall progress bar)"
echo " - auto_progress: true (show per-page countdown)"
echo " - recurrence_rule: FREQ=DAILY"
echo " - occurrence_of_id: 999"
echo ""
mosquitto_pub -h "$MQTT_HOST" -t "infoscreen/events/2" -m "$TEST_EVENT"
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ Event sent successfully${NC}"
else
echo -e "${RED}✗ Failed to send event${NC}"
exit 1
fi
echo ""
echo "2. Waiting for event to be processed..."
sleep 2
echo ""
echo "3. Checking current_event.json for scheduler fields..."
echo ""
EVENT_FILE="../src/current_event.json"
if [ ! -f "$EVENT_FILE" ]; then
echo -e "${RED}✗ Event file not found: $EVENT_FILE${NC}"
exit 1
fi
echo -e "${YELLOW}Current event content:${NC}"
cat "$EVENT_FILE" | jq '.'
echo ""
# Check for specific fields
echo "4. Verifying scheduler fields are preserved..."
echo ""
PAGE_PROGRESS=$(cat "$EVENT_FILE" | jq -r '.[0].page_progress // .page_progress // "not_found"')
AUTO_PROGRESS=$(cat "$EVENT_FILE" | jq -r '.[0].auto_progress // .auto_progress // "not_found"')
OCCURRENCE_ID=$(cat "$EVENT_FILE" | jq -r '.[0].occurrence_of_id // .occurrence_of_id // "not_found"')
RECURRENCE=$(cat "$EVENT_FILE" | jq -r '.[0].recurrence_rule // .recurrence_rule // "not_found"')
if [ "$PAGE_PROGRESS" = "true" ]; then
echo -e "${GREEN}✓ page_progress preserved: $PAGE_PROGRESS${NC}"
else
echo -e "${RED}✗ page_progress missing or incorrect: $PAGE_PROGRESS${NC}"
fi
if [ "$AUTO_PROGRESS" = "true" ]; then
echo -e "${GREEN}✓ auto_progress preserved: $AUTO_PROGRESS${NC}"
else
echo -e "${RED}✗ auto_progress missing or incorrect: $AUTO_PROGRESS${NC}"
fi
if [ "$OCCURRENCE_ID" = "999" ]; then
echo -e "${GREEN}✓ occurrence_of_id preserved: $OCCURRENCE_ID${NC}"
else
echo -e "${RED}✗ occurrence_of_id missing or incorrect: $OCCURRENCE_ID${NC}"
fi
if [ "$RECURRENCE" = "FREQ=DAILY" ]; then
echo -e "${GREEN}✓ recurrence_rule preserved: $RECURRENCE${NC}"
else
echo -e "${RED}✗ recurrence_rule missing or incorrect: $RECURRENCE${NC}"
fi
echo ""
echo "5. Checking simclient log for debug messages..."
echo ""
LOG_FILE="../logs/simclient.log"
if [ -f "$LOG_FILE" ]; then
echo -e "${YELLOW}Recent log entries mentioning scheduler fields:${NC}"
tail -20 "$LOG_FILE" | grep -E "page_progress|auto_progress" || echo " (no debug messages - set LOG_LEVEL=DEBUG to see them)"
else
echo -e "${YELLOW}Log file not found: $LOG_FILE${NC}"
fi
echo ""
echo "=========================================="
echo -e "${GREEN}Test Complete${NC}"
echo "=========================================="
echo ""
echo "Summary:"
echo " - Scheduler fields (page_progress, auto_progress) should be preserved in current_event.json"
echo " - All metadata from the scheduler is automatically stored without filtering"
echo " - Set LOG_LEVEL=DEBUG in .env to see field logging in simclient.log"
echo ""

27
scripts/test-screenshot.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/bash
SCREENSHOT_DIR="$(dirname "$0")/../screenshots"
mkdir -p "$SCREENSHOT_DIR"
# Ensure DISPLAY is set for screenshot capture
if [ -z "$DISPLAY" ]; then
export DISPLAY=:0
fi
# Test screenshot capture
echo "Testing screenshot capture with DISPLAY=$DISPLAY"
if scrot "$SCREENSHOT_DIR/test_$(date +%Y%m%d_%H%M%S).png" 2>/dev/null; then
echo "✅ Screenshot captured successfully"
echo "📁 Screenshot saved to: $SCREENSHOT_DIR"
ls -la "$SCREENSHOT_DIR"/test_*.png | tail -1
else
echo "❌ Screenshot failed with scrot, trying imagemagick..."
if import -window root "$SCREENSHOT_DIR/test_$(date +%Y%m%d_%H%M%S).png" 2>/dev/null; then
echo "✅ Screenshot captured with imagemagick"
echo "📁 Screenshot saved to: $SCREENSHOT_DIR"
ls -la "$SCREENSHOT_DIR"/test_*.png | tail -1
else
echo "❌ Screenshot capture failed. Check DISPLAY variable and X11 access."
echo "💡 Try: export DISPLAY=:0"
echo "💡 Or run from local Pi terminal instead of SSH"
fi
fi

149
scripts/test-utc-timestamps.sh Executable file
View File

@@ -0,0 +1,149 @@
#!/bin/bash
# Test UTC timestamp handling in Display Manager
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
EVENT_FILE="$PROJECT_ROOT/src/current_event.json"
echo "🕐 UTC Timestamp Test for Display Manager"
echo "=========================================="
echo ""
# Get current UTC and local times
UTC_NOW=$(date -u '+%Y-%m-%d %H:%M:%S')
LOCAL_NOW=$(date '+%Y-%m-%d %H:%M:%S')
TIMEZONE=$(date +%Z)
UTC_OFFSET=$(date +%z)
echo "📅 Current Time Information:"
echo " UTC Time: $UTC_NOW"
echo " Local Time: $LOCAL_NOW"
echo " Timezone: $TIMEZONE (UTC$UTC_OFFSET)"
echo ""
# Calculate timestamps for testing
# Start: 1 minute ago (UTC)
START_TIME=$(date -u -d '1 minute ago' '+%Y-%m-%d %H:%M:%S')
# End: 10 minutes from now (UTC)
END_TIME=$(date -u -d '10 minutes' '+%Y-%m-%d %H:%M:%S')
echo "🧪 Test Scenarios:"
echo ""
echo "1⃣ Test Active Event (should display NOW)"
echo " Create event with:"
echo " - Start: $START_TIME UTC (1 minute ago)"
echo " - End: $END_TIME UTC (in 10 minutes)"
echo ""
read -p "Press Enter to create test event..."
cat > "$EVENT_FILE" <<EOF
{
"id": 998,
"title": "UTC Test - Active Event",
"start": "$START_TIME",
"end": "$END_TIME",
"web": {
"url": "https://www.timeanddate.com/worldclock/timezone/utc"
}
}
EOF
echo "✅ Created test event:"
cat "$EVENT_FILE" | jq .
echo ""
echo "⏱️ Display Manager should detect this as ACTIVE and start displaying"
echo " Check the logs with: tail -f logs/display_manager.log"
echo ""
read -p "Press Enter to continue to next test..."
echo ""
# Test 2: Event in the future
FUTURE_START=$(date -u -d '5 minutes' '+%Y-%m-%d %H:%M:%S')
FUTURE_END=$(date -u -d '15 minutes' '+%Y-%m-%d %H:%M:%S')
echo "2⃣ Test Future Event (should NOT display yet)"
echo " Create event with:"
echo " - Start: $FUTURE_START UTC (in 5 minutes)"
echo " - End: $FUTURE_END UTC (in 15 minutes)"
echo ""
read -p "Press Enter to create future event..."
cat > "$EVENT_FILE" <<EOF
{
"id": 997,
"title": "UTC Test - Future Event",
"start": "$FUTURE_START",
"end": "$FUTURE_END",
"web": {
"url": "https://www.timeanddate.com/worldclock/timezone/utc"
}
}
EOF
echo "✅ Created future event:"
cat "$EVENT_FILE" | jq .
echo ""
echo "⏱️ Display Manager should detect this as NOT ACTIVE YET"
echo " It should stop any current display"
echo " Check logs: tail -f logs/display_manager.log"
echo ""
read -p "Press Enter to continue to next test..."
echo ""
# Test 3: Event in the past
PAST_START=$(date -u -d '30 minutes ago' '+%Y-%m-%d %H:%M:%S')
PAST_END=$(date -u -d '20 minutes ago' '+%Y-%m-%d %H:%M:%S')
echo "3⃣ Test Past Event (should NOT display - already ended)"
echo " Create event with:"
echo " - Start: $PAST_START UTC (30 minutes ago)"
echo " - End: $PAST_END UTC (20 minutes ago)"
echo ""
read -p "Press Enter to create past event..."
cat > "$EVENT_FILE" <<EOF
{
"id": 996,
"title": "UTC Test - Past Event",
"start": "$PAST_START",
"end": "$PAST_END",
"web": {
"url": "https://www.timeanddate.com/worldclock/timezone/utc"
}
}
EOF
echo "✅ Created past event:"
cat "$EVENT_FILE" | jq .
echo ""
echo "⏱️ Display Manager should detect this as ALREADY ENDED"
echo " It should stop any current display"
echo " Check logs: tail -f logs/display_manager.log"
echo ""
read -p "Press Enter to clean up..."
# Clean up
rm -f "$EVENT_FILE"
echo ""
echo "🧹 Cleaned up test event file"
echo ""
echo "📊 Summary:"
echo "==========="
echo ""
echo "✅ Test 1: Active event (past start, future end) → Should DISPLAY"
echo "✅ Test 2: Future event (future start, future end) → Should NOT display yet"
echo "✅ Test 3: Past event (past start, past end) → Should NOT display"
echo ""
echo "📝 The Display Manager should now correctly handle UTC timestamps!"
echo ""
echo "💡 Tips for debugging:"
echo " • Watch logs: tail -f logs/display_manager.log"
echo " • Check timezone: date; date -u"
echo " • The Display Manager logs show both UTC and comparison times"
echo ""

75
scripts/test_cdp.py Executable file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""Quick CDP tester: lists targets and evaluates a small script in the first target.
Usage:
python3 scripts/test_cdp.py
Requires: requests, websocket-client
"""
import requests
import websocket
import json
def main():
try:
resp = requests.get('http://127.0.0.1:9222/json', timeout=3)
tabs = resp.json()
except Exception as e:
print('ERROR: could not fetch http://127.0.0.1:9222/json ->', e)
return
if not tabs:
print('No targets returned')
return
print('Found targets:')
for i, t in enumerate(tabs):
print(i, t.get('type'), t.get('url'), '-', t.get('title'))
target = tabs[0]
ws_url = target.get('webSocketDebuggerUrl')
print('\nUsing target[0]:', target.get('url'))
print('webSocketDebuggerUrl:', ws_url)
if not ws_url:
print('No webSocketDebuggerUrl for target')
return
try:
# Some Chromium builds require an Origin header to avoid 403 during the websocket handshake
try:
ws = websocket.create_connection(ws_url, timeout=5, header=["Origin: http://127.0.0.1"])
except TypeError:
# older websocket-client accepts origin kw instead
ws = websocket.create_connection(ws_url, timeout=5, origin="http://127.0.0.1")
except Exception as e:
print('ERROR: could not open websocket to', ws_url, '->', e)
return
idn = 1
# Enable runtime
msg = {'id': idn, 'method': 'Runtime.enable'}
ws.send(json.dumps(msg))
idn += 1
try:
print('Runtime.enable =>', ws.recv())
except Exception as e:
print('No response to Runtime.enable:', e)
# Evaluate a script that logs and returns a value
script = "console.log('cdp-test-log'); 12345"
msg = {'id': idn, 'method': 'Runtime.evaluate', 'params': {'expression': script, 'returnByValue': True}}
ws.send(json.dumps(msg))
idn += 1
try:
resp = ws.recv()
print('Runtime.evaluate =>', resp)
except Exception as e:
print('No response to Runtime.evaluate:', e)
ws.close()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
# scripts/test_cdp_origins.py
# Tries several Origin headers when connecting to Chromium DevTools websocket
import requests, websocket, json, sys, time
def try_origin(ws_url, origin_header):
try:
# websocket-client accepts header=[...] or origin=... depending on version
try:
ws = websocket.create_connection(ws_url, timeout=5, header=[f"Origin: {origin_header}"])
except TypeError:
ws = websocket.create_connection(ws_url, timeout=5, origin=origin_header)
return True, "connected"
except Exception as e:
return False, str(e)
def main():
try:
tabs = requests.get('http://127.0.0.1:9222/json', timeout=3).json()
except Exception as e:
print("ERROR: could not fetch DevTools json:", e)
return
if not tabs:
print("No DevTools targets found")
return
target = tabs[0]
ws_url = target.get('webSocketDebuggerUrl')
print("Using target url:", target.get('url'))
print("DevTools websocket:", ws_url)
if not ws_url:
print("No webSocketDebuggerUrl found in target")
return
# candidate origins to try
candidates = [
"http://127.0.0.1",
"http://localhost",
"https://www.computerbase.de", # page origin (use the exact scheme + host of the page)
"https://computerbase.de",
"null",
"chrome-devtools://devtools",
""
]
print("\nTrying origins (may need to match page origin exactly):")
for orig in candidates:
ok, msg = try_origin(ws_url, orig)
print(f"Origin={repr(orig):30} -> {ok} : {msg}")
# If one of them connected, try to send a Runtime.evaluate to confirm:
# (try the first that succeeded)
for orig in candidates:
try:
try:
ws = websocket.create_connection(ws_url, timeout=5, header=[f"Origin: {orig}"])
except TypeError:
ws = websocket.create_connection(ws_url, timeout=5, origin=orig)
print("\nConnected with origin:", orig)
msg_id = 1
payload = {"id": msg_id, "method": "Runtime.enable"}
ws.send(json.dumps(payload))
print("Runtime.enable ->", ws.recv())
msg_id += 1
payload = {"id": msg_id, "method": "Runtime.evaluate", "params": {"expression": "1+2", "returnByValue": True}}
ws.send(json.dumps(payload))
print("Runtime.evaluate ->", ws.recv())
ws.close()
break
except Exception as e:
# try next origin
continue
if __name__ == "__main__":
main()

23
src/.dockerignore Normal file
View File

@@ -0,0 +1,23 @@
# Docker ignore file for production builds
.git
.gitignore
*.md
.env*
.vscode/
.devcontainer/
dev-workflow.sh
pi-dev-setup.sh
# Development artifacts
screenshots/
logs/
config/
presentation/
# Python cache
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.so

View File

@@ -0,0 +1,23 @@
# Production Environment Template
# Copy this file to .env and customize for your production deployment
# Client Configuration
CLIENT_ID=client-001
VERSION=latest
# MQTT Broker
MQTT_BROKER=192.168.1.100
MQTT_PORT=1883
# Timing (production values)
HEARTBEAT_INTERVAL=60
SCREENSHOT_INTERVAL=300
# File/API Server (used to download presentation files)
# Defaults to the same host as MQTT_BROKER, port 8000, scheme http.
# If incoming event URLs use host 'server' (or are host-less), simclient rewrites them to this server.
FILE_SERVER_HOST= # optional: e.g., 192.168.1.100
FILE_SERVER_PORT=8000 # default API port
# http or https
FILE_SERVER_SCHEME=http
# FILE_SERVER_BASE_URL= # optional full override, e.g., https://api.example.com:443

27
src/.env.template Normal file
View File

@@ -0,0 +1,27 @@
# Infoscreen Client Configuration Template
# Copy this file to .env and adjust values for your setup
# Development Environment
ENV=development
DEBUG_MODE=1
LOG_LEVEL=DEBUG
# MQTT Broker Configuration
MQTT_BROKER=192.168.1.100 # Change to your MQTT server IP
MQTT_PORT=1883
# Timing Configuration (shorter intervals for development)
HEARTBEAT_INTERVAL=10 # Heartbeat frequency in seconds
SCREENSHOT_INTERVAL=30 # Screenshot capture frequency in seconds
# Display Manager
DISPLAY_CHECK_INTERVAL=5 # Display Manager event check frequency in seconds
# File/API Server (used to download presentation files)
# Defaults to the same host as MQTT_BROKER, port 8000, scheme http.
# If incoming event URLs use host 'server' (or are host-less), simclient rewrites them to this server.
FILE_SERVER_HOST= # optional: e.g., 192.168.1.100
FILE_SERVER_PORT=8000 # default API port
# http or https
FILE_SERVER_SCHEME=http
# FILE_SERVER_BASE_URL= # optional full override, e.g., http://192.168.1.100:8000

96
src/.gitignore vendored Normal file
View File

@@ -0,0 +1,96 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual environments
venv/
ENV/
env.bak/
venv.bak/
# Jupyter Notebook checkpoints
.ipynb_checkpoints
# VS Code settings
.vscode/
# Devcontainer settings
.devcontainer/
# Docker
*.log
docker-compose.override.yml
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# mypy
.mypy_cache/
.dmypy.json
# Pyre type checker
.pyre/
# Profiling data
.prof
# CSV/Excel/other data files
*.csv
*.tsv
*.xls
*.xlsx
# Misc
*.swp
*.swo
*.bak
*.tmp
# System files
.DS_Store
Thumbs.db
# own modifications
.env
sync.ffs_db
config/
presentation/
screenshots/
logs/
simclient.log*
current_event.json
last_event.json

168
src/CONTAINER_TRANSITION.md Normal file
View File

@@ -0,0 +1,168 @@
# Container Transition Guide
## Converting Pi Development to Container
Your `simclient.py` is already well-prepared for containerization! Here are the minimal changes needed:
## ✅ Already Container-Ready Features
1. **Multi-path Environment Loading**: Already supports container paths
2. **Volume-friendly File Handling**: Uses relative paths for shared directories
3. **Screenshot Service**: Designed to read from shared volume
4. **Configuration**: Environment variable based
5. **Logging**: Configurable output (file + console)
## 🔧 Required Changes
### 1. Minimal Code Adjustments (Optional)
The current code will work in containers as-is, but you can optimize it:
```python
# Current multi-path approach (already works):
env_paths = [
"/workspace/simclient/.env", # Container path
os.path.join(os.path.dirname(__file__), ".env"), # Same directory
os.path.join(os.path.expanduser("~"), "infoscreen-dev", ".env"), # Development path
]
# For production container, you could simplify to:
# load_dotenv() # Just use environment variables
```
### 2. Container Files Needed
Create these files for containerization:
#### Dockerfile
```dockerfile
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies (no GUI tools needed in container)
RUN apt-get update && apt-get install -y \\
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python packages
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY simclient.py .
# Create directories for volumes
RUN mkdir -p /app/presentation /app/screenshots /app/config /app/logs
# Run the application
CMD ["python", "simclient.py"]
```
#### docker-compose.yml
```yaml
version: '3.8'
services:
infoclient:
build: .
container_name: infoclient
restart: unless-stopped
environment:
- ENV=production
- MQTT_BROKER=${MQTT_BROKER}
- MQTT_PORT=${MQTT_PORT}
- HEARTBEAT_INTERVAL=60
- SCREENSHOT_INTERVAL=300
volumes:
# Shared with host OS for presentation files
- /opt/infoscreen/presentations:/app/presentation:rw
# Screenshots from host OS
- /opt/infoscreen/screenshots:/app/screenshots:ro
# Persistent config
- /opt/infoscreen/config:/app/config:rw
# Logs for monitoring
- /opt/infoscreen/logs:/app/logs:rw
networks:
- infonet
networks:
infonet:
driver: bridge
```
## 🚀 Transition Strategy
### Phase 1: Test Container Locally
```bash
# On your Pi, test the container version
cd ~/infoscreen-dev/src
docker build -t infoclient .
docker run --rm -e MQTT_BROKER=192.168.1.100 infoclient
```
### Phase 2: Hybrid Setup (Recommended)
Keep your current architecture but containerize the communication part:
```
┌─────────────────────────────────────────┐
│ Raspberry Pi │
├─────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────┐│
│ │ Container │ │ Host OS ││
│ │ │ │ ││
│ │ • simclient.py │ │ • Presentation ││
│ │ • MQTT Client │ │ • Screenshots ││
│ │ • File Download │ │ • Display Mgmt ││
│ └─────────────────┘ └─────────────────┘│
│ ↕ Shared Volumes ↕ │
└─────────────────────────────────────────┘
```
### Phase 3: Production Deployment
Use the container for easy updates across multiple Pis:
```bash
# Build and push to registry
docker build -t your-registry/infoclient:v1.0 .
docker push your-registry/infoclient:v1.0
# Deploy to all Pis
ansible all -i inventory -m shell -a "docker-compose pull && docker-compose up -d"
```
## 📋 Containerization Checklist
### Code Changes Needed: ❌ None (already compatible!)
### Files to Create:
- [ ] `Dockerfile`
- [ ] `docker-compose.yml`
- [ ] `.dockerignore`
- [ ] Production environment template
### Host OS Services Needed:
- [ ] Screenshot capture service
- [ ] Presentation handler service
- [ ] Shared volume directories
## 🎯 Zero Code Changes Required!
Your current `simclient.py` will run in a container without any modifications because:
1.**Environment Loading**: Already supports container environment paths
2.**File Paths**: Uses container-friendly relative paths
3.**Volume Mounting**: Presentation/screenshot directories are already externalized
4.**Configuration**: Fully environment variable based
5.**Logging**: Already outputs to both file and console
6.**Screenshot Reading**: Reads from shared volume (not capturing directly)
## 🚀 Deployment Benefits
Container deployment will give you:
- **Easy Updates**: `docker-compose pull && docker-compose up -d`
- **Consistency**: Same runtime across all Pis
- **Rollback**: Quick version switching
- **Monitoring**: Health checks and centralized logging
- **Isolation**: Container issues don't affect host presentation
The transition will be seamless! 🎉

457
src/DISPLAY_MANAGER.md Normal file
View File

@@ -0,0 +1,457 @@
# Display Manager - Event Display Controller
## Overview
The **Display Manager** is a daemon process that monitors `current_event.json` and automatically controls display software (LibreOffice, Chromium, VLC) to show the appropriate content based on scheduled events.
## Architecture
```
MQTT Server → simclient.py → current_event.json → display_manager.py → Display Software
├─ LibreOffice (presentations)
├─ Chromium (web pages)
└─ VLC/MPV (videos)
```
### How It Works
1. **Event Reception**: `simclient.py` receives events via MQTT and writes them to `current_event.json`
2. **File Monitoring**: `display_manager.py` continuously monitors this file for changes
3. **Event Processing**: When changes detected, manager determines what to display
4. **Time-based Activation**: Respects event `start` and `end` times
5. **Process Management**: Starts appropriate display software and manages its lifecycle
6. **Clean Transitions**: Gracefully terminates old software before starting new
## Supported Event Types
**⚠️ Important: Timestamps are in UTC**
All event `start` and `end` times must be in **UTC format** (as stored in the server database). The Display Manager automatically converts these to the local timezone for comparison.
Example format: `"2025-10-01 08:00:00"` (interpreted as UTC)
### 1. Presentation Events (PowerPoint/PDF)
```json
{
"id": 1,
"title": "Company Overview",
"start": "2025-10-01 08:00:00",
"end": "2025-10-01 18:00:00",
"presentation": {
"type": "slideshow",
"files": [
{
"name": "presentation.pptx",
"url": "http://server/files/presentation.pptx"
}
],
"slide_interval": 10,
"auto_advance": true
}
}
```
**Supported Formats:**
- `.pptx`, `.ppt` (Microsoft PowerPoint) → LibreOffice Impress
- `.odp` (OpenDocument Presentation) → LibreOffice Impress
- `.pdf` (PDF documents) → Evince or Okular
**Display Behavior:**
- Fullscreen/presentation mode
- Auto-advance slides (if supported by viewer)
- Loops through presentation continuously
### 2. Web Page Events
```json
{
"id": 2,
"title": "Dashboard Display",
"start": "2025-10-01 08:00:00",
"end": "2025-10-01 18:00:00",
"web": {
"url": "https://dashboard.example.com"
}
}
```
**Display Behavior:**
- Kiosk mode (fullscreen, no UI)
- Uses Chromium/Chrome browser
- Disables session restore and crash bubbles
### 3. Video Events
```json
{
"id": 3,
"title": "Promotional Video",
"start": "2025-10-01 08:00:00",
"end": "2025-10-01 18:00:00",
"video": {
"url": "http://server/videos/promo.mp4",
"loop": true
}
}
```
**Supported Formats:**
- Local files or HTTP URLs
- All formats supported by VLC/MPV (mp4, avi, mkv, etc.)
**Display Behavior:**
- Fullscreen playback
- Optional looping
- Uses VLC or MPV player
## Installation & Setup
### Development Setup
1. **Install dependencies:**
```bash
# Already in requirements.txt, but ensure these system packages are installed:
sudo apt-get update
sudo apt-get install -y \
libreoffice-impress \
chromium-browser \
vlc \
evince
```
2. **Start Display Manager:**
```bash
./scripts/start-display-manager.sh
```
### Production Setup (Systemd Service)
1. **Copy systemd service file:**
```bash
sudo cp scripts/infoscreen-display.service /etc/systemd/system/
sudo systemctl daemon-reload
```
2. **Enable and start service:**
```bash
sudo systemctl enable infoscreen-display.service
sudo systemctl start infoscreen-display.service
```
3. **Check status:**
```bash
sudo systemctl status infoscreen-display.service
sudo journalctl -u infoscreen-display.service -f
```
## Configuration
Configure via `.env` file:
```bash
# Display Manager Settings
DISPLAY_CHECK_INTERVAL=5 # How often to check for event changes (seconds)
LOG_LEVEL=INFO # Logging level (DEBUG, INFO, WARNING, ERROR)
ENV=production # Environment (development, production)
# Display environment
DISPLAY=:0 # X11 display (usually :0)
```
## Usage
### Starting the Display Manager
**Development:**
```bash
./scripts/start-display-manager.sh
```
**Production (systemd):**
```bash
sudo systemctl start infoscreen-display.service
```
### Testing
Run the interactive test script:
```bash
./scripts/test-display-manager.sh
```
**Test menu options:**
1. Check Display Manager status
2. Create PRESENTATION test event
3. Create WEBPAGE test event
4. Create VIDEO test event
5. Remove event (no display)
6. Check active display processes
7. View current event file
8. Interactive test (cycle through events)
### Manual Testing
Create a test event file:
```bash
cat > src/current_event.json <<EOF
{
"id": 999,
"title": "Test Presentation",
"start": "2025-01-01 00:00:00",
"end": "2025-12-31 23:59:59",
"presentation": {
"files": [{"name": "test.pptx"}]
}
}
EOF
```
Display Manager will detect the change within 5 seconds and start the presentation.
### Stopping Display
Remove the event file:
```bash
rm src/current_event.json
```
Or create an empty event:
```bash
echo "{}" > src/current_event.json
```
## Best Practices Implemented
### ✅ 1. Separation of Concerns
- **MQTT Client** (`simclient.py`): Handles network communication
- **Display Manager** (`display_manager.py`): Handles display control
- Communication via file: `current_event.json`
### ✅ 2. Robust Process Management
- Clean process lifecycle (start → monitor → terminate)
- Graceful termination with fallback to force kill
- Process health monitoring and automatic restart
- PID tracking for debugging
### ✅ 3. Event State Machine
- Clear states: NO_EVENT → EVENT_ACTIVE → DISPLAY_RUNNING
- Proper state transitions
- Event change detection via file modification time
- Event deduplication (same event doesn't restart display)
### ✅ 4. Time-based Scheduling
- Respects event `start` and `end` times
- Automatically stops display when event expires
- Handles timezone-aware timestamps
### ✅ 5. Application Lifecycle Management
**Starting Applications:**
- Detects available software (LibreOffice, Chromium, VLC)
- Uses appropriate command-line flags for kiosk/fullscreen
- Sets correct environment variables (DISPLAY, XAUTHORITY)
**Stopping Applications:**
- First attempts graceful termination (SIGTERM)
- Waits 5 seconds for clean shutdown
- Falls back to force kill (SIGKILL) if needed
- Cleans up zombie processes
### ✅ 6. Error Handling & Logging
- Comprehensive error logging with context
- Rotating log files (2MB per file, 5 backups)
- Different log levels for development/production
- Exception handling around all external operations
### ✅ 7. File Watching Strategy
- Efficient: Only re-reads when file changes (mtime check)
- Handles missing files gracefully
- JSON parsing with error recovery
- Non-blocking I/O
### ✅ 8. Graceful Shutdown
- Signal handlers (SIGTERM, SIGINT)
- Stops current display before exiting
- Clean resource cleanup
### ✅ 9. Development Experience
- Test scripts for all functionality
- Interactive testing mode
- Verbose logging in development
- Easy manual testing
### ✅ 10. Production Readiness
- Systemd service integration
- Auto-restart on failure
- Resource limits and security settings
- Journal logging
## Troubleshooting
### Display Manager not starting
```bash
# Check logs
tail -f logs/display_manager.log
# Check if virtual environment activated
source venv/bin/activate
# Verify Python can import required modules
python3 -c "import paho.mqtt.client; print('OK')"
```
### Display software not appearing
```bash
# Check DISPLAY variable
echo $DISPLAY
# Verify X11 authentication
xhost +local:
# Check if software is installed
which libreoffice chromium-browser vlc
# Check running processes
ps aux | grep -E 'libreoffice|chromium|vlc'
```
### Events not triggering display changes
```bash
# Verify event file exists and is valid JSON
cat src/current_event.json | jq .
# Check file modification time
stat src/current_event.json
# Check Display Manager is running
pgrep -f display_manager.py
# Watch logs in real-time
tail -f logs/display_manager.log
```
### Display software crashes
```bash
# Check for error messages
journalctl -xe | grep -E 'libreoffice|chromium|vlc'
# Verify files exist
ls -la src/presentation/
# Test manual start
libreoffice --impress --show src/presentation/test.pptx
```
### Timezone / Event timing issues
**Problem**: Events not displaying at the expected time
**Cause**: Event times are in UTC, but you're thinking in local time
**Solution**:
```bash
# Check current UTC time
date -u
# Check current local time
date
# Check timezone offset
date +%Z
date +%z
# Test with UTC timestamp script
./scripts/test-utc-timestamps.sh
# View Display Manager timezone info in logs
tail -f logs/display_manager.log | grep -i "time\|utc"
```
**Understanding UTC timestamps:**
- Server stores times in UTC (database standard)
- Display Manager compares with current UTC time
- Events display correctly regardless of client timezone
**Example**:
- Event start: `2025-10-01 08:00:00` (UTC)
- Your timezone: CEST (UTC+2)
- Event will display at: 10:00:00 local time
**Debugging timing issues:**
1. Check Display Manager logs for time comparisons
2. Logs show: "Current time (UTC): ..." and "Event start time (UTC): ..."
3. Use test script: `./scripts/test-utc-timestamps.sh`
4. Verify server sends UTC timestamps (not local times)
## Architecture Decisions
### Why separate processes?
- **Fault isolation**: Display crash doesn't affect MQTT client
- **Independent lifecycle**: Can restart display without losing connection
- **Simpler debugging**: Separate logs and process monitoring
### Why file-based communication?
- **Simplicity**: No IPC complexity (sockets, pipes, queues)
- **Persistence**: Event survives process restarts
- **Debuggability**: Can inspect/modify events manually
- **Atomic operations**: File writes are atomic
### Why polling instead of inotify?
- **Portability**: Works on all systems
- **Simplicity**: No external dependencies
- **Reliability**: Catches events even if filesystem events missed
- **Performance**: 5-second interval is sufficient
### Why subprocess instead of libraries?
- **Flexibility**: Can use any display software
- **Reliability**: Process isolation
- **Feature completeness**: Full application features (vs. library subset)
- **Maintainability**: No need to update when apps change
## Performance Characteristics
- **CPU Usage**: Minimal when idle (<1%)
- **Memory**: ~20-30MB for manager + display software memory
- **Startup Time**: <1 second
- **Event Detection**: ~5 seconds average, max 5 seconds
- **Display Transition**: 1-3 seconds for clean shutdown + start
## Future Enhancements
Potential improvements:
1. **Multi-display support**: Handle multiple screens
2. **Playlist support**: Cycle through multiple presentations
3. **Transition effects**: Fade between content
4. **Health checks**: Verify display is rendering correctly
5. **Remote control**: MQTT commands to pause/resume
6. **Screenshot monitoring**: Send actual display output to server
7. **Performance metrics**: Track frame rates, response times
8. **Fallback content**: Default display when no events active
## Integration with MQTT Client
The Display Manager integrates seamlessly with `simclient.py`:
```
Server MQTT → simclient.py → current_event.json → display_manager.py → Screen
Downloads files
to presentation/
```
**simclient.py responsibilities:**
- MQTT communication
- Event file downloads
- Writing `current_event.json`
**display_manager.py responsibilities:**
- Reading `current_event.json`
- Time-based event activation
- Display software control
## License & Support
Part of the Infoscreen Client 2025 project.
See main README.md for license and contribution guidelines.

31
src/Dockerfile.production Normal file
View File

@@ -0,0 +1,31 @@
FROM python:3.11-slim
# Set working directory
WORKDIR /app
# Install system dependencies (minimal for container)
RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python packages
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY simclient.py .
# Create directories for volume mounts
RUN mkdir -p /app/presentation /app/screenshots /app/config /app/logs
# Create non-root user for security
RUN useradd -r -s /bin/false infoclient && \
chown -R infoclient:infoclient /app
USER infoclient
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import socket; socket.create_connection(('${MQTT_BROKER:-localhost}', ${MQTT_PORT:-1883}), timeout=5)" || exit 1
# Run the application
CMD ["python", "simclient.py"]

Some files were not shown because too many files have changed in this diff Show More