diff --git a/frame.py b/frame.py index 9937c65..8bbcb72 100755 --- a/frame.py +++ b/frame.py @@ -135,6 +135,25 @@ def cfg_keyvalue(key, value): if key in ['width', 'height', 'depth', 'tvservice']: display.setConfiguration(settings.getUser('width'), settings.getUser('height'), settings.getUser('depth'), settings.getUser('tvservice')) display.enable(True, True) + if key in ['timezone']: + # Make sure we convert + to / + settings.setUser('timezone', value.replace('+', '/')) + helper.timezoneSet(settings.getUser('timezone')) + if key in ['resolution']: + # This one needs some massaging, we essentially deduce all settings from a string (DMT/CEA CODE HDMI) + items = settings.getUser('resolution').split(' ') + logging.debug('Items: %s', repr(items)) + resolutions = display.available() + for res in resolutions: + if res['code'] == int(items[1]) and res['mode'] == items[0]: + logging.debug('Found this item: %s', repr(res)) + settings.setUser('width', res['width']) + settings.setUser('height', res['height']) + settings.setUser('depth', 32) + settings.setUser('tvservice', value) + display.setConfiguration(settings.getUser('width'), settings.getUser('height'), settings.getUser('depth'), settings.getUser('tvservice')) + display.enable(True, True) + break if key in ['display-on', 'display-off']: timekeeper.setConfiguration(settings.getUser('display-on'), settings.getUser('display-off')) if key in ['autooff-lux', 'autooff-time']: @@ -222,15 +241,17 @@ def cfg_shutdown(): def cfg_details(about): if about == 'tvservice': result = {} - result['cea'] = subprocess.check_output(['/opt/vc/bin/tvservice', '-m', 'cea']) - result['dmt'] = subprocess.check_output(['/opt/vc/bin/tvservice', '-m', 'dmt']) - result['status'] = subprocess.check_output(['/opt/vc/bin/tvservice', '-status']) + result['resolution'] = display.available() + result['status'] = display.current() return jsonify(result) elif about == 'current': image, mime = display.get() response = app.make_response(image) response.headers.set('Content-Type', mime) return response + elif about == 'timezone': + result = helper.timezoneList() + return jsonify(result) elif about == 'version': output = subprocess.check_output(['git', 'log', '-n1'], stderr=void) lines = output.split('\n') @@ -278,11 +299,15 @@ def web_template(file): # First run, grab display settings from current mode current = display.current() logging.info('No display settings, using: %s' % repr(current)) - settings.setUser('tvservice', '%s %s %s' % (current['group'], current['mode'], current['drive'])) + settings.setUser('tvservice', '%s %s HDMI' % (current['mode'], current['code'])) settings.setUser('width', int(current['width'])) settings.setUser('height', int(current['height'])) settings.save() +if settings.getUser('timezone') == '': + settings.setUser('timezone', helper.timezoneCurrent()) + settings.save() + display = display(settings.getUser('width'), settings.getUser('height'), settings.getUser('depth'), settings.getUser('tvservice')) # Force display to desired user setting diff --git a/modules/colormatch.py b/modules/colormatch.py old mode 100644 new mode 100755 index 59a0773..527a007 --- a/modules/colormatch.py +++ b/modules/colormatch.py @@ -33,6 +33,11 @@ def __init__(self, script, min = None, max = None): self.max = max self.listener = None self.allowAdjust = False + if self.script is not None and self.script != '': + self.hasScript = os.path.exists(self.script) + else: + self.hasScript = False + self.start() def setLimits(self, min, max): @@ -58,7 +63,7 @@ def setUpdateListener(self, listener): self.listener = listener def adjust(self, src, dst, temperature = None): - if not self.allowAdjust: + if not self.allowAdjust or not self.hasScript: return False if self.temperature is None or self.sensor is None: @@ -75,7 +80,11 @@ def adjust(self, src, dst, temperature = None): else: logging.debug('Adjusting color temperature to %dK' % temperature) - return subprocess.call([self.script, '-t', "%d" % temperature, src + '[0]', dst], stderr=self.void) == 0 + try: + return subprocess.call([self.script, '-t', "%d" % temperature, src + '[0]', dst], stderr=self.void) == 0 + except: + logging.exception('Unable to run %s:', self.script) + return False # The following function (_temperature_and_lux) is lifted from the # https://github.com/adafruit/Adafruit_CircuitPython_TCS34725 project and diff --git a/modules/display.py b/modules/display.py old mode 100644 new mode 100755 index 0fce53b..dd025f8 --- a/modules/display.py +++ b/modules/display.py @@ -18,6 +18,7 @@ import logging import time import re +import json class display: def __init__(self, width, height, depth, tvservice_params): @@ -118,7 +119,7 @@ def enable(self, enable, force=False): subprocess.call(['/opt/vc/bin/tvservice', '-e', self.params], stderr=self.void, stdout=self.void) time.sleep(1) subprocess.call(['/bin/fbset', '-depth', '8'], stderr=self.void) - subprocess.call(['/bin/fbset', '-depth', str(self.depth), '-xres', str(self.width), '-yres', str(self.height)], stderr=self.void) + subprocess.call(['/bin/fbset', '-depth', str(self.depth), '-xres', str(self.width), '-yres', str(self.height), '-vxres', str(self.width), '-vyres', str(self.height)], stderr=self.void) else: subprocess.call(['/usr/bin/vcgencmd', 'display_power', '1'], stderr=self.void) else: @@ -134,6 +135,7 @@ def clear(self): @staticmethod def current(): + ''' output = subprocess.check_output(['/opt/vc/bin/tvservice', '-s'], stderr=subprocess.STDOUT) print('"%s"' % (output)) # state 0x120006 [DVI DMT (82) RGB full 16:9], 1920x1080 @ 60.00Hz, progressive @@ -146,4 +148,33 @@ def current(): 'height' : m.group(5) } return result + ''' + output = subprocess.check_output(['/opt/vc/bin/tvservice', '-s'], stderr=subprocess.STDOUT) + # state 0x120006 [DVI DMT (82) RGB full 16:9], 1920x1080 @ 60.00Hz, progressive + m = re.search('state 0x[0-9a-f]* \[([A-Z]*) ([A-Z]*) \(([0-9]*)\) [^,]*, ([0-9]*)x([0-9]*) \@ ([0-9]*)\.[0-9]*Hz, (.)', output) + result = { + 'mode' : m.group(2), + 'code' : int(m.group(3)), + 'width' : int(m.group(4)), + 'height' : int(m.group(5)), + 'rate' : int(m.group(6)), + 'aspect_ratio' : '', + 'scan' : m.group(7), + '3d_modes' : [] + } + return result + + @staticmethod + def available(): + cea = json.loads(subprocess.check_output(['/opt/vc/bin/tvservice', '-j', '-m', 'CEA'], stderr=subprocess.STDOUT)) + dmt = json.loads(subprocess.check_output(['/opt/vc/bin/tvservice', '-j', '-m', 'DMT'], stderr=subprocess.STDOUT)) + result = [] + for entry in cea: + entry['mode'] = 'CEA' + result.append(entry) + for entry in dmt: + entry['mode'] = 'DMT' + result.append(entry) + # Finally, sort by pixelcount + return sorted(result, key=lambda k: k['width']*k['height']) \ No newline at end of file diff --git a/modules/helper.py b/modules/helper.py old mode 100644 new mode 100755 index 6d887e3..319537a --- a/modules/helper.py +++ b/modules/helper.py @@ -148,3 +148,25 @@ def makeFullframe(filename, imageWidth, imageHeight): return False os.rename(filename_temp, filename) return True + + @staticmethod + def timezoneList(): + zones = subprocess.check_output(['/usr/bin/timedatectl', 'list-timezones']).split('\n') + return [x for x in zones if x] + + @staticmethod + def timezoneCurrent(): + with open('/etc/timezone', 'r') as f: + result = f.readlines() + return result[0].strip() + + @staticmethod + def timezoneSet(zone): + result = 1 + try: + with open(os.devnull, 'wb') as void: + result = subprocess.check_call(['/usr/bin/timedatectl', 'set-timezone', zone], stderr=void) + except: + logging.exception('Unable to change timezone') + pass + return result == 0 diff --git a/modules/remember.py b/modules/remember.py index d165c9c..40b1bee 100644 --- a/modules/remember.py +++ b/modules/remember.py @@ -22,14 +22,18 @@ class remember: def __init__(self, filename, count): self.filename = os.path.splitext(filename)[0] + '_memory.json' self.count = count - if os.path.exists(self.filename): - with open(self.filename, 'rb') as f: - self.memory = json.load(f) - if 'count' not in self.memory or self.memory['count'] == 0: - self.memory['count'] = count + try: + if os.path.exists(self.filename): + with open(self.filename, 'rb') as f: + self.memory = json.load(f) + if 'count' not in self.memory or self.memory['count'] == 0: + self.memory['count'] = count + else: + self.debug() else: - self.debug() - else: + self.memory = {'seen':[], 'count':count} + except: + logging.exception('Failed to load database') self.memory = {'seen':[], 'count':count} def forget(self): diff --git a/modules/settings.py b/modules/settings.py old mode 100644 new mode 100755 index 6c241fb..c772707 --- a/modules/settings.py +++ b/modules/settings.py @@ -19,6 +19,8 @@ import random class settings: + CONFIGFILE = '/root/settings.json' + def __init__(self): self.settings = { 'oauth_token' : None, @@ -37,6 +39,8 @@ def userDefaults(self): 'height' : 1080, 'depth' : 32, 'tvservice' : 'DMT 82 DVI', + 'resolution' : '', # Place holder, used to deduce correct resolution before setting TV service + 'timezone' : '', 'interval' : 60, # Delay in seconds between images (minimum) 'display-off' : 22, # What hour (24h) to disable display and sleep 'display-on' : 4, # What hour (24h) to enable display and continue @@ -51,21 +55,37 @@ def userDefaults(self): } def load(self): - if os.path.exists('/root/settings.json'): - with open('/root/settings.json') as f: - # A bit messy, but it should allow new defaults to be added - # to old configurations. - tmp = self.settings['cfg'] - self.settings = json.load(f) - tmp2 = self.settings['cfg'] - self.settings['cfg'] = tmp - self.settings['cfg'].update(tmp2) + if os.path.exists(settings.CONFIGFILE): + with open(settings.CONFIGFILE) as f: + try: + # A bit messy, but it should allow new defaults to be added + # to old configurations. + tmp = self.settings['cfg'] + self.settings = json.load(f) + tmp2 = self.settings['cfg'] + self.settings['cfg'] = tmp + self.settings['cfg'].update(tmp2) + + # Also, we need to iterate the settings and make sure numbers and floats are + # that, and not strings (which the old version did) + for k in self.settings['cfg']: + self.settings['cfg'][k] = self.convertToNative(self.settings['cfg'][k]) + # Lastly, correct the tvservice field, should be "TEXT NUMBER TEXT" + # This is a little bit of a cheat + parts = self.settings['cfg']['tvservice'].split(' ') + if type(self.convertNative(parts[1])) != int and type(self.convertNative(parts[2])) == int: + logging.debug('Reordering tvservice value due to old bug') + self.settings['cfg']['tvservice'] = "%s %s %s" % (parts[0], parts[2], parts[1]) + self.save() + except: + logging.exception('Failed to load settings.json, corrupt file?') + return False return True else: return False def save(self): - with open('/root/settings.json', 'w') as f: + with open(settings.CONFIGFILE, 'w') as f: json.dump(self.settings, f) def convertToNative(self, value): diff --git a/static/index.js b/static/index.js index a3ec052..8921ede 100644 --- a/static/index.js +++ b/static/index.js @@ -165,6 +165,24 @@ function populateKeywords() { }); } +function loadResolution(funcOk) +{ + $.ajax({ + url:"/details/tvservice" + }).done(function(data) { + funcOk(data); + }); +} + +function loadTimezone(funcOk) +{ + $.ajax({ + url:"/details/timezone" + }).done(function(data) { + funcOk(data); + }); +} + function loadSettings(funcOk) { $.ajax({ @@ -177,7 +195,13 @@ function loadSettings(funcOk) value = data[key]; result[key] = value; } - funcOk(result); + loadResolution(function(data) { + result['resolution'] = data; + loadTimezone(function(data) { + result['timezones'] = data; + funcOk(result); + }); + }); }); } @@ -229,7 +253,8 @@ TemplateEngine = function() { thiz = this; $.ajax({ - url:'template/' + url + url:'template/' + url, + cache: false }).done(function(data){ thiz.regTemplate[url] = Handlebars.compile(data); if (i == templates.length) diff --git a/static/template/main.html b/static/template/main.html index df7a4ce..d275a7d 100644 --- a/static/template/main.html +++ b/static/template/main.html @@ -2,16 +2,26 @@

PhotoFrame Configuration< On screen now
- Screen + Options
- Resolution - x - - bitdepth + Resolution +
- TV service options - + Time zone +
GPIO PIN to monitor for shutdown interrupts @@ -23,8 +33,6 @@

PhotoFrame Configuration< lux for minutes
- Current ambient environment -.-- lux and ----K color temperature. -
Power saving measures should +
+ Current ambient environment -.-- lux and ----K color temperature.


@@ -125,6 +135,22 @@

PhotoFrame Configuration< }); }); + $("select[name=tvservice]").change(function() { + $.ajax({ + url:"/setting/resolution/" + encodeURIComponent($(this).val()), + type:"PUT" + }).done(function(){ + }); + }); + + $("select[name=timezone]").change(function() { + $.ajax({ + url:"/setting/timezone/" + encodeURIComponent($(this).val().replace('/', '+')), + type:"PUT" + }).done(function(){ + }); + }); + $("#reset").click(function() { if (confirm("Are you sure? Link to photos will also be reset")) { $.ajax({ diff --git a/update.sh b/update.sh index 800adbe..c153e85 100755 --- a/update.sh +++ b/update.sh @@ -27,13 +27,41 @@ function error cd /root/photoframe +if [ "$1" = "post" ]; then + echo "Performing post-update changes (if any)" + + ####-vvv- ANYTHING HERE MUST HANDLE BEING RUN AGAIN AND AGAIN -vvv-#### + ####################################################################### + + # Due to older version not enabling the necessary parts, + # we need to add i2c-dev to modules if not there + if ! grep "i2c-dev" /etc/modules-load.d/modules.conf >/dev/null ; then + echo "i2c-dev" >> /etc/modules-load.d/modules.conf + modprobe i2c-dev + fi + cp frame.service /etc/systemd/system/ + systemctl daemon-reload + + ####################################################################### + ####-^^^- ANYTHING HERE MUST HANDLE BEING RUN AGAIN AND AGAIN -^^^-#### + touch /root/.donepost + exit 0 +elif [ ! -f /root/.donepost ]; then + # Since we didn't have this behavior, we need to make sure it happens regardless + # of availability of new update. + /root/photoframe/update.sh post +fi + git fetch 2>&1 >/tmp/update.log || error "Unable to load info about latest" git log -n1 --oneline origin >/tmp/server.txt git log -n1 --oneline >/tmp/client.txt if ! diff /tmp/server.txt /tmp/client.txt >/dev/null ; then echo "New version is available" - git pull --rebase 2>&1 >>/tmp/update.log && error "Unable to update" - cp frame.service /etc/systemd/system/ + git pull --rebase 2>&1 >>/tmp/update.log || error "Unable to update" + + # Run again with the post option so any necessary changes can be carried out + /root/photoframe/update.sh post + systemctl restart frame.service fi