diff --git a/.github/Example_1.png b/.github/Example_1.png
new file mode 100644
index 0000000..0ae25b1
Binary files /dev/null and b/.github/Example_1.png differ
diff --git a/.github/Example_2.png b/.github/Example_2.png
new file mode 100644
index 0000000..697c4ca
Binary files /dev/null and b/.github/Example_2.png differ
diff --git a/.github/Example_3.png b/.github/Example_3.png
new file mode 100644
index 0000000..9b89cdc
Binary files /dev/null and b/.github/Example_3.png differ
diff --git a/.github/Example_4.png b/.github/Example_4.png
new file mode 100644
index 0000000..14b1b80
Binary files /dev/null and b/.github/Example_4.png differ
diff --git a/.github/Example_5.png b/.github/Example_5.png
new file mode 100644
index 0000000..9eb9d77
Binary files /dev/null and b/.github/Example_5.png differ
diff --git a/.github/Example_6.png b/.github/Example_6.png
new file mode 100644
index 0000000..7ebb45a
Binary files /dev/null and b/.github/Example_6.png differ
diff --git a/.github/Example_7.png b/.github/Example_7.png
new file mode 100644
index 0000000..61f1324
Binary files /dev/null and b/.github/Example_7.png differ
diff --git a/.github/workflows/branch-push.yaml b/.github/workflows/branch-push.yaml
new file mode 100644
index 0000000..58ac262
--- /dev/null
+++ b/.github/workflows/branch-push.yaml
@@ -0,0 +1,22 @@
+name: Branch push
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ # Required to request the OIDC JWT Token
+ id-token: write
+ # Required when GH token is used to authenticate with private repo
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Draft release
+ uses: KoblerS/release-drafter@v1
+ with:
+ version-prefix: ''
diff --git a/.github/workflows/release-published.yaml b/.github/workflows/release-published.yaml
new file mode 100644
index 0000000..50129cb
--- /dev/null
+++ b/.github/workflows/release-published.yaml
@@ -0,0 +1,29 @@
+name: Release published
+on:
+ release:
+ types:
+ - released
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ # Required to request the OIDC JWT Token
+ id-token: write
+ # Required when GH token is used to authenticate with private repo
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Update version in package.json
+ run: |
+ jq ".version = \"${{ github.event.release.tag_name}}\"" package.json > tmp.json && mv tmp.json package.json
+
+ - name: Commit changes
+ run: |
+ git config --global user.name 'github-actions'
+ git config --global user.email 'github-actions@github.com'
+ git add package.json
+ git commit -m "Update version to ${{ github.event.release.tag_name}}"
+ git push
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d3f11de
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+# https://git-scm.com/docs/gitignore
+# https://help.github.com/articles/ignoring-files
+# Example .gitignore files: https://github.com/github/gitignore
+/bower_components/
+/node_modules/
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..beca4e9
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,66 @@
+module.exports = function(grunt) {
+ require("time-grunt")(grunt);
+ grunt.initConfig({
+ pkg: grunt.file.readJSON("package.json"),
+ eslint: {
+ options: {
+ configFile: ".eslintrc.json"
+ },
+ target: ["*.js"]
+ },
+ stylelint: {
+ simple: {
+ options: {
+ configFile: ".stylelintrc"
+ },
+ src: ["*.css"]
+ }
+ },
+ jsonlint: {
+ main: {
+ src: ["package.json", "translations/*.json"],
+ options: {
+ reporter: "jshint"
+ }
+ }
+ },
+ markdownlint: {
+ all: {
+ options: {
+ config: {
+ "default": true,
+ "line-length": false,
+ "blanks-around-headers": false,
+ "no-duplicate-header": false,
+ "no-inline-html": false,
+ "MD010": false,
+ "MD001": false,
+ "MD031": false,
+ "MD040": false,
+ "MD002": false,
+ "MD029": false,
+ "MD041": false,
+ "MD032": false,
+ "MD036": false,
+ "MD037": false,
+ "MD009": false,
+ "MD018": false,
+ "MD012": false,
+ "MD026": false,
+ "MD038": false
+ }
+ },
+ src: ["README.md", "CHANGELOG.md", "LICENSE.txt"]
+ }
+ },
+ yamllint: {
+ all: [".travis.yml"]
+ }
+ });
+ grunt.loadNpmTasks("grunt-eslint");
+ grunt.loadNpmTasks("grunt-stylelint");
+ grunt.loadNpmTasks("grunt-jsonlint");
+ grunt.loadNpmTasks("grunt-yamllint");
+ grunt.loadNpmTasks("grunt-markdownlint");
+ grunt.registerTask("default", ["eslint", "stylelint", "jsonlint", "markdownlint", "yamllint"]);
+};
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fb2f45a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Simon Kobler
+
+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.
diff --git a/README.md b/README.md
index 4675bb6..4782f44 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,9 @@ MagicMirror² Module to monitor public transport (U-Bahn, Tram, Bus, S-Bahn) in
| | |
| --- | --- |
| **Stations names with arrival time** | ![](.github/Example_1.png) |
-```
+```javascript
config: {
- haltestelle: "Hauptbahnhof",
+ station: "Hauptbahnhof",
maxEntries: 10, // 10 items on screen
updateInterval: 60000, // 60 s
showIcons: false, // Show transport type icon
@@ -31,13 +31,13 @@ config: {
walkingTimeFormat: "relative", // format of the walking time
}
```
+
| | |
| --- | --- |
| **Stations names with icons and arrival time** | ![](.github/Example_3.png) |
-
-```
+```javascript
config: {
- haltestelle: "Karlsplatz",
+ station: "Karlsplatz",
maxEntries: 15, // 10 items on screen
updateInterval: 60000, // 60 s
showIcons: true, // Show transport type icon
@@ -56,12 +56,13 @@ config: {
walkingTimeFormat: "relative" // format of the walking time
}
```
+
| | |
| --- | --- |
| **Stations names with icons, arrival time and walking time** | ![](.github/Example_4.png) |
-```
+```javascript
config: {
- haltestelle: "Karlsplatz",
+ station: "Karlsplatz",
maxEntries: 15, // 10 items on screen
updateInterval: 60000, // 60 s
showIcons: true, // Show transport type icon
@@ -84,9 +85,9 @@ config: {
| | |
| --- | --- |
| **Stations names with icons, arrival time and disruption marking** | ![](.github/Example_5.png) |
-```
+```javascript
config: {
- haltestelle: "Harras",
+ station: "Harras",
maxEntries: 10, // 10 items on screen
updateInterval: 60000, // 60 s
showIcons: true, // Show transport type icon
@@ -106,14 +107,15 @@ config: {
showInterruptions: true,
showInterruptionsDetails: false,
countInterruptionsAsItemShown: false
+}
```
| | |
| --- | --- |
| **Stations names with icons, arrival time, disruption marking and disruptions details** (disruption details are not counted as new lines)| ![](.github/Example_6.png) |
-```
+```javascript
config: {
- haltestelle: "Harras",
+ station: "Harras",
maxEntries: 10, // 10 items on screen
updateInterval: 60000, // 60 s
showIcons: true, // Show transport type icon
@@ -133,14 +135,15 @@ config: {
showInterruptions: true,
showInterruptionsDetails: true,
countInterruptionsAsItemShown: false
+}
```
| | |
| --- | --- |
| **Stations names with icons, arrival time, disruption marking and disruptions details** (disruption details are counted as new lines) | ![](.github/Example_7.png) |
-```
+```javascript
config: {
- haltestelle: "Harras",
+ station: "Harras",
maxEntries: 10, // 10 items on screen
updateInterval: 60000, // 60 s
showIcons: true, // Show transport type icon
@@ -160,11 +163,12 @@ config: {
showInterruptions: true,
showInterruptionsDetails: true,
countInterruptionsAsItemShown: true
+}
```
## Dependencies
* instance of [MagicMirror²](https://github.com/MichMich/MagicMirror)
-* urlencode (linux: npm install urlencode)
+* Nothing else, module is small and simple! 🙌
## Installation
1. Clone this repository in your MagicMirror installation into the folder modules.
@@ -177,7 +181,7 @@ config: {
position: "bottom_left",
header: "MVG",
config: {
- haltestelle: "Hauptbahnhof",
+ station: "Hauptbahnhof",
maxEntries: 10, // 10 items on screen
updateInterval: 60000, // 60 s
showIcons: true, // Show transport type icon
@@ -203,13 +207,13 @@ config: {
showInterruptionsDetails: false, // show details of interruptions in next line
countInterruptionsAsItemShown: false, // count interruptions details lines as a line shown
}
-},
+}
```
## Config Options
| **Option** | **Description** |
| --- | --- |
-| `haltestelle` | Station for which you want to display data.
**Default:** `Hauptbahnhof`
**Source:** http://www.mvg-live.de/MvgLive/MvgLive.jsp |
+| `station` | Station for which you want to display data.
**Default:** `Hauptbahnhof`
**Source:** https://www.mvg.de/meinhalt.html |
| `maxEntries` | Number of items shown in table.
**Default:** `8` |
| `updateInterval` | Update interval
**Default:** `60000` |
| `ubahn` | Show data for U-Bahn.
**Possible values:** `true` or `false`
**Default:** `true` |
diff --git a/mmm-mvg.css b/mmm-mvg.css
new file mode 100644
index 0000000..7419bd1
--- /dev/null
+++ b/mmm-mvg.css
@@ -0,0 +1,154 @@
+/*
+ * MMM-MVG
+ *
+ * By Simon Kobler
+ * MIT Licensed.
+ *
+ */
+
+.stationColumn {
+ padding-left: 10px;
+ padding-right: 10px;
+ min-width: 240px;
+}
+
+.mmm-mvg table {
+border-spacing: 0 0.3em;
+}
+
+.mmm-mvg tr.normal {
+ color: white;
+}
+
+.mmm-mvg tr.gray {
+ color: dimgray;
+}
+
+.mmm-mvg td.tram {
+ background: url(resources/tram.svg) no-repeat 0 0;
+ width: 30px;
+ padding-bottom: 5px;
+ background-size: 25px;
+}
+
+.mmm-mvg td.sbahn {
+ background: url(resources/s-bahn.svg) no-repeat 0 0;
+ width: 30px;
+ padding-bottom: 5px;
+ background-size: 25px;
+}
+
+.mmm-mvg td.bus, .mmm-mvg td.regional_bus {
+ background: url(resources/bus.svg) no-repeat 0 0;
+ width: 30px;
+ padding-bottom: 5px;
+ background-size: 25px;
+}
+
+.mmm-mvg td.ubahn {
+ background: url(resources/u-bahn.svg) no-repeat 0 0;
+ width: 30px;
+ padding-bottom: 5px;
+ background-size: 25px;
+}
+
+.mmm-mvg td.empty {
+ color: dimgray;
+ font-size: 16px;
+ width: 30px;
+ padding-bottom: 5px;
+ background-size: 25px;
+}
+
+.mmm-mvg td.timing {
+ text-align: right;
+}
+
+.U1 {
+ --bg: #52822F;
+}
+.U2 {
+ --bg: #C2243B;
+}
+.U3 {
+ --bg: #EC6726;
+}
+.U4 {
+ --bg: #00A984;
+}
+.U5 {
+ --bg: #BB7A00;
+}
+.U6 {
+ --bg: #0065AD;
+}
+.U7 {
+ --bg: #000;
+}
+.U8 {
+ --bg: #C2243B;
+ position: relative;
+ overflow: hidden;
+ display: inline-block;
+ z-index: 0;
+}
+.U8::after {
+ background-color: #EC6726;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ clip-path: polygon(100% 0, 0% 100%, 100% 100%);
+ z-index: -1;
+ content: '';
+}
+.S1 {
+ --bg: #1B9FC6;
+}
+.S2 {
+ --bg: #69A338;
+}
+.S3 {
+ --bg: #973083;
+}
+.S4 {
+ --bg: #136680;
+}
+.S5 {
+ --bg: #136680;
+}
+.S6 {
+ --bg: #008D5E;
+}
+.S7 {
+ --bg: #883B32;
+}
+.S8 {
+ --bg: #2D2B29;
+}
+.SEV {
+ --bg: #95368C;
+}
+.TRAM {
+ --bg: #E30613;
+}
+.BUS {
+ --bg: #00586A;
+}
+
+.transport {
+ background-color: var(--bg);
+ color: #ffffff;
+ padding: 0 clamp(.25rem, calc(.13rem + .32vw), .38rem);
+ text-align: center;
+ font-variant-numeric: lining-nums tabular-nums;
+ font-family: Manrope, sans-serif;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 20px;
+ height: 20px;
+ border-radius: 4px;
+ white-space: nowrap;
+}
diff --git a/mmm-mvg.js b/mmm-mvg.js
new file mode 100755
index 0000000..0aecbae
--- /dev/null
+++ b/mmm-mvg.js
@@ -0,0 +1,351 @@
+/* Timetable for public transport in Munich using MVG API */
+
+/*
+ * Magic Mirror
+ * Module: MVG
+ *
+ * By Simon Kobler
+ * MIT Licensed
+ *
+ */
+
+const MS_PER_MINUTE = 60000;
+Module.register("mmm-mvg", {
+ // Default module configuration
+ defaults: {
+ headerPrefix: '', // Header prefix
+ maxEntries: 8, // maximum number of results shown on UI
+ updateInterval: MS_PER_MINUTE, // update every 60 seconds
+ station: "Hirschgartenallee", // default departure station
+ stationId: 0,
+ stationName: "",
+ ignoreStations: [], // list of destination to be ignored in the list
+ lineFiltering: {
+ active: true, // set this to active if filtering should be used
+ filterType: "blacklist", // whitelist = only specified lines will be displayed, blacklist = all lines except specified lines will be displayed
+ lineNumbers: ["U1, U3, X50"], // lines that should be on the white-/blacklist
+ },
+ timeToWalk: 0, // walking time to the station
+ showWalkingTime: false, // if the walking time should be included and the starting time is displayed
+ showTrainDepartureTime: true,
+ trainDepartureTimeFormat: "relative",
+ walkingTimeFormat: "relative",
+ showIcons: false,
+ transportTypesToShow: {
+ ubahn: true,
+ sbahn: true,
+ regional_bus: true,
+ bus: true,
+ tram: true,
+ },
+ showInterruptions: true,
+ showInterruptionsDetails: true,
+ countInterruptionsAsItemShown: false,
+ },
+
+ getStyles: function () {
+ return ["mmm-mvg.css"];
+ },
+
+ // Load translations files
+ getTranslations: function () {
+ return {
+ en: "translations/en.json",
+ de: "translations/de.json",
+ };
+ },
+
+ start: function () {
+ this.resultData = [];
+ this.interruptionData = null;
+ Log.info(
+ "Starting module: " + this.name + ", identifier: " + this.identifier
+ );
+ if (this.config.station !== "") {
+ this.sendSocketNotification("GET_STATION_INFO", this.config);
+ }
+ },
+
+ /*
+ * getData
+ * function call getData function in node_helper.js
+ *
+ */
+ getData: function () {
+ const self = this;
+ self.sendSocketNotification("GET_DEPARTURE_DATA", self.config);
+ setInterval(function () {
+ self.sendSocketNotification("GET_DEPARTURE_DATA", self.config);
+ }, self.config.updateInterval);
+ },
+
+ // Override dom generator.
+ getDom: function () {
+ let wrapperTable = document.createElement("div");
+ if (this.config.station === "") {
+ wrapperTable.className = "dimmed light small";
+ wrapperTable.innerHTML = "Please set value for 'station'.";
+ return wrapperTable;
+ }
+
+ if (Object.keys(this.resultData).length === 0) {
+ wrapperTable.className = "dimmed light small";
+ wrapperTable.innerHTML = "Loading data from MVG ...";
+ return wrapperTable;
+ }
+
+ wrapperTable = document.createElement("table");
+ wrapperTable.className = "small";
+ wrapperTable.innerHTML = this.resultData[this.config.stationId];
+ return wrapperTable;
+ },
+
+ getHtml: function (jsonObject) {
+ let htmlText = "";
+
+ let visibleLines = 0;
+ const interruptions = new Set();
+
+ for (let i = 0; i < jsonObject.length; i++) {
+ if (visibleLines >= this.config.maxEntries) {
+ break;
+ }
+ // get one item from api result
+ const apiResultItem = jsonObject[i];
+ // get transport type
+ const transportType = apiResultItem.transportType.toLocaleLowerCase();
+
+ // check if we should show data of this transport type
+ if (
+ !this.config.transportTypesToShow[transportType] ||
+ this.config.ignoreStations.includes(apiResultItem.destination) ||
+ this.checkToIgnoreOrIncludeLine(apiResultItem.label)
+ ) {
+ continue;
+ }
+
+ if (
+ this.config.showInterruptions &&
+ this.isLineAffected(apiResultItem.label)
+ ) {
+ htmlText += "