main: add image hashing

build md5 sum over the resulting assembled image(s) and
check against a stored hash file to determine if we really need
to update the screen.
Option can be controlled by new image_hash global option.

If info_section is enabled while image_hash is on,
the time of update will be stripped from the section,
as it is clearly not hashable.

In the end this enables us to update the information in the
background way more frequent without increasing the stress
caused to the display

Signed-off-by: Konrad Weihmann <kweihmann@outlook.com>
This commit is contained in:
Konrad Weihmann 2022-08-23 09:09:36 +00:00
parent 8d788b06ca
commit 026b3c1da0

View File

@ -6,7 +6,10 @@ Main class for inkycal Project
Copyright by aceisace Copyright by aceisace
""" """
import glob
import hashlib
import json import json
import os
import traceback import traceback
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
@ -168,6 +171,9 @@ class Inkycal:
# Path to store images # Path to store images
self.image_folder = image_folder self.image_folder = image_folder
# Remove old hashes
self._remove_hashes(self.image_folder)
# Give an OK message # Give an OK message
print('loaded inkycal') print('loaded inkycal')
@ -235,6 +241,45 @@ class Inkycal:
self._assemble() self._assemble()
def _image_hash(self, _in):
"""Create a md5sum of a path or a bytes stream."""
if not isinstance(_in, str):
image_bytes = _in.tobytes()
else:
try:
with open(_in) as i:
return i.read()
except FileNotFoundError:
image_bytes = None
return hashlib.md5(image_bytes).hexdigest() if image_bytes else ""
def _remove_hashes(self, basepath):
for _file in glob.glob(f"{basepath}/*.hash"):
try:
os.remove(_file)
except:
pass
def _write_image_hash(self, path, _in):
"""Write hash to a file."""
with open(path, "w") as o:
o.write(self._image_hash(_in))
def _needs_image_update(self, _list):
"""Check if any image has been updated or not.
Input a list of tuples(str, image)."""
res = False
for item in _list:
_a = self._image_hash(item[0])
_b = self._image_hash(item[1])
print("{f1}:{h1} -> {h2}".format(f1=item[0], h1=_a, h2=_b))
if _a != _b:
res = True
self._write_image_hash(item[0], item[1])
print("Refresh needed: {a}".format(a=res))
return res
def run(self): def run(self):
"""Runs main program in nonstop mode. """Runs main program in nonstop mode.
@ -264,7 +309,10 @@ class Inkycal:
errors = [] # store module numbers in here errors = [] # store module numbers in here
# short info for info-section # short info for info-section
self.info = f"{current_time.format('D MMM @ HH:mm')} " if not self.settings.get('image_hash', False):
self.info = f"{current_time.format('D MMM @ HH:mm')} "
else:
self.info = ""
for number in range(1, self._module_number): for number in range(1, self._module_number):
@ -299,6 +347,9 @@ class Inkycal:
display = self.Display display = self.Display
self._calibration_check() self._calibration_check()
if self._calibration_state:
# after calibration we have to forcefully rewrite the screen
self._remove_hashes(self.image_folder)
if self.supports_colour: if self.supports_colour:
im_black = Image.open(f"{self.image_folder}canvas.png") im_black = Image.open(f"{self.image_folder}canvas.png")
@ -310,7 +361,12 @@ class Inkycal:
im_colour = upside_down(im_colour) im_colour = upside_down(im_colour)
# render the image on the display # render the image on the display
display.render(im_black, im_colour) if not self.settings.get('image_hash', False) or self._needs_image_update([
(f"{self.image_folder}/canvas.png.hash", im_black),
(f"{self.image_folder}/canvas_colour.png.hash", im_colour)
]):
# render the image on the display
display.render(im_black, im_colour)
# Part for black-white ePapers # Part for black-white ePapers
elif not self.supports_colour: elif not self.supports_colour:
@ -321,7 +377,10 @@ class Inkycal:
if self.settings['orientation'] == 180: if self.settings['orientation'] == 180:
im_black = upside_down(im_black) im_black = upside_down(im_black)
display.render(im_black) if not self.settings.get('image_hash', False) or self._needs_image_update([
(f"{self.image_folder}/canvas.png.hash", im_black),
]):
display.render(im_black)
print(f'\nNo errors since {counter} display updates \n' print(f'\nNo errors since {counter} display updates \n'
f'program started {runtime.humanize()}') f'program started {runtime.humanize()}')