Initial import: clean snapshot from /home/olafn/infoscreen-dev (2025-10-25)
This commit is contained in:
1
.git.legacy_backup.1761406947
Submodule
1
.git.legacy_backup.1761406947
Submodule
Submodule .git.legacy_backup.1761406947 added at 2a4701fc5d
1
.git.legacy_backup/COMMIT_EDITMSG
Normal file
1
.git.legacy_backup/COMMIT_EDITMSG
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Initial import: clean snapshot from /home/olafn/infoscreen-dev (2025-10-25)
|
||||||
1
.git.legacy_backup/HEAD
Normal file
1
.git.legacy_backup/HEAD
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ref: refs/heads/master
|
||||||
5
.git.legacy_backup/config
Normal file
5
.git.legacy_backup/config
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[core]
|
||||||
|
repositoryformatversion = 0
|
||||||
|
filemode = true
|
||||||
|
bare = false
|
||||||
|
logallrefupdates = true
|
||||||
1
.git.legacy_backup/description
Normal file
1
.git.legacy_backup/description
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Unnamed repository; edit this file 'description' to name the repository.
|
||||||
15
.git.legacy_backup/hooks/applypatch-msg.sample
Executable file
15
.git.legacy_backup/hooks/applypatch-msg.sample
Executable 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+"$@"}
|
||||||
|
:
|
||||||
24
.git.legacy_backup/hooks/commit-msg.sample
Executable file
24
.git.legacy_backup/hooks/commit-msg.sample
Executable 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
|
||||||
|
}
|
||||||
174
.git.legacy_backup/hooks/fsmonitor-watchman.sample
Executable file
174
.git.legacy_backup/hooks/fsmonitor-watchman.sample
Executable 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;
|
||||||
|
}
|
||||||
8
.git.legacy_backup/hooks/post-update.sample
Executable file
8
.git.legacy_backup/hooks/post-update.sample
Executable 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
|
||||||
14
.git.legacy_backup/hooks/pre-applypatch.sample
Executable file
14
.git.legacy_backup/hooks/pre-applypatch.sample
Executable 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+"$@"}
|
||||||
|
:
|
||||||
49
.git.legacy_backup/hooks/pre-commit.sample
Executable file
49
.git.legacy_backup/hooks/pre-commit.sample
Executable 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 --
|
||||||
13
.git.legacy_backup/hooks/pre-merge-commit.sample
Executable file
13
.git.legacy_backup/hooks/pre-merge-commit.sample
Executable 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"
|
||||||
|
:
|
||||||
53
.git.legacy_backup/hooks/pre-push.sample
Executable file
53
.git.legacy_backup/hooks/pre-push.sample
Executable 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
|
||||||
169
.git.legacy_backup/hooks/pre-rebase.sample
Executable file
169
.git.legacy_backup/hooks/pre-rebase.sample
Executable 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
|
||||||
24
.git.legacy_backup/hooks/pre-receive.sample
Executable file
24
.git.legacy_backup/hooks/pre-receive.sample
Executable 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
|
||||||
42
.git.legacy_backup/hooks/prepare-commit-msg.sample
Executable file
42
.git.legacy_backup/hooks/prepare-commit-msg.sample
Executable 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
|
||||||
78
.git.legacy_backup/hooks/push-to-checkout.sample
Executable file
78
.git.legacy_backup/hooks/push-to-checkout.sample
Executable 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
|
||||||
128
.git.legacy_backup/hooks/update.sample
Executable file
128
.git.legacy_backup/hooks/update.sample
Executable 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
BIN
.git.legacy_backup/index
Normal file
Binary file not shown.
6
.git.legacy_backup/info/exclude
Normal file
6
.git.legacy_backup/info/exclude
Normal 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]
|
||||||
|
# *~
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 M¢“&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
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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θ
|
||||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
|||||||
|
x…SÍjã0î¹O1·HÔMÜCCÙl
|
||||||
|
](´‡@ ½ÅžlL)Hã4!øÝ;’l'YØ]c°¤™ïgFã•¶+x<1A>oĺ6UÖyº<18>`QmwAÕd}á¬Ö? }¡´_ì<ä>Ú <C39A>•*><3E>,¿»îPÓ<>;¶½rPÖN~˜Â8çg4ƹçpJð„»™çðc>éÉ’Ò‹(Ï‘™¢Íp«¢´E½ECÃnñª1î“Ã7¬~o(c£mÚÊ–ÇáeLÂ=|U†+VÆ 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
|
||||||
Binary file not shown.
@@ -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 žÐZ‚c$½<>žÑRpèr?o¼eU¼¦¼_hèÄŒ†eˆ¶íí¶ÎRÑW(Ó¿)›ÐÝy†
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
x%ŽAŠÃ0EgSº+Ô¦…Þ ˜ÇVS×2’’ÒÛWI7}ñ‘ÞXit·ûõï䔞ȮÌ<C2AE>ÝTª<54>Øu¦¼&-Ôܸ–šü\ôÀ¯
|
||||||
|
gÿÊà±mgð›$ÊÀgÜ5<>¥!°ty?§Jo/ôrÙW‚ºö=ƒ½Ç
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
xĄŹANĂ0EY糡<>±;v„,Ů śÔnFŤ=•3‰Ó“ž<E2809C>í[ü÷ß,9ł‚uĂťÖ
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
.git.legacy_backup/refs/heads/master
Normal file
1
.git.legacy_backup/refs/heads/master
Normal file
@@ -0,0 +1 @@
|
|||||||
|
da43f2b29ecf071e9b4ffc2fa03482e25d7cf0c6
|
||||||
393
.github/copilot-instructions.md
vendored
Normal file
393
.github/copilot-instructions.md
vendored
Normal 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 don’t 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
64
.gitignore
vendored
Normal 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
195
CLEANUP_SUMMARY.md
Normal 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
272
IMPRESSIVE_INTEGRATION.md
Normal 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)
|
||||||
258
PROGRESS_BAR_IMPLEMENTATION.md
Normal file
258
PROGRESS_BAR_IMPLEMENTATION.md
Normal 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
192
QUICK_REFERENCE.md
Normal 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
612
README.md
Normal 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.0–1.0 (mapped internally to VLC's 0–100 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 don’t 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
202
SCHEDULER_FIELDS_SUPPORT.md
Normal 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
40
TODO.md
Normal 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
193
WORKSPACE_STATUS.txt
Normal 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
|
||||||
36
scripts/infoscreen-display.service
Normal file
36
scripts/infoscreen-display.service
Normal 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
|
||||||
163
scripts/present-pdf-auto-advance.sh
Executable file
163
scripts/present-pdf-auto-advance.sh
Executable 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
5
scripts/start-dev.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
source venv/bin/activate
|
||||||
|
export $(cat .env | xargs)
|
||||||
|
python3 src/simclient.py
|
||||||
43
scripts/start-display-manager.sh
Executable file
43
scripts/start-display-manager.sh
Executable 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
210
scripts/test-display-manager.sh
Executable 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
92
scripts/test-impressive-loop.sh
Executable 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
115
scripts/test-impressive.sh
Executable 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
9
scripts/test-mqtt.sh
Executable 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
174
scripts/test-progress-bars.sh
Executable 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
143
scripts/test-scheduler-fields.sh
Executable 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
27
scripts/test-screenshot.sh
Executable 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
149
scripts/test-utc-timestamps.sh
Executable 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
75
scripts/test_cdp.py
Executable 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()
|
||||||
76
scripts/test_cdp_origins.py
Normal file
76
scripts/test_cdp_origins.py
Normal 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
23
src/.dockerignore
Normal 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
|
||||||
23
src/.env.production.template
Normal file
23
src/.env.production.template
Normal 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
27
src/.env.template
Normal 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
96
src/.gitignore
vendored
Normal 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
168
src/CONTAINER_TRANSITION.md
Normal 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
457
src/DISPLAY_MANAGER.md
Normal 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
31
src/Dockerfile.production
Normal 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
Reference in New Issue
Block a user