diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml new file mode 100644 index 0000000..17ccb09 --- /dev/null +++ b/.github/workflows/pr-lint.yml @@ -0,0 +1,81 @@ +name: PR Lint + +on: + pull_request: + +jobs: + prettier: + name: Prettier check + runs-on: ubuntu-latest + container: + image: node:lts-alpine + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install prettier + run: npm install --global prettier@latest + + - name: Run prettier check + run: | + prettier --check \ + --config .prettierrc.json \ + --ignore-path .prettierignore \ + "**/*.{js,json,md,yaml,yml}" + + qml-check: + name: QML format and lint check + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install PySide6 (provides qmllint and qmlformat) + run: pip install "PySide6==6.11.1" + + - name: Determine changed QML files + id: changed + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + base_ref="${{ github.base_ref }}" + git fetch --no-tags origin "$base_ref" + files=$(git diff --name-only --diff-filter=ACMR "origin/$base_ref"...HEAD -- '*.qml') + if [ -z "$files" ]; then + echo "No changed QML files in this PR." + echo "has_changes=false" >> "$GITHUB_OUTPUT" + else + echo "Changed QML files:" + echo "$files" + echo "has_changes=true" >> "$GITHUB_OUTPUT" + { + echo "files<> "$GITHUB_OUTPUT" + fi + + - name: Check qmlformat + if: steps.changed.outputs.has_changes == 'true' + run: | + echo "${{ steps.changed.outputs.files }}" | xargs pyside6-qmlformat -i + git diff --exit-code || { + echo "" + echo "qmlformat found formatting issues. Run locally to fix:" + echo " git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }}...HEAD -- '*.qml' | xargs pyside6-qmlformat -i" + exit 1 + } + + - name: Run qmllint + if: steps.changed.outputs.has_changes == 'true' + continue-on-error: true + run: | + echo "${{ steps.changed.outputs.files }}" | xargs pyside6-qmllint --bare diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c5f206 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.claude/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..df7adde --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,32 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=lf] + - id: check-merge-conflict + - id: check-added-large-files + + - repo: https://github.com/rbubley/mirrors-prettier + rev: v3.8.4 + hooks: + - id: prettier + files: \.(js|json|md|ya?ml)$ + + - repo: local + hooks: + - id: qmlformat + name: qmlformat + language: python + additional_dependencies: [PySide6==6.11.1] + entry: pyside6-qmlformat -i + files: \.qml$ + + - id: qmllint + name: qmllint + language: python + additional_dependencies: [PySide6==6.11.1] + entry: pyside6-qmllint + files: \.qml$ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..4c5f206 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +.claude/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..4b28762 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "useTabs": true, + "tabWidth": 4, + "printWidth": 120, + "semi": true, + "singleQuote": false, + "trailingComma": "none", + "endOfLine": "lf" +} diff --git a/Changelog.txt b/Changelog.txt index d26098e..f00bd87 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -4,4 +4,4 @@ - Add optional Waze travel time for up to two routes 1.0.1 -- initial release +- initial release diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b22bc02 --- /dev/null +++ b/LICENSE @@ -0,0 +1,40 @@ +MIT License + +Copyright (c) 2026 Luuk Visser + +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 above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. + +--- + +Upstream notice +--------------- + +This repository is a fork of https://github.com/ToonSoftwareCollective/tscClock +(forked at commit af304ec, 27 December 2022), which itself builds on the +standard Toon clock app. + +At the time of writing, the upstream repository publishes no license file. The +MIT grant above therefore covers only the changes and additions made in this +fork by the copyright holder named above; it cannot and does not grant rights +in the portions of the code that originate from ToonSoftwareCollective. Those +portions remain under their authors' copyright, with no license terms stated. + +If you intend to redistribute this software, ask ToonSoftwareCollective to +state a license for the upstream code first. Several other repositories in +that organisation (nlalert, toonmqtt, ToonRooter) are MIT-licensed. diff --git a/README.md b/README.md new file mode 100644 index 0000000..208f579 --- /dev/null +++ b/README.md @@ -0,0 +1,179 @@ +# tscClock + +A clock tile for the **Eneco Toon** thermostat display: time, seconds, date and day of +the week, plus optional **Waze travel times** for up to two routes. + +This is a fork of +[ToonSoftwareCollective/tscClock](https://github.com/ToonSoftwareCollective/tscClock), +which itself extends the standard Toon clock app with a date and seconds. See +[Differences from upstream](#differences-from-upstream). + +## The tile + +Two layouts are available. The original one anchors the time and date at fixed offsets +from the top of the tile: + +``` + ma 07:31 42 + 31 januari + Thuis-Werk: 24 min + Werk-Thuis: 31 min +``` + +The centred layout stacks the same rows in a `Column` centred vertically in the tile, +and grows the time font when the day of the week has been moved to the date row: + +``` + 07:31 42 + ma 31 januari + Thuis-Werk: 24 min + Werk-Thuis: 31 min +``` + +Every row below the time is optional; a row that is switched off or has no value takes +up no vertical space. Tapping the tile opens the settings screen. + +## Requirements + +- A **rooted Toon** running the ToonStore client (`tsc`). Settings are stored in + `/mnt/data/tsc/`, which is the ToonStore data directory. +- For travel times only: the Toon must be able to reach `routing-livemap-row.waze.com` + over HTTPS. No account or API key is needed. + +The clock itself works fully offline; leave the route coordinates blank and no network +requests are made. + +## Installation + +Copy the repository contents into a `tscClock` directory under the Toon's apps directory +and restart the UI: + +```sh +scp *.qml version.txt root@:/qmf/qml/apps/tscClock/ +``` + +The exact apps path depends on your firmware and root method — check where your other +ToonStore apps live and use the same parent directory. After restarting the Toon UI, add +the tile via the tile screen's **+** button (category _general_, label _Klok-TSC_). + +## Configuration + +Tap the tile to open **TSC Klok configuratie**: + +| Setting | Default | Description | +| ----------------------------------- | ------- | --------------------------------------------------------------------------- | +| `Toon datum` | on | Show the date row | +| `Toon seconden in de tijd` | on | Show seconds in small type next to the time | +| `Maand in datum voluit geschreven` | on | Month as a word (`31 januari`) instead of a number (`31-01`) | +| `Toon dag van de week` | on | Show the two-letter day abbreviation | +| `Verticaal gecentreerde weergave` | off | Use the centred layout instead of the fixed-offset one | +| `Dag voor datum (i.p.v. voor tijd)` | off | Move the day abbreviation to the date row, freeing width for a larger clock | + +`Waze reistijd »` at the bottom opens the travel-time screen. Press **Opslaan** on either +screen to save; settings are written to `/mnt/data/tsc/tscClock.userSettings.json` and +reloaded on startup. + +### Travel times + +Two independent routes can be configured, each with the same set of fields: + +| Field | Default (route 1 / route 2) | Description | +| ---------------------- | --------------------------------- | ----------------------------------------------------------------------- | +| `Periode` | `06:30`–`09:00` / `16:00`–`18:30` | Time window, `HH:MM`. Only used when `Tijdvenster` is on | +| `Label` | `Thuis-Werk:` / `Werk-Thuis:` | Prefix shown before the duration. Leave blank for the bare duration | +| `Van` / `Naar` Lat/Lon | _(empty)_ | Decimal coordinates, e.g. `52.3704` / `4.8952`. Commas are accepted too | +| `Tijdvenster` | off | Off: the route is always active. On: only inside `Periode` | +| `Alleen op werkdagen` | off | Restrict the route to Monday–Friday | + +A route is shown when its coordinates are complete and it is currently active. Routes are +evaluated independently, so overlapping windows show both rows. Saving re-evaluates both +routes immediately rather than waiting for the next tick. + +Finding coordinates: right-click a spot in Google Maps and copy the `lat, lon` pair it +shows, or read them from the URL after `@`. + +## How it works + +`TscClockApp.qml` ticks a 1-second `Timer` that rebuilds the time and date strings via +`i18n.dateTime` and then calls `checkTravelTimeWindow()`. A route that becomes active +triggers one Waze request; a route that goes inactive is cleared immediately, so a stale +duration can never linger on the tile. A second timer refreshes the active routes every +5 minutes. + +Travel times come from Waze's public live-map routing endpoint +(`routing-livemap-row.waze.com/RoutingManager/routingRequest`), summing `crossTime` over +the returned route segments. Failures are logged to the Toon's console and leave the +previous value in place. + +| File | Role | +| ------------------------------ | ------------------------------------------------------------ | +| `TscClockApp.qml` | App root: settings persistence, clock tick, Waze fetch logic | +| `TscClockTile.qml` | The tile — both layouts, switched by `app.centerLayout` | +| `TscClockSettings.qml` | Clock settings screen | +| `TscClockTravelSettings.qml` | Two-column travel-time settings screen | +| `version.txt`, `Changelog.txt` | Read by the ToonStore to detect and describe updates | + +`isNxt` selects the larger font and margin values for Toon 2 (NXT) hardware. The UI is in +Dutch, matching the rest of the Toon interface. + +## Differences from upstream + +Upstream has been unchanged since December 2022 (version 1.0.1). This fork adds: + +- **Waze travel times** for up to two routes, with per-route label, time window, + workdays-only toggle and a dedicated settings screen. +- **Vertically centred layout** as an alternative to the fixed baseline offsets. +- **Day of the week on the date row**, which frees horizontal space and allows a larger + time font (65 → 75 px on NXT). + +Nothing was removed. Existing settings files keep working: the new keys simply fall back +to their defaults when absent. + +## Versioning + +`version.txt` follows [semantic versioning](https://semver.org/) and continues upstream's +numbering, which stopped at `1.0.1`. This fork is at **1.1.0** — a minor bump, which is +the right call: every change above is additive and backwards compatible, with no removed +settings and no change to the on-disk settings format. + +Keep `version.txt` and `Changelog.txt` in step when releasing; the ToonStore uses the +former to detect updates and shows the latter to the user. + +One caveat of sharing upstream's numbering line: if ToonSoftwareCollective ever releases +their own `1.1.0`, the two are indistinguishable by version alone. Given upstream's +dormancy this is a low risk, but a fork-specific suffix is the fix if it ever matters. + +## Development + +Formatting and linting run through [pre-commit](https://pre-commit.com/): + +```sh +pip install pre-commit +pre-commit install +pre-commit run --all-files +``` + +That runs `prettier` on JSON/YAML/Markdown and `pyside6-qmlformat` / `pyside6-qmllint` +(from `PySide6`) on the QML. The same checks run on pull requests via +[`.github/workflows/pr-lint.yml`](.github/workflows/pr-lint.yml); `qmllint` is advisory, +since the Toon's `qb.components` types are not available outside the device. + +There is no way to run the app off-device: it depends on Toon-private QML modules +(`qb.components`, `qb.base`, `FileIO`, `BxtClient`). Test on a real Toon. + +## Known limitations + +- The Waze endpoint is undocumented and unofficial. It can change or start refusing + requests at any time, and every fetch sends your route coordinates to Waze. +- `thumbnailIcon` points at `drawables/clock.svg`, which is not in this repository — + inherited from upstream, where the file is likewise absent. +- Travel times are fetched over HTTP without a timeout; an unreachable Waze leaves the + last value on the tile until the route goes inactive. +- The settings screens are Dutch-only and not translated via `qsTr`. + +## License + +[MIT](LICENSE), covering the changes made in this fork. The upstream +[ToonSoftwareCollective/tscClock](https://github.com/ToonSoftwareCollective/tscClock) +code this builds on declares no license of its own — see the notice in +[`LICENSE`](LICENSE) before redistributing. diff --git a/version.txt b/version.txt index 1cc5f65..9084fa2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.1.0 \ No newline at end of file +1.1.0