Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python3 #192

Open
wants to merge 101 commits into
base: python3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 64 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
b6d2053
Update debug.py
Feb 24, 2021
0bf9013
Update shutdown.py
Feb 24, 2021
2b352e1
Update helper.py
Feb 25, 2021
4a2c00c
Update debug.py
Feb 27, 2021
ba3b6ab
Update helper.py
Feb 27, 2021
66eb329
Update base.py
Mar 8, 2021
079d761
Update display.py
Mar 9, 2021
2a2dc70
Update servicemanager.py
Mar 11, 2021
cd47273
Update oauth.py
Mar 11, 2021
6633fc8
Update oauthlink.py
Mar 11, 2021
4dc9064
Update README.md
May 4, 2021
fa84132
Update svc_googlephotos.py
May 5, 2021
f05a321
Update colormatch.py
May 5, 2021
aff4765
Update README.md
May 5, 2021
91bc4ec
Update helper.py
May 5, 2021
e06d83e
Update README.md
May 5, 2021
6a36c79
Update helper.py
May 5, 2021
b9c8f50
Update helper.py
May 5, 2021
cf5deeb
Update helper.py
May 5, 2021
e23c136
Update colormatch.py
May 18, 2021
ac4ed48
Update colormatch.py
May 18, 2021
f1f2548
Update colormatch.py
May 18, 2021
4a251b6
Update colormatch.py
May 18, 2021
9617546
Update colormatch.py
May 18, 2021
7f89b2d
Update colormatch.py
May 18, 2021
914b09e
Update colormatch.py
May 19, 2021
91b767c
Update colormatch.py
May 19, 2021
1992a47
Update colormatch.py
May 19, 2021
c8b1864
Update colormatch.py
May 19, 2021
91c3e4e
Update colormatch.py
May 19, 2021
8e8845e
reduce Warnings and clean up update script
May 19, 2021
169276e
Update README.md
May 19, 2021
781dacb
Update README.md
May 19, 2021
15c5fcc
Update README.md
May 19, 2021
a12cc11
update to install instructions
May 19, 2021
072da2d
Re-written temp logic
May 21, 2021
108928e
syntax bug
May 21, 2021
8cc2028
Monitor brightness fix
May 21, 2021
1e1ca4e
Run from port 80
May 21, 2021
6487cb7
Update README.md
May 23, 2021
e13b98e
Update svc_usb.py
May 23, 2021
576238e
Merge pull request #1 from dadr/Philip
Jun 8, 2021
cf1e744
Update README.md
Jun 8, 2021
c6e4581
Update Readme
Jun 8, 2021
7f31f27
Update Readme
Jun 8, 2021
2c6ff44
point repo to mrworf
Jun 8, 2021
8a1c664
Update README.md
Jun 8, 2021
5ffb18a
Screen Standby
Jun 9, 2021
fba4690
Update README.md
Jun 9, 2021
3cc375a
Update README.md
Jun 9, 2021
e7baad4
Update maintenance.py
Jun 11, 2021
8dfee5b
screen buttons
Jun 11, 2021
9ee63d6
Update maintenance.py
Jun 11, 2021
d4142df
Merge pull request #2 from dadr/feature/screen-off-url
Jun 12, 2021
f43294f
Update README.md
Jun 12, 2021
a3b205a
Finish feature
Jun 12, 2021
9afc531
Bugfix
Jun 12, 2021
db9057d
Update main.js
Jun 12, 2021
92c1cce
Update main.js
Jun 12, 2021
422bbc3
Return web pages
Jun 12, 2021
e0aa548
Update maintenance.py
Jun 12, 2021
df90112
Update maintenance.py
Jun 12, 2021
e736bd5
Merge branch 'python3' into feature/backup
Jun 12, 2021
62dad93
Merge pull request #3 from dadr/feature/backup
Jun 12, 2021
2e94072
Update README.md
Jun 14, 2021
ec390d9
Update base.py
Jun 17, 2021
3732562
Work with files
Jun 17, 2021
2e52fa7
Update main.js
Jun 17, 2021
7794c9b
Update upload.py
Jun 17, 2021
bbd453e
Update maintenance.py
Jun 17, 2021
9387079
Finish Download
Jun 17, 2021
936764c
Upload config changes
Jun 17, 2021
b73f91f
Style adjust
Jun 17, 2021
70d86c6
Update upload.py
Jun 17, 2021
8883555
Update upload.py
Jun 17, 2021
228331e
restart
Jun 17, 2021
29db0b7
Merge pull request #4 from dadr/feature/backup
Jun 17, 2021
b5d9ae7
Update main.html
Jun 17, 2021
566254b
Update upload.py
Jun 17, 2021
65e5d11
Update main.js
Jun 17, 2021
2f96e5c
Merge pull request #5 from dadr/feature/backup
Jun 17, 2021
d532232
Update main.html
Jun 18, 2021
61d09c3
Update main.html
Jun 18, 2021
fbf4653
Config Page Formatting
Jun 18, 2021
020e6ef
Update main.html
Jun 18, 2021
c3e6f01
Update main.html
Jun 18, 2021
38b97d9
Update main.html
Jun 18, 2021
d33f8db
Merge pull request #6 from dadr/feature/Configuration
Jun 18, 2021
7a74b7f
Initial partial commit
Jul 5, 2021
2ac8c75
Second set of work
Jul 8, 2021
81d5977
Rework restore to run as separate process.
Aug 17, 2021
96c993a
Create Version File
Jan 10, 2022
a584370
Merge pull request #8 from dadr/feature/backup
dadr Jan 10, 2022
b215564
Final Bugfixes for backup feature
Jan 17, 2022
ad2a43e
Merge pull request #9 from dadr/feature/backup
dadr Jan 17, 2022
42e5130
Last Bugfixes
Jan 17, 2022
da233e9
Merge pull request #10 from dadr/feature/backup
dadr Jan 17, 2022
6e57772
Error checking on tar
Jan 18, 2022
8ca44ca
Merge pull request #11 from dadr/feature/backup
dadr Jan 18, 2022
471c4f8
Indent error
Jan 18, 2022
95ecaa1
Merge pull request #12 from dadr/feature/backup
dadr Jan 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,4 @@ ENV/

# from developer leogaube
/useful_commands
.DS_Store
9 changes: 0 additions & 9 deletions MIGRATION.md

This file was deleted.

275 changes: 228 additions & 47 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
parser = argparse.ArgumentParser(description="PhotoFrame - A RaspberryPi based digital photoframe",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--logfile', default=None, help="Log to file instead of stdout")
parser.add_argument('--port', default=7777, type=int, help="Port to listen on")
parser.add_argument('--port', default=80, type=int, help="Port to listen on")
parser.add_argument('--countdown', default=10, type=int, help="Set seconds to countdown before starting slideshow")
parser.add_argument('--listen', default="0.0.0.0", help="Address to listen on")
parser.add_argument('--debug', action='store_true', default=False, help='Enable loads more logging')
Expand Down Expand Up @@ -143,7 +143,7 @@ def setupWebserver(self, listen, port):
self._loadRoute('keywords', 'RouteKeywords', self.serviceMgr, self.slideshow)
self._loadRoute('orientation', 'RouteOrientation', self.cacheMgr)
self._loadRoute('overscan', 'RouteOverscan', self.cacheMgr)
self._loadRoute('maintenance', 'RouteMaintenance', self.emulator, self.driverMgr, self.slideshow)
self._loadRoute('maintenance', 'RouteMaintenance', self.emulator, self.driverMgr, self.slideshow, self.timekeeperMgr)
self._loadRoute('details', 'RouteDetails', self.displayMgr, self.driverMgr,
self.colormatch, self.slideshow, self.serviceMgr, self.settingsMgr)
self._loadRoute('upload', 'RouteUpload', self.settingsMgr, self.driverMgr)
Expand Down
210 changes: 154 additions & 56 deletions modules/colormatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import smbus
import time
import os
import re
import subprocess
import logging
from . import debug


class colormatch(Thread):
Expand All @@ -27,8 +29,18 @@ def __init__(self, script, min=None, max=None):
self.daemon = True
self.sensor = False
self.temperature = None
self.default_temp = 3350.0
self.lux = None
self.sensor_scale = 6.0 # ToDo - set this from Configuration - This is (GA) from AMS App note DN40
self.lux_scale = 1.0 # ToDo - set this from Configuration - Monitor Brightness = lux * lux_scale
self.script = script
self.mon_adjust = False
self.mon_min_bright = 0.0 # Can't get this through ddc - assume 0
self.mon_max_bright = 0.0
self.mon_min_temp = 0.0
self.mon_max_temp = 0.0
self.mon_temp_inc = 0.0
self.mon_max_inc = 126.0 # Can't get this through ddc - has to be set by hand for now
self.void = open(os.devnull, 'wb')
self.min = min
self.max = max
Expand All @@ -38,6 +50,40 @@ def __init__(self, script, min=None, max=None):
self.hasScript = os.path.exists(self.script)
else:
self.hasScript = False
if os.path.exists("/usr/bin/ddcutil") and os.path.exists("/dev/i2c-2"):
# Logic to read monitor adjustment ranges and increments from ddc channel
# This is written assuming the monitor is a HP Z24i - If the regex strings differ for other
# monitors, then this section may need to be replicated and "ddcutil detect" used to
# determine the monitor type. At that point, it may be better to have a new module -
# similar to self.script - or a data file/structure can be created with the regex expressions for
# various monitors
self.mon_adjust = True
try:
temp_str = debug.subprocess_check_output(['/usr/bin/ddcutil', 'getvcp', '0B'])
self.mon_temp_inc = int(re.search('([0-9]*) (degree)', temp_str).group(1))
logging.debug('Monitor temp increment is %i' % self.mon_temp_inc)
except:
logging.exception('ddcutil is present but not getting temp increment from monitor. ')
self.mon_adjust = False
try:
temp_str = debug.subprocess_check_output(['/usr/bin/ddcutil', 'getvcp', '0C'])
self.mon_min_temp = int(re.search('([0-9]*) (\\+)', temp_str).group(1))
logging.debug('Monitor min temp is %i' % self.mon_min_temp)
except:
logging.exception('ddcutil is present but not getting min temp status from monitor. ')
self.mon_adjust = False
try:
temp_str = debug.subprocess_check_output(['/usr/bin/ddcutil', 'getvcp', '10'])
self.mon_max_bright = int(re.search('(max value \\= *) ([0-9]*)', temp_str).group(2))
logging.debug('Monitor max brightness is %i' % self.mon_max_bright)
except:
logging.exception('ddcutil is present but not getting brightness info from monitor')
self.mon_adjust = False
if self.mon_adjust == True:
logging.info('Monitor adjustments enabled')
else:
logging.debug('/usr/bin/ddcutil or /dev/i2c-2 not found - cannot adjust monitor')
self.mon_adjust = False

self.start()

Expand Down Expand Up @@ -66,7 +112,7 @@ def setUpdateListener(self, listener):
def adjust(self, filename, filenameTemp, temperature=None):
if not self.allowAdjust or not self.hasScript:
return False

if self.temperature is None or self.sensor is None:
logging.debug('Temperature is %s and sensor is %s', repr(self.temperature), repr(self.sensor))
return False
Expand All @@ -92,48 +138,82 @@ def adjust(self, filename, filenameTemp, temperature=None):
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
# is under MIT license, this license ONLY applies to said function and no
# other part of this project.
#
# The MIT License (MIT)
#
# Copyright (c) 2017 Tony DiCola for Adafruit Industries
def adjustableMonitor(self):
return self.mon_adjust

def setMonBright(self):
brightness = self.lux * self.lux_scale
if brightness > self.mon_max_bright:
brightness = self.mon_max_bright
if brightness < self.mon_min_bright:
brightness = self.mon_min_bright
try:
debug.subprocess_call(['/usr/bin/ddcutil', 'setvcp', '10', repr(int(brightness))])
logging.debug('setMonBright set monitor to %s percent' % repr(int(brightness)))
except:
logging.debug('setMonBright failed to set monitor to %s' % repr(int(brightness)))
return False
return True

def setMonTemp(self):
temp = self.temperature
if self.max:
if temp > self.max:
temp = self.max
if self.min:
if temp < self.min:
temp = self.min
if temp > self.mon_max_temp:
temp = self.mon_max_temp
if temp < self.mon_min_temp:
temp = self.mon_min_temp
tempset = int((temp - self.mon_min_temp)/self.mon_temp_inc)
if tempset > self.mon_max_inc:
tempset = self.mon_max_inc
try:
debug.subprocess_call(['/usr/bin/ddcutil', 'setvcp', '0C', repr(tempset)])
logging.debug('setMonTempt set monitor to %s temp' % repr(temp))
except:
logging.debug('setMonTemp failed to set monitor to %s' % repr(temp))
return False
return True

###################################################################################
# It seems that there is a widespread sharing of incorrect code to read the TCS3472.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The following is based on the equations and coefficients provided by the manufacturer
# (AMS) in their Application Note DN40-Rev 1.0 Appendix I
# While it's hard to test this without expensive equipment, I was able to get a temp reading
# very close to 2700K using LED lighting from a bulb with the same spec.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# Note, if a sensor other than a TCS3472 is used, these will no longer be correct.
# However this is correct for TCS34721, TCS34723, TCS34725 and TCS34727
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
def _temperature_and_lux(self, data):
"""Convert the 4-tuple of raw RGBC data to color temperature and lux values. Will return
2-tuple of color temperature and lux."""
r, g, b, _ = data
x = -0.14282 * r + 1.54924 * g + -0.95641 * b
y = -0.32466 * r + 1.57837 * g + -0.73191 * b
z = -0.68202 * r + 0.77073 * g + 0.56332 * b
divisor = x + y + z
n = (x / divisor - 0.3320) / (0.1858 - y / divisor)
cct = 449.0 * n**3 + 3525.0 * n**2 + 6823.3 * n + 5520.33
return cct, y
###################################################################################

# This function is mostly based of the example provided by Brad Berkland's blog:
# http://bradsrpi.blogspot.com/2013/05/tcs34725-rgb-color-sensor-raspberry-pi.html
# Finally, the vairable sensor_scale is used to compensate for light attenuation for sensors
# behind glass, or in some cases behind acrylic rods.
#
def _temperature_and_lux(self, r, g, b, c, tms_gain, tms_atime):
itime = (256 - tms_atime) * 2.4
gain_table = {0: 1, 1: 4, 2: 16, 3: 60}
gain = gain_table[tms_gain]
if (self.sensor_scale <= 0):
cpl = 1.0 # protect from bad settings
else:
cpl = (gain * itime) / (self.sensor_scale * 310)
ir = float(r + g + b - c)/2
rp = float(r) - ir
gp = float(g) - ir
bp = float(b) - ir
lux = ((.136 * rp) + gp - (.444 * bp))/cpl
if (lux < 0):
lux = 0
if (rp == 0):
temp = self.default_temp
else:
temp = (3810 * ( bp / rp )) + 1391
return temp, lux


def run(self):
try:
bus = smbus.SMBus(1)
Expand All @@ -149,41 +229,59 @@ def run(self):
logging.info('ColorSensor not available')
return
ver = bus.read_byte(0x29)
# version # should be 0x44
if ver == 0x44:
# version # should be 0x44 or 0x4D
if (ver == 0x44) or (ver == 0x4D):
# Make sure we have the needed script
if not os.path.exists(self.script):
if not (os.path.exists(self.script) or self.mon_adjust):
logging.info(
'No color temperature script, download it from '
'No color temperature script or adjustable monitor detected, download the script from '
'http://www.fmwconcepts.com/imagemagick/colortemp/index.php and save as "%s"' % self.script
)
self.allowAdjust = False
self.allowAdjust = True

else:
self.allowAdjust = True
self.sensor = True

# Configure Sensor
# Todo - The app note suggests checking the clear value, and if it's less than 100,
# to change the gain to increase sensitivity. That's not yet implemented here.
# Lok here for algorithm:
# https://ams.com/documents/20143/36005/AmbientLightSensors_AN000171_2-00.pdf/9d1f1cd6-4b2d-1de7-368f-8b372f3d8517
#
tms_gain = 0x1 # Must be 0, 1, 2, 3 which become 1x, 4x, 16x, and 60x gain
tms_atime = 0x0 # Must be 0xFF, 0xF6, 0xDB, 0xC0, 0x00 which become 2.4, 24, 101, 154, 700ms
tms_wtime = 0xFF # Must be 0xFF, 0xAB, 0x00 which become 2.4, 204 614ms

bus.write_byte(0x29, 0x80 | 0x00) # 0x00 = ENABLE register
bus.write_byte(0x29, 0x01 | 0x02) # 0x01 = Power on, 0x02 RGB sensors enabled
bus.write_byte(0x29, 0x80 | 0x01) # 0x01 = ATIME (Integration time) register
bus.write_byte(0x29, tms_atime)
bus.write_byte(0x29, 0x80 | 0x03) # 0x03 = WTIME (Wait) register
bus.write_byte(0x29, tms_wtime)
bus.write_byte(0x29, 0x80 | 0x0F) # 0x0F = Control register
bus.write_byte(0x29, tms_gain)


bus.write_byte(0x29, 0x80 | 0x14) # Reading results start register 14, LSB then MSB
self.sensor = True
logging.debug('TCS34725 detected, starting polling loop')
while True:
data = bus.read_i2c_block_data(0x29, 0)
clear = clear = data[1] << 8 | data[0]
red = data[3] << 8 | data[2]
green = data[5] << 8 | data[4]
blue = data[7] << 8 | data[6]
if red > 0 and green > 0 and blue > 0 and clear > 0:
temp, lux = self._temperature_and_lux((red, green, blue, clear))
self.temperature = temp
self.lux = lux
else:
# All zero Happens when no light is available, so set temp to zero
self.temperature = 0
self.lux = 0


self.temperature, self.lux = self._temperature_and_lux(red, green, blue, clear, tms_gain, tms_atime)

if self.listener:
self.listener(self.temperature, self.lux)

if self.mon_adjust:
logging.debug('Adjusting monitor to %3.2f lux and %5.0fK temp' % (self.lux, self.temperature))
self.setMonBright()
self.setMonTemp()

time.sleep(1)
time.sleep(15) # Not sure how often it's acceptable to change monitor settings
else:
logging.info('No TCS34725 color sensor detected, will not compensate for ambient color temperature')
self.sensor = False
12 changes: 8 additions & 4 deletions modules/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ def _stringify(args):

def subprocess_call(cmds, stderr=None, stdout=None):
return subprocess.call(cmds, stderr=stderr, stdout=stdout)

# TODO Relocate to helper? Convert to subprocess.run? Add exception to collect output
# in an error log. Add debug logging option as well? Add **kwargs

def subprocess_check_output(cmds, stderr=None):
return subprocess.check_output(cmds, stderr=stderr).decode("utf-8")

# TODO basically same treatment as suborocess_call. Although, with using subprocess.run,
# check output or not is just another arg. So, this might just set that arg and subprocess_call.

def stacktrace():
title = 'Stacktrace of all running threads'
Expand All @@ -60,7 +62,8 @@ def logfile(all=False):
if all:
title = 'Last 100 lines from the system log (/var/log/syslog)'
cmd = 'tail -n 100 /var/log/syslog'
lines = subprocess.check_output(cmd, shell=True)
lines = subprocess.check_output(cmd, shell=True).decode("utf-8")
# TODO - convert this to use a common subprocess._check_output dunction
if lines:
lines = lines.splitlines()
suffix = '(size of logfile %d bytes, created %s)' % (stats.st_size,
Expand All @@ -70,7 +73,8 @@ def logfile(all=False):

def version():
title = 'Running version'
lines = subprocess.check_output('git log HEAD~1..HEAD ; echo "" ; git status', shell=True)
lines = subprocess.check_output('git log HEAD~1..HEAD ; echo "" ; git status', shell=True).decode("utf-8")
# TODO - convert this to use a common subprocess_check_output function
if lines:
lines = lines.splitlines()
return (title, lines, None)
4 changes: 1 addition & 3 deletions modules/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def message(self, message, showConfig=True):

url = 'caption:'
if helper.getDeviceIp() is not None and showConfig:
url = 'caption:Configuration available at http://%s:7777' % helper.getDeviceIp()
url = 'caption:Configuration available at http://%s' % helper.getDeviceIp()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to have to fix this later so it pulls that info from the settings 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking at this, and thought it might be a simple fix (copying the server port from the code at line 175). But that code crashes when I use it here. I get a "server" is not defined when I try to use server.get_server_port(). Seems there may be a bug at 175 as well.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been thinking about this too, server will fail since it's not exposed to this namespace, an option is to have the server expose a hook that the helper registers to. But like I mentioned, this doesn't have to be solved at this time since the default is 80 and anyone changing will hopefully also know what they're doing 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about frame.py? It parses the cmd line args and sets the port to start the server. So, instead of asking the server what port it is at, we collect the information at the start of the program and make it available with all the other base configuration variables?


args = [
'convert',
Expand Down Expand Up @@ -229,8 +229,6 @@ def image(self, filename):
args = [
'convert',
filename + '[0]',
'-resize',
'%dx%d' % (self.width, self.height),
'-background',
'black',
'-gravity',
Expand Down
Loading