Skip to content

Commit

Permalink
Merge pull request #305 from Bristol-Braille/304-add-configuration
Browse files Browse the repository at this point in the history
Add additional configuration for use on the canute console
  • Loading branch information
woodcoder authored Feb 9, 2024
2 parents 632d296 + ad96a60 commit 1c4e590
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 122 deletions.
48 changes: 32 additions & 16 deletions config.rc.in
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
[files]
# relative path to where log is kept
log_file = canute.log
# book directory
log_file = 'canute.log'

# where usb sticks and sd-cards will be automounted
media_dir = /media
# the exact sub mount-point directory name of the sd-card
sd_card_dir = sd-card

# Additional books made available on the USB ports will (for now at
# least) be visible in the library. The current state will be written
# to all mount points that are active, however the state file on the
# SD card will take precedence, followed by lib_1 and finally lib_2.
# These behaviours may change at any time so shouldn't be relied upon.
#
# These paths are relative to media_dir.
# These config values are optional.
additional_lib_1 = front-usb
additional_lib_2 = back-usb
media_dir = '/media'

# Book Directories
# Additional books made available on the USB ports will be made visible
# in the library. The current state will be written to all mount points
# that are active. The first state file found (based on the order in the
# list) will take precedence if they differ.
# These paths can be relative to media_dir or absolute. The mountpoint
# flag should be used if they point to mountpoints that may not be present
# and the swappable flag means USB media that is interchangeable.
library = [
{ name = 'SD', path = 'sd-card', mountpoint = true },
{ name = 'USB1', path = 'front-usb', mountpoint = true, swappable = true },
{ name = 'USB2', path = 'back-usb', mountpoint = true, swappable = true }
]

# if required, a program to look for changes to removable media
media_helper = './media.py'

[comms]
# serial timeout in seconds
timeout = 1000

[hardware]
# count row actuations (motor wear) when running on Canute 360
log_duty = true

# minumum number of samples of button down to count as a press
button_debounce = 1

# shutdown mode
shutdown_on_exit = true

# versions
13 changes: 9 additions & 4 deletions config.rc.travis
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
[files]
# relative path to where log is kept
log_file = canute.log
# book directory
# where usb sticks and sd-cards will be automounted
media_dir = ~/canute-media
# the exact sub mount-point directory name of the sd-card
sd_card_dir = sd-card

# Book Directories
# Additional books made available on the USB ports will be made visible
# in the library. The current state will be written to all mount points
# that are active. The first state file found (based on the order in the
# list) will take precedence if they differ.
library = [
{ name = 'SD', path = 'sd-card' }
]

[comms]
# serial timeout in seconds
Expand Down
10 changes: 5 additions & 5 deletions tests/test_book_file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest
from ui.book.book_file import BookFile
from ui.book.handlers import _read_pages, get_page_data
from ui.book.handlers import _read_pages2, get_page_data

from .util import async_test

Expand All @@ -12,7 +12,7 @@ async def setUpClass(self):
self.filename = ('books/A_balance_between_technology_and_Braille_Addin'
+ 'g_Value_and_Creating_a_Love_of_Reading.BRF')
book = BookFile(self.filename, 40, 9)
self.book = await _read_pages(book)
self.book = await _read_pages2(book)

def test_filename(self):
self.assertEqual(self.book.filename, self.filename)
Expand All @@ -21,7 +21,7 @@ def test_title(self):
self.assertIsNotNone(self.book.title)

def test_has_len(self):
self.assertGreater(len(self.book.pages), 0)
self.assertGreater(self.book.num_pages, 0)

@async_test
async def test_get_line(self):
Expand All @@ -45,7 +45,7 @@ class TestBookFilePef(unittest.TestCase):
async def setUpClass(self):
self.filename = "books/g2 AESOP'S FABLES.pef"
book = BookFile(self.filename, 40, 9)
self.book = await _read_pages(book)
self.book = await _read_pages2(book)

def test_filename(self):
self.assertEqual(self.book.filename, self.filename)
Expand All @@ -54,7 +54,7 @@ def test_title(self):
self.assertIsNotNone(self.book.title)

def test_has_len(self):
self.assertGreater(len(self.book.pages), 0)
self.assertGreater(self.book.num_pages, 0)

@async_test
async def test_get_line(self):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ui.library.explorer import Library

dir_path = os.path.dirname(os.path.realpath(__file__))
test_books_dir = [('test-books', 'test dir')]
test_books_dir = [{ 'path': 'test-books', 'name': 'test dir' }]


class TestUtility(unittest.TestCase):
Expand Down
22 changes: 11 additions & 11 deletions ui/config_loader.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import os.path
from configparser import ConfigParser

config_file = 'config.rc'
import toml

config_file = 'config.rc'

def load(config_file=config_file):
config = ConfigParser()
c = config.read(config_file)
if len(c) == 0:
if not os.path.exists(config_file):
raise ValueError('Please provide a config.rc')
media_dir = config.get('files', 'media_dir')
config.set('files', 'media_dir', os.path.expanduser(media_dir))
if not config.has_section('comms'):
config.add_section('comms')
if not config.has_option('comms', 'timeout'):
config.set('comms', 'timeout', 60)

config = toml.load(config_file)

# expand any ~ home dirs in media_dir
files_section = config.get('files', {})
media_dir = files_section.get('media_dir', '/media')
files_section['media_dir'] = os.path.expanduser(media_dir)

return config
23 changes: 13 additions & 10 deletions ui/driver/driver_pi.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ class Pi(Driver):
:param port: the serial port the display is plugged into
"""

def __init__(self, port=None, timeout=60):
def __init__(self, port=None, timeout=60, button_threshold=1):
self.timeout = timeout
self.button_threshold = button_threshold
# get serial connection
if port is None:
ports = serial.tools.list_ports.comports()
Expand All @@ -53,7 +54,7 @@ def __init__(self, port=None, timeout=60):
else:
self.port = None

self.previous_buttons = tuple()
self.previous_buttons = dict()

self.row_actuations = [0] * N_ROWS

Expand Down Expand Up @@ -121,17 +122,19 @@ def get_buttons(self):
buttons = {}
self.send_data(comms.CMD_SEND_BUTTONS)
read_buttons = self.get_data(comms.CMD_SEND_BUTTONS)
down = list(self.previous_buttons)
for i, n in enumerate(reversed(list('{:0>14b}'.format(read_buttons)))):
name = mapping[str(i)]
if n == '1' and (name not in self.previous_buttons):
buttons[name] = 'down'
down.append(name)
if n == '1':
if name in self.previous_buttons:
self.previous_buttons[name] += 1
else:
self.previous_buttons[name] = 1
if self.previous_buttons[name] == self.button_threshold:
buttons[name] = 'down'
elif n == '0' and (name in self.previous_buttons):
buttons[name] = 'up'
down.remove(name)

self.previous_buttons = tuple(down)
if self.previous_buttons[name] >= self.button_threshold:
buttons[name] = 'up'
del self.previous_buttons[name]

return buttons

Expand Down
73 changes: 49 additions & 24 deletions ui/initial_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,57 @@ def to_state_file(book_path):

def configured_source_dirs():
config = config_loader.load()
state_sources = [('sd_card_dir', 'SD')]
if config.has_option('files', 'additional_lib_1'):
state_sources.append(('additional_lib_1', 'USB1'))
if config.has_option('files', 'additional_lib_2'):
state_sources.append(('additional_lib_2', 'USB2'))
return [(config.get('files', source), name) for source, name in state_sources]
return config.get('files', {}).get('library', [])


def mounted_source_paths(media_dir):
for source_dir, name in configured_source_dirs():
source_path = os.path.join(media_dir, source_dir)
if os.path.ismount(source_path) or 'TRAVIS' in os.environ:
yield source_path
for source_dir in configured_source_dirs():
source_path = os.path.join(media_dir, source_dir.get('path'))
if not source_dir.get('mountpoint', False) or os.path.ismount(source_path):
yield source_path, source_dir.get('swappable', False)


def swappable_usb(data):
"""
The state file contains part of the book path (e.g. usb0) and so if we want
to maintain compatibility with the old standalone setup, we need to convert
it to something it understands. So here we fix any 'swappable' path (i.e.
USB removable media) to 'front-usb' and rely on the function below to find
the correct prefix on app startup.
"""
book = data.get('current_book')
for source_dir in configured_source_dirs():
if source_dir.get('swappable', False):
prefix = source_dir.get('path') + os.sep
if book.startswith(prefix):
book = os.path.join('front-usb', book[len(prefix):])
# modifying data is safe as a new dict is created each time
data.set('current_book', book)
break
return data


def swap_library(current_book):
def swap_library(current_book, books):
"""
The current_book path includes a mount-point subpath (if on removable media)
so try to cope with user accidentally swapping slots
"""
config = config_loader.load()
if config.has_option('files', 'additional_lib_1') and \
config.has_option('files', 'additional_lib_2'):
lib1 = config.get('files', 'additional_lib_1')
lib2 = config.get('files', 'additional_lib_2')
if current_book.startswith(lib1):
return lib2 + current_book[len(lib1):]
elif current_book.startswith(lib2):
return lib1 + current_book[len(lib2):]
return current_book
library = config.get('files', {}).get('library', [])

# check for expected path, for backward compatibility with standalone unit
for path in ['front-usb' + os.path.sep, 'back-usb' + os.path.sep]:
if current_book.startswith(path):
rel_book = current_book[len(path):]
break

# see if we can find it on a different swappable device path
for lib in library:
if lib.get('swappable', False):
path = lib['path']
book = os.path.join(path, rel_book)
if book in books:
return book


async def read_user_state(media_dir, state):
Expand All @@ -66,7 +91,7 @@ async def read_user_state(media_dir, state):
book_files = library.book_files()

source_paths = mounted_source_paths(media_dir)
for source_path in source_paths:
for source_path, swappable in source_paths:
main_toml = os.path.join(source_path, USER_STATE_FILE)
if os.path.exists(main_toml):
try:
Expand Down Expand Up @@ -129,7 +154,7 @@ async def read_user_state(media_dir, state):
if current_book not in books:
# let's check that they're not just using a different USB port
log.info('current book not in original library {}'.format(current_book))
current_book = swap_library(current_book)
current_book = swap_library(current_book, books)
if current_book not in books:
log.warn('current book not found {}, ignoring'.format(current_book))
current_book = manual_filename
Expand All @@ -148,12 +173,12 @@ async def write(media_dir, queue):
log.info('save file worker started')
while True:
(filename, data) = await queue.get()
s = toml.dumps(data)
s = toml.dumps(swappable_usb(data))

if filename is None:
# main user state file
source_paths = mounted_source_paths(media_dir)
for source_path in source_paths:
for source_path, swappable in source_paths:
path = os.path.join(source_path, USER_STATE_FILE)
log.debug('writing user state file save to {path}')
async with aiofiles.open(path, 'w') as f:
Expand Down
17 changes: 9 additions & 8 deletions ui/library/explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,15 @@ def __init__(self, media_dir, source_dirs, file_exts):
self.file_exts = file_exts
self.dirs = []

for source_dir, name in source_dirs:
# if os.path.ismount(os.path.join(self.media_dir, source_dir)):
# not needed as unmounted devices will be empty and so pruned
root = Directory(source_dir, display=name)
self.walk(root)
self.prune(root)
if len(root.dirs) > 0 or root.files_count > 0:
self.flatten(root)
for source_dir in source_dirs:
source_path = source_dir.get('path')
if not source_dir.get('mountpoint', False) or \
os.path.ismount(os.path.join(self.media_dir, source_path)):
root = Directory(source_path, display=source_dir.get('name'))
self.walk(root)
self.prune(root)
if len(root.dirs) > 0 or root.files_count > 0:
self.flatten(root)

self.dir_count = len(self.dirs)
self.files_dir_index = None
Expand Down
Loading

0 comments on commit 1c4e590

Please sign in to comment.