Skip to content

Commit 9aa545c

Browse files
committed
Update and modernize project.
1 parent 7d7dea4 commit 9aa545c

File tree

18 files changed

+271
-242
lines changed

18 files changed

+271
-242
lines changed

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: CI
2+
3+
on: [push, pull_request, workflow_dispatch]
4+
5+
jobs:
6+
main:
7+
name: Python ${{ matrix.python }}
8+
runs-on: ubuntu-20.04
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
python: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy-3.7", "pypy-3.8"]
13+
steps:
14+
- uses: actions/checkout@v3
15+
- uses: actions/setup-python@v4
16+
with:
17+
python-version: ${{ matrix.python }}
18+
- run: python -m pip install coverage tox
19+
- run: python -m tox
20+
- uses: codecov/codecov-action@v3
21+
with:
22+
name: ${{ matrix.python }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
*.egg-info
2+
*.log
23
*.pyc
34
*.swp
45
.cache/

.travis.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015-2017 Thomas Kemmer
3+
Copyright (c) 2015-2022 Thomas Kemmer
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy of
66
this software and associated documentation files (the "Software"), to deal in

README.rst

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,27 @@ carreralib
55
:target: https://pypi.org/project/carreralib/
66
:alt: Latest PyPI version
77

8+
.. image:: https://img.shields.io/github/workflow/status/tkem/carreralib/CI
9+
:target: https://github.com/tkem/carreralib/actions/workflows/ci.yml
10+
:alt: CI build status
11+
812
.. image:: https://img.shields.io/readthedocs/carreralib
913
:target: http://carreralib.readthedocs.io/
1014
:alt: Documentation build status
1115

12-
.. image:: http://img.shields.io/travis/tkem/carreralib
13-
:target: https://travis-ci.org/tkem/carreralib/
14-
:alt: Travis CI build status
15-
16-
.. image:: http://img.shields.io/coveralls/tkem/carreralib
17-
:target: https://coveralls.io/r/tkem/carreralib
16+
.. image:: https://img.shields.io/codecov/c/github/tkem/carreralib/master.svg
17+
:target: https://codecov.io/gh/tkem/carreralib
1818
:alt: Test coverage
1919

20-
.. image:: https://img.shields.io/github/license/tkem/carreralib
21-
:target: http://raw.github.com/tkem/carreralib/master/LICENSE
20+
.. image:: https://img.shields.io/github/license/tkem/cachetools
21+
:target: https://raw.github.com/tkem/cachetools/master/LICENSE
2222
:alt: License
2323

24+
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
25+
:target: https://github.com/psf/black
26+
:alt: Code style: black
27+
28+
2429
This module provides a Python interface to Carrera(R) DIGITAL 124/132
2530
slotcar systems connected via serial port or Bluetooth.
2631

@@ -76,12 +81,12 @@ Project Resources
7681
License
7782
------------------------------------------------------------------------
7883

79-
Copyright (c) 2015-2020 Thomas Kemmer.
84+
Copyright (c) 2015-2022 Thomas Kemmer.
8085

8186
Licensed under the `MIT License`_.
8287

83-
Carrera and Carrera AppConnect are registered trademarks of Stadlbauer
84-
Marketing + Vertrieb GmbH.
88+
Carrera and Carrera AppConnect are registered trademarks of Carrera
89+
Toys GmbH.
8590

8691
Thanks to Stephan Heß (a.k.a. slotbaer_) for doing all the hard work.
8792

carreralib/__init__.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

docs/conf.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
1+
import pathlib
2+
import sys
3+
4+
basedir = pathlib.Path(__file__).parent.parent
5+
6+
sys.path.insert(0, str((basedir / "src").resolve()))
7+
8+
19
def get_version():
210
import configparser
3-
import pathlib
411

512
cp = configparser.ConfigParser()
6-
# Python 3.5 ConfigParser does not accept Path as filename
7-
cp.read(str(pathlib.Path(__file__).parent.parent / "setup.cfg"))
13+
cp.read(basedir / "setup.cfg")
814
return cp["metadata"]["version"]
915

1016

11-
project = 'carreralib'
12-
copyright = '2015-2020 Thomas Kemmer'
17+
project = "carreralib"
18+
copyright = "2015-2022 Thomas Kemmer"
1319
version = get_version()
1420
release = version
1521

1622
extensions = [
17-
'sphinx.ext.autodoc',
18-
'sphinx.ext.coverage',
19-
'sphinx.ext.doctest',
20-
'sphinx.ext.todo'
23+
"sphinx.ext.autodoc",
24+
"sphinx.ext.coverage",
25+
"sphinx.ext.doctest",
26+
"sphinx.ext.todo",
2127
]
22-
exclude_patterns = ['_build']
23-
master_doc = 'index'
24-
html_theme = 'default'
28+
exclude_patterns = ["_build"]
29+
master_doc = "index"
30+
html_theme = "default"

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[build-system]
2+
requires = ["setuptools >= 46.4.0", "wheel"]
3+
build-backend = "setuptools.build_meta"

setup.cfg

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = carreralib
3-
version = 0.7.0
3+
version = attr: carreralib.__version__
44
url = https://github.com/tkem/carreralib/
55
author = Thomas Kemmer
66
author_email = [email protected]
@@ -13,26 +13,37 @@ classifiers =
1313
Environment :: Other Environment
1414
Intended Audience :: Developers
1515
License :: OSI Approved :: MIT License
16-
Operating System :: POSIX :: Linux
16+
Operating System :: POSIX
17+
Operating System :: Microsoft :: Windows
18+
Operating System :: MacOS :: MacOS X
1719
Programming Language :: Python
1820
Programming Language :: Python :: 3
19-
Programming Language :: Python :: 3.5
20-
Programming Language :: Python :: 3.6
2121
Programming Language :: Python :: 3.7
2222
Programming Language :: Python :: 3.8
23+
Programming Language :: Python :: 3.9
24+
Programming Language :: Python :: 3.10
25+
Programming Language :: Python :: 3.11
2326
Topic :: Software Development :: Libraries :: Python Modules
2427

2528
[options]
29+
package_dir =
30+
= src
2631
packages = find:
27-
python_requires = ~= 3.5
32+
install_requires =
33+
pyserial
34+
python_requires = ~= 3.7
2835

2936
[options.packages.find]
30-
exclude =
31-
tests
32-
tests.*
37+
where = src
3338

3439
[flake8]
35-
exclude = .git, .tox
40+
max-line-length = 80
41+
exclude = .git, .tox, build
42+
select = C, E, F, W, B, B950, I, N
43+
# B008: function calls in argument defaults
44+
# E203: whitespace before ':' (black)
45+
# E501: line too long (black)
46+
ignore = B008, E203, E501
3647

3748
[build_sphinx]
3849
source-dir = docs/

src/carreralib/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Python interface to Carrera(R) DIGITAL 124/132 slotcar systems."""
2+
3+
from . import connection
4+
from . import protocol
5+
from .cu import ControlUnit
6+
7+
__all__ = ("ControlUnit", "connection", "protocol")
8+
9+
__version__ = "0.7.0"

carreralib/__main__.py renamed to src/carreralib/__main__.py

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import unicode_literals
2-
31
import argparse
42
import contextlib
53
import curses
@@ -17,27 +15,31 @@ def posgetter(driver):
1715

1816
def formattime(time, longfmt=False):
1917
if time is None:
20-
return 'n/a'
18+
return "n/a"
2119
s = time // 1000
2220
ms = time % 1000
2321

2422
if not longfmt:
25-
return '%d.%03d' % (s, ms)
23+
return "%d.%03d" % (s, ms)
2624
elif s < 3600:
27-
return '%d:%02d.%03d' % (s // 60, s % 60, ms)
25+
return "%d:%02d.%03d" % (s // 60, s % 60, ms)
2826
else:
29-
return '%d:%02d:%02d.%03d' % (s // 3600, (s // 60) % 60, s % 60, ms)
27+
return "%d:%02d:%02d.%03d" % (s // 3600, (s // 60) % 60, s % 60, ms)
3028

3129

3230
class RMS(object):
3331

34-
HEADER = 'Pos No Time Lap time Best lap Laps Pit Fuel'
35-
FORMAT1 = ('{pos:<4}#{car:<2}{time:>12}{laptime:>10}{bestlap:>10}' +
36-
'{laps:>5}{pits:>4}{fuel:>5.0%}')
37-
FORMAT2 = ('{pos:<4}#{car:<2}{time:>12}{laptime:>10}{bestlap:>10}' +
38-
'{laps:>5} n/a n/a')
39-
FOOTER1 = ' * * * * * SPACE to start/pause, ESC for pace car'
40-
FOOTER2 = ' [R]eset, [S]peed, [B]rake, [F]uel, [C]ode, [Q]uit'
32+
HEADER = "Pos No Time Lap time Best lap Laps Pit Fuel"
33+
FORMAT1 = "%s%s" % (
34+
"{pos:<4}#{car:<2}{time:>12}{laptime:>10}{bestlap:>10}",
35+
"{laps:>5}{pits:>4}{fuel:>5.0%}",
36+
)
37+
FORMAT2 = "%s%s" % (
38+
"{pos:<4}#{car:<2}{time:>12}{laptime:>10}{bestlap:>10}",
39+
"{laps:>5} n/a n/a",
40+
)
41+
FOOTER1 = " * * * * * SPACE to start/pause, ESC for pace car"
42+
FOOTER2 = " [R]eset, [S]peed, [B]rake, [F]uel, [C]ode, [Q]uit"
4143

4244
# CU reports zero fuel for all cars unless pit lane adapter is connected
4345
# FUEL_MASK = ControlUnit.Status.FUEL_MODE | ControlUnit.Status.REAL_MODE
@@ -90,21 +92,21 @@ def run(self):
9092
try:
9193
self.update()
9294
c = self.window.getch()
93-
if c == ord('q'):
95+
if c == ord("q"):
9496
break
95-
elif c == ord('r'):
97+
elif c == ord("r"):
9698
self.reset()
97-
elif c == ord(' '):
99+
elif c == ord(" "):
98100
self.cu.start()
99-
elif (c == 27): # ESC
101+
elif c == 27: # ESC
100102
self.cu.request(ControlUnit.PACE_CAR_KEY)
101-
elif c == ord('s'):
103+
elif c == ord("s"):
102104
self.cu.request(ControlUnit.SPEED_KEY)
103-
elif c == ord('b'):
105+
elif c == ord("b"):
104106
self.cu.request(ControlUnit.BRAKE_KEY)
105-
elif c == ord('f'):
107+
elif c == ord("f"):
106108
self.cu.request(ControlUnit.FUEL_KEY)
107-
elif c == ord('c'):
109+
elif c == ord("c"):
108110
self.cu.request(ControlUnit.CODE_KEY)
109111
data = self.cu.request()
110112
# prevent counting duplicate laps
@@ -115,7 +117,7 @@ def run(self):
115117
elif isinstance(data, ControlUnit.Timer):
116118
self.handle_timer(data)
117119
else:
118-
logging.warn('Unknown data from CU: ' + data)
120+
logging.warn("Unknown data from CU: " + data)
119121
last = data
120122
except select.error:
121123
pass
@@ -166,47 +168,56 @@ def update(self, blink=lambda: (time.time() * 2) % 2 == 0):
166168
leader = driver
167169
t = formattime(driver.time - self.start, True)
168170
elif driver.laps == leader.laps:
169-
t = '+%ss' % formattime(driver.time - leader.time)
171+
t = "+%ss" % formattime(driver.time - leader.time)
170172
else:
171173
gap = leader.laps - driver.laps
172-
t = '+%d Lap%s' % (gap, 's' if gap != 1 else '')
174+
t = "+%d Lap%s" % (gap, "s" if gap != 1 else "")
173175
if (self.status.mode & self.FUEL_MASK) != 0:
174176
text = self.FORMAT1.format(
175-
pos=pos, car=driver.num, time=t, laps=driver.laps,
177+
pos=pos,
178+
car=driver.num,
179+
time=t,
180+
laps=driver.laps,
176181
laptime=formattime(driver.laptime),
177182
bestlap=formattime(driver.bestlap),
178-
fuel=driver.fuel/15.0,
179-
pits=driver.pits
183+
fuel=driver.fuel / 15.0,
184+
pits=driver.pits,
180185
)
181186
else:
182187
text = self.FORMAT2.format(
183-
pos=pos, car=driver.num, time=t, laps=driver.laps,
188+
pos=pos,
189+
car=driver.num,
190+
time=t,
191+
laps=driver.laps,
184192
laptime=formattime(driver.laptime),
185-
bestlap=formattime(driver.bestlap)
193+
bestlap=formattime(driver.bestlap),
186194
)
187195
window.addnstr(pos, 0, text, ncols)
188196
window.refresh()
189197

190198

191-
parser = argparse.ArgumentParser(prog='python -m carreralib')
192-
parser.add_argument('device', metavar='DEVICE')
193-
parser.add_argument('-l', '--logfile', default='carreralib.log')
194-
parser.add_argument('-t', '--timeout', default=1.0, type=float)
195-
parser.add_argument('-v', '--verbose', action='store_true')
199+
parser = argparse.ArgumentParser(prog="python -m carreralib")
200+
parser.add_argument("device", metavar="DEVICE")
201+
parser.add_argument("-l", "--logfile", default="carreralib.log")
202+
parser.add_argument("-t", "--timeout", default=1.0, type=float)
203+
parser.add_argument("-v", "--verbose", action="store_true")
196204
args = parser.parse_args()
197205

198-
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.WARN,
199-
filename=args.logfile,
200-
format='%(asctime)s: %(message)s')
206+
logging.basicConfig(
207+
level=logging.DEBUG if args.verbose else logging.WARN,
208+
filename=args.logfile,
209+
format="%(asctime)s: %(message)s",
210+
)
201211

202212
with contextlib.closing(ControlUnit(args.device, timeout=args.timeout)) as cu:
203-
print('CU version %s' % cu.version())
213+
print("CU version %s" % cu.version())
204214

205215
def run(win):
206216
curses.curs_set(0)
207217
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
208218
rms = RMS(cu, win)
209219
rms.run()
220+
210221
try:
211222
curses.wrapper(run)
212223
except KeyboardInterrupt:

0 commit comments

Comments
 (0)