mrVragec/MMM-mvgmunich

View on GitHub
mvgmunich.js

Summary

Maintainability
C
1 day
Test Coverage
/* Timetable for public transport in Munich */

/*
 * Magic Mirror
 * Module: MVG Munich
 *
 * By Simon Crnko
 * MIT Licensed
 *
 */

const MS_PER_MINUTE = 60000;
Module.register("mvgmunich", {
    // Default module configuration
    defaults: {
        maxEntries: 8, // maximum number of results shown on UI
        updateInterval: MS_PER_MINUTE, // update every 60 seconds
        haltestelle: "Hauptbahnhof", // default departure station
        haltestelleId: 0,
        haltestelleName: "",
        ignoreStations: [], // list of destination to be ignored in the list
        lineFiltering: {
            "active": true,             // set this to active if filtering should be used
            "filterType": "whitelist",     // 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: true,
        transportTypesToShow: {
            "ubahn": true,
            "sbahn": true,
            "regional_bus": true,
            "bus": true,
            "tram": true
        },
        showInterruptions: false,
        showInterruptionsDetails: false,
        countInterruptionsAsItemShown: false,
    },

    getStyles: function () {
        return ["mvgmunich.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.haltestelle !== "") {
            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.haltestelle === "") {
            wrapperTable.className = "dimmed light small";
            wrapperTable.innerHTML = "Please set value for 'Haltestelle'.";
            return wrapperTable;
        }

        if (this.resultData === []) {
            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.haltestelle];
        return wrapperTable;
    },

    getHtml: function (jsonObject) {
        let htmlText = "";

        let visibleLines = 0;
        const interruptions = new Set();

        for (let i = 0; i < jsonObject.departures.length; i++) {
            if (visibleLines >= this.config.maxEntries) {
                break;
            }
            // get one item from api result
            const apiResultItem = jsonObject.departures[i];
            // get transport type
            const transportType = apiResultItem.product.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 += "<tr class='gray'>";
            } else {
                htmlText += "<tr class='normal'>";
            }
            // check if user want's icons
            htmlText += this.showIcons(apiResultItem.product, this.config.showIcons);
            // add transport number
            htmlText += "<td>" + apiResultItem.label + "</td>";
            // add last station aka direction
            htmlText += "<td class='stationColumn'>" + apiResultItem.destination + "</td>";
            // check if user want's to see departure time
            htmlText += this.showDepartureTime(apiResultItem.departureTime);
            // check if user want's to see walking time
            htmlText += this.showWalkingTime(apiResultItem.departureTime);
            htmlText += "</tr>";
            if (this.config.showInterruptionsDetails && this.isLineAffected(apiResultItem.label)) {
                let interruption = this.getInterruptionsDetails(apiResultItem.label);
                if (!interruptions.has(interruption)) {
                    interruptions.add(interruption);
                    htmlText += "<tr><td></td><td class='empty' colspan='3'>" + interruption +"</td></tr>";
                    if (this.config.countInterruptionsAsItemShown) {
                        visibleLines++;
                    }
                }
            }
            visibleLines++;
        }
        return htmlText;
    },

    checkToIgnoreOrIncludeLine: function (lineName) {
        return this.config.lineFiltering !== undefined 
        && this.config.lineFiltering.active 
        && (this.config.lineFiltering.filterType.localeCompare("whitelist") === 0 ?
                !this.checkLineNumbersIncludes(lineName) : this.checkLineNumbersIncludes(lineName));
    },

    checkLineNumbersIncludes: function (lineName) {
        return (this.config.lineFiltering.lineNumbers.includes(lineName));
    },

    isLineAffected: function (lineName) {
        if (this.interruptionData.affectedLines != undefined) {
            for (let i = 0; i < this.interruptionData.affectedLines.line.length; i++) {
                if (this.interruptionData.affectedLines.line[i].line === lineName) {
                    return true;
                }
            }
        }
        return false;
    },

    getInterruptionsDetails: function (lineName) {
        for (let i = 0; i < this.interruptionData.interruption.length; i++) {
            if (this.interruptionData.interruption[i].lines.line != null) {
                for (let j = 0; j < this.interruptionData.interruption[i].lines.line.length; j++) {
                    if (this.interruptionData.interruption[i].lines.line[j].line === lineName) {
                        return this.interruptionData.interruption[i].duration.text + " - " + this.interruptionData.interruption[i].title;
                    }
                }
            }
           }
        return "";
    },

    showIcons: function (product, showIcons) {
        let icons = "";
        if (showIcons) {
            icons = "<td class='" + product.toLocaleLowerCase() + "'></td>";
        }
        return icons;
    }
    ,

    showWalkingTime: function (departureTime) {
        let htmlText = "";
        if (this.config.showWalkingTime) {
            htmlText += "<td> / ";
            const startWalkingTime = new Date(departureTime - this.config.timeToWalk * MS_PER_MINUTE);
            // check what kind of walking time user wants (absolute / relative)
            if (this.config.walkingTimeFormat === "absolute") {
                htmlText += this.getAbsoluteTime(startWalkingTime);
            } else if (this.config.walkingTimeFormat === "relative") {
                htmlText += this.getRelativeTime(startWalkingTime);
            } else {
                htmlText += "walkingTimeFormat config is wrong";
            }
            htmlText += "</td>";
        }
        return htmlText;
    }
    ,

    showDepartureTime: function (departureTime) {
        let htmlText = "";
        if (this.config.showTrainDepartureTime) {
            // add departure time
            htmlText += "<td class='timing'>";
            const departureDate = new Date(departureTime);
            // check what kind of time user wants (absolute / relative)
            if (this.config.trainDepartureTimeFormat === "absolute") {
                htmlText += this.getAbsoluteTime(departureDate);
            } else if (this.config.trainDepartureTimeFormat === "relative") {
                htmlText += this.getRelativeTime(departureDate);
            } else {
                htmlText += "trainDepartureTimeFormat config is wrong";
            }
            htmlText += "</td>";
        }
        return htmlText;
    }
    ,

    getAbsoluteTime: function (time) {
        let hoursStr = (time.getHours() < 10 ? "0" : "") + time.getHours();
        let minutesStr = (time.getMinutes() < 10 ? "0" : "") + time.getMinutes();

        return hoursStr + ":" + minutesStr;
    }
    ,

    getRelativeTime: function (time) {
        const timingForStartWalking = Math.floor((time.getTime() - new Date().getTime()) / 1000 / 60);
        return (timingForStartWalking <= 0
            ? this.translate("JETZT")
            : this.translate("IN") + " " + timingForStartWalking + " " + this.translate("MIN"));
    }
    ,

    // Override getHeader method.
    getHeader: function () {
        if (this.config.haltestelle !== "" || this.config.haltestelleName !== "") {
            return this.data.header + " Munich: " +
                (this.config.haltestelleName === "" ? this.config.haltestelle : this.config.haltestelleName);
        }
        return "";
    }
    ,

    socketNotificationReceived: function (notification, payload) {
        switch (notification) {
        case "UPDATE_DEPARTURE_INFO":
            this.resultData[payload.haltestelle] = this.getHtml(payload.transport);
            break;

        case "UPDATE_STATION":
            if (this.config.haltestelle === payload.haltestelle) {
                this.config.haltestelleId = payload.haltestelleId;
                this.config.haltestelleName = payload.haltestelleName;
            }
            this.getHeader();
            this.getData();
            break;

        case "UPDATE_INTERRUPTION_DATA":
            this.interruptionData = payload;
            break;

        default:
            Log.error();
        }
        this.updateDom();
    }
});