Skip to content

Commit

Permalink
Big update (#6)
Browse files Browse the repository at this point in the history
* Corrects issues caused by older configurations

Older photoframe would save all settings as strings,
causing issues when used as non-strings.

* Do not use script when not available

This applies EVEN if you have the hardware

* Added timezone functions

We must be able to control the timezone locally, this is
the first step. Adding functions to list, get and set.

* Update will now add i2c-dev to modules.conf if not present

This is to make sure adding the colortemp sensor will actually
work with the photoframe.

* Added functions to get current display as well as all resolutions

This is a step towards having a dropdown box instead of manually
entering the settings.

* Simplified resolution management

User now chooses one of the available resolutions and photoframe
figures out the rest.

* Add support for altering the timezone

* Fixed initial display setup

* Update now has a post-update step

Also adds support to enforce that step if it didn't happen
due to old release.

* Typo causing update.sh to fail

* Make json more fault tolerant

Ie, if we cannot parse it, dismiss it

* Use absolute paths

* Handle broken tvservice field in config

* Must convert parts to numbers for detection to work

* Show progressive or interlace in resolution list

* Stop ajax from caching old html

* Fixed major bug which blocked frame from updating properly

Used && when it should have been ||

But frames will auto update within 2 days and correct themselves,
so it's not as bad as it would seem.

* reload systemd units on update

* Change real and virtual resolution
  • Loading branch information
mrworf authored Jun 13, 2018
1 parent 236949f commit aa208d4
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 37 deletions.
33 changes: 29 additions & 4 deletions frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']:
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions modules/colormatch.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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:
Expand All @@ -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
Expand Down
33 changes: 32 additions & 1 deletion modules/display.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import logging
import time
import re
import json

class display:
def __init__(self, width, height, depth, tvservice_params):
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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'])
22 changes: 22 additions & 0 deletions modules/helper.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 11 additions & 7 deletions modules/remember.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
40 changes: 30 additions & 10 deletions modules/settings.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import random

class settings:
CONFIGFILE = '/root/settings.json'

def __init__(self):
self.settings = {
'oauth_token' : None,
Expand All @@ -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
Expand All @@ -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):
Expand Down
29 changes: 27 additions & 2 deletions static/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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);
});
});
});
}

Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit aa208d4

Please sign in to comment.