MagicMirror Forum
    • Recent
    • Tags
    • Unsolved
    • Solved
    • MagicMirror² Repository
    • Documentation
    • 3rd-Party-Modules
    • Donate
    • Discord
    • Register
    • Login
    1. Home
    2. Scott-M
    A New Chapter for MagicMirror: The Community Takes the Lead
    Read the statement by Michael Teeuw here.
    S
    Offline
    • Profile
    • Following 0
    • Followers 0
    • Topics 2
    • Posts 34
    • Groups 0

    Scott-M

    @Scott-M

    4
    Reputation
    2
    Profile views
    34
    Posts
    0
    Followers
    0
    Following
    Joined
    Last Online
    Location Scottish Borders

    Scott-M Unfollow Follow

    Best posts made by Scott-M

    • RE: MMM-CalendarExt3 and MMM-MonthlyCalendar blank

      @sdetweil
      I probably did add the code block after. Lesson learned! And this is partly why I am having more issues with this. It has been very frustating, after it worked last night, it isn’t now.

      I think I know what the issue is but it is time consuming writing the SD image and running the script so if you can bear with me. I want to test it at least twice before I update here.

      I suspect the 2 dashes before

      --force-confdef
      --force-confnew
      

      have been replaced by a single dash of a different character type. Again, because I put the code block around the text after I copied it.

      This should be the correct code but let me test again to make sure. It has worked once, just want to do it again and will update.

      sudo apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" full-upgrade
      

      In the mean time, the above code will auto select the default option if one is available and then answer Yes if there is no default. In the case of the prompt that was appearing, the old configuration file ‘/etc/xdg/labwc-greeter/autostart’ is kept. If you want the package maintainers file instead just remove the first option:

      -o Dpkg::Options::="--force-confdef"
      
      posted in Troubleshooting
      S
      Scott-M
    • RE: MMM-CalendarExt3 and MMM-MonthlyCalendar blank

      @sdetweil So it does, not sure why, I copied it from the terminal window.

      posted in Troubleshooting
      S
      Scott-M
    • RE: MMM-CalendarExt3 and MMM-MonthlyCalendar blank

      @sdetweil

      It seemed to get through the upgrade stage very quickly and

      apt-get upgrade  started
      apt-get upgrade result =rc=0 Reading package lists...
      Building dependency tree...
      

      in the log seems to show the upgrade didn’t work. I will run the script locally on the pi and send the output to the termnial to see what is happening but it won’t be tonight, I have to go out now.

      Update tomorrow…

      posted in Troubleshooting
      S
      Scott-M
    • RE: My module stops MMM-Pages working

      @sdetweil Great, will hopefully get my mirror on the wall next week!#

      posted in Troubleshooting
      S
      Scott-M

    Latest posts made by Scott-M

    • RE: My module stops MMM-Pages working

      @sdetweil Great, will hopefully get my mirror on the wall next week!#

      posted in Troubleshooting
      S
      Scott-M
    • RE: My module stops MMM-Pages working

      @sdetweil
      I think I understand, am I getting closer with something like this:

      Module.register("MMM-TravelLine", {
        start() {
          this.timesReturned = [];
        },
      
        notificationReceived(notification, payload, sender) {
          if (notification === "ALL_MODULES_STARTED") {
            console.log("All modules started; fetching routes now");
            this.sendSocketNotification("FETCH_ROUTES", {
              username: this.config.username,
              password: this.config.password,
              apiBase: this.config.apiBase,
              stopIDs: this.config.stopIDs,
              timestamp: new Date().toISOString()
            });
      
            // Schedule refresh
            setInterval(() => {
              this.sendSocketNotification("FETCH_ROUTES", {
                username: this.config.username,
                password: this.config.password,
                apiBase: this.config.apiBase,
                stopIDs: this.config.stopIDs,
                timestamp: new Date().toISOString()
              });
            }, this.config.updateFrequency * 60 * 1000);
          }
        },
      
        socketNotificationReceived(notification, payload) {
          if (notification === "UPDATED_ROUTES") {
            this.timesReturned = payload;
            this.updateDom();
          }
        },
      
        getDom() {
          const container = document.createElement("div");
          container.className = "travelline-container";
      
          if (!Array.isArray(this.timesReturned) || this.timesReturned.length === 0) {
            container.innerHTML = "<div class='no-data'>Loading bus times…</div>";
            return container;
          }
      
          // ...builds table etc.......
      
          return container;
        }
      });
      
      

      Again, I have lost my remote conection to the PI so I can’t try it until tomorrow!

      posted in Troubleshooting
      S
      Scott-M
    • RE: My module stops MMM-Pages working

      @sdetweil said in My module stops MMM-Pages working:

      you dont tell MagicMirror you are ready, it tells you

      You wait for ALL_MODULES_STARTED
      You can start operations in start()
      Do whatever, but you can’t draw yet

      getDom() will be called for your content the first time

      You never SEND any of those notifications

      https://github.com/sdetweil/SampleModule

      See my sample module

      You are absolutely correct, I should have properly read about the life cylce of MagicMirror first.

      I changed socketNotificationRecieved to

        socketNotificationReceived: function (notification, payload) {
          if (notification === "UPDATED_ROUTES") {
            this.timesReturned = payload;
            this.loaded = true;
            this.updateDom();
          }
        },
      

      and added this in getDom()

          // show message until we have data
          if (!this.loaded) {
            container.innerHTML = "<div class='no-data'>Loading bus times…</div>";
            return container;
          }
      

      I think the module made updateDom() call before returning a valid DOM the first time. Let me know what you think! This also works now…

      posted in Troubleshooting
      S
      Scott-M
    • RE: My module stops MMM-Pages working

      @sdetweil
      There are no errors or issues in the console for MMM-Pages but what I didn’t see when my module is active is

      [MMM-pages] received that all objects are created; will now hide things!
      

      Which is present in the console when I disable it. I notice in the Pages docs it says

      MMM-pages doesn't activate until we receive the DOM_OBJECTS_CREATED notification, as that notification ensures all modules have been loaded.
      

      That is obviously not happening when my module is active. it loads the data from the NextBus API asyncronously so I think updateDom() keeps running before the first ready DOM is returned. I manually notifyied MagicMirror that it is ready by adding this into socketNotificationRecieved and seems to work now.

      if (!this.hasSentDomCreated) {
              this.hasSentDomCreated = true;
              this.sendNotification("DOM_OBJECTS_CREATED");
            }
      

      There is probably a better way to do this but seems to work.

      This is my first attempt at a module so I wasn’t expecting plain sailing but any feedback is always appreciated.

      MMM-TravelLine.js

      // LCARS Colours
      var busColours = [
        "#cc99ff", //african-violet
        "#ffaa90", //almond
        "#ffbbaa", //almond-creme
        "#5566ff", //blue
        "#8899ff", //bluey
        "#ff9966", //butterscotch
        "#ffaa00", //gold
        "#ff9900", //golden-orange
        "#666688", //gray
        "#999933", //green
        "#99ccff", //ice
        "#cc55ff", //lilac
        "#cccc66", //lima-bean
        "#cc5599", //magenta
        "#ff2200", //mars
        "#9966ff", //moonlit-violet
        "#ff8800", //orange
        "#ff8866", //peach
        "#cc4444", //red
        "#aaaaff", //sky
        "#f5f6fa", //space-white
        "#ffcc99", //sunflower
        "#ff5555", //tomato
        "#ddbbff", //violet-creme
        // Lower Decks
        "#ffeecc", //butter
        "#ff9911", //daybreak
        "#ffaa44", //harvestgold
        "#ffcc99", //honey
        "#ff4400", //october-sunset
        "#ff7700", //orange
        "#cc5500" //rich-pumpkin
      ]
      
      function newColour () {
          var randomNumber = Math.floor(Math.random() * busColours.length);
          return randomNumber;
      }
      
      const directionColours = {
        "Edinburgh, Bus Station": "#cc99ff",          // 
        "Galashiels Transport Interchange": "#ff7700" // blue
      };
      
      // fallback colour if direction not in map
      const defaultColour = "#cc9bcd";
      
      Module.register("MMM-TravelLine", {
        defaults: {
          apiBase: 'https://nextbus.mxdata.co.uk/nextbuses/1.0/1',
          username: "yourusername",
          password: "yourpassword",
          // stopIDs[[stop ID, Name for heading, target bus, Random colours or not]]
          stopIDs: [  ["36235972","Princes St West (PD)","34",true],       
                      ["36232486","The Mound (MD)","27",true]],  
          //targetBus: "X62",     // Service we’re interested in
          updateFrequency: 10,   // Refresh time in minutes
          maxResults: 10,
          debug: false,
        },
      
        getStyles() {
          return ["travelline.css"];
        },
      
        start() {   
          // some dummy values
          this.timesReturned = [
            {aimed: "..." ,service: "Error", direction: "...", raw: ""},
            {aimed: "..." ,service: "Error", direction: "...", raw: ""},
            {aimed: "..." ,service: "Error", direction: "...", raw: ""}
          ];
      
          this.sendSocketNotification("FETCH_ROUTES", {
            username:this.config.username,
            password:this.config.password,
            apiBase:this.config.apiBase,
            stopIDs:this.config.stopIDs,
            //targetBus:this.config.targetBus,
            timestamp: new Date().toISOString(),    // Build a fresh ISO8601 timestamp
      
          });
      
          // Fetch new routes at the interval set in the config
          setInterval(() => this.sendSocketNotification("FETCH_ROUTES", {
            username:this.config.username,
            password:this.config.password,
            apiBase:this.config.apiBase,
            stopIDs:this.config.stopIDs,
            //targetBus:this.config.targetBus,
            timestamp: new Date().toISOString(),    // Build a fresh ISO8601 timestamp
          }), this.config.updateFrequency * 60 * 1000);
      
          // Refresh the display every 30 seconds
          setInterval(() => this.updateDom(), 30000);
        },
      
        notificationReceived(notification, payload) {
          if (notification === "UPDATED_ROUTES") {
            this.timesReturned = payload;
            //this.updateDom();
          }
        },
      
        socketNotificationReceived: function (notification, payload) {
          if (notification === "UPDATED_ROUTES") {
            this.timesReturned = payload;
            this.updateDom();
            // Notify once that DOM objects exist
            if (!this.hasSentDomCreated) {
              this.hasSentDomCreated = true;
              this.sendNotification("DOM_OBJECTS_CREATED");
            }
          }
        },
      
        getDom() {
          const container = document.createElement('div');
          container.className = 'travelline-container';
          container.style.display = 'flex';           // show tables side-by-side
          container.style.gap = '20px';               // spacing between tables
      
          //console.log("timesReturned is: ",this.timesReturned)
      
          // show message until we have data
          if (!Array.isArray(this.timesReturned) || this.timesReturned.length === 0) {
            container.innerHTML = "<div class='no-data'>Loading bus times…</div>";
            return container;
          }
            // loop through each stop’s departures
            this.timesReturned.forEach((stopResults, stopIndex) => {
              const resultsArray = Array.isArray(stopResults) ? stopResults : [];
      
              // create a wrapper for this stop
              const stopWrapper = document.createElement('div');
              stopWrapper.className = 'stop-wrapper';
      
              // optional: heading for the stop
              const heading = document.createElement('h3');
              heading.className = "travelline-heading";
              heading.textContent = `${resultsArray[0].stopName}`;  //heading.textContent = `Stop ${stopIndex + 1}`;
              //heading.style.textAlign = 'center';
              stopWrapper.appendChild(heading);
              //if (!(stopResults[stopIndex].direction == "No Departures")) {
      
              // create table
              const table = document.createElement('table');
              table.className = 'travelline-table';
      
              // limit results
              const limitedResults = resultsArray.slice(0, this.config.maxResults);
      
              limitedResults.forEach(item => {
                if (!(item.direction == "No Departures")) {
                  const row = document.createElement('tr');
                  row.className = 'travelline-row';
      
                  if (item.randomColour){
                    const colourIndex = newColour();
                    row.style.color = busColours[colourIndex];
                  } else {
                    // Pick a fixed colour based on destination direction
                    const dir = item.direction.trim();
                    row.style.color = directionColours[dir] || defaultColour;
                  }
      
                  // minutes-to-departure
                  const minutesCell = document.createElement('td');
                  minutesCell.className = 'travelline-minutes';
                  const depTime = new Date(item.raw);
                  const diffMs = depTime - Date.now();
                  const diffMin = Math.max(0, Math.round(diffMs / 60000));
                  if (diffMin >= 60) {
                    const hours = Math.floor(diffMin / 60);
                    const mins  = diffMin % 60;
                    minutesCell.textContent = `(${hours}h ${mins}m)`;
                  } else {
                    minutesCell.textContent = `(${diffMin} m)`;
                  }
      
                  // aimed departure time
                  const timeCell = document.createElement('td');
                  timeCell.className = 'travelline-time';
                  timeCell.textContent = item.aimed;
      
                  // service
                  const serviceCell = document.createElement('td');
                  serviceCell.className = 'travelline-service';
                  serviceCell.textContent = item.service;
      
                  // direction
                  const directionCell = document.createElement('td');
                  directionCell.className = 'travelline-direction';
                  directionCell.textContent = "To " + item.direction.replace(/,/g, "").split(" ")[0];
      
                  // assemble row
                  row.appendChild(minutesCell);
                  row.appendChild(timeCell);
                  row.appendChild(serviceCell);
                  row.appendChild(directionCell);
                  table.appendChild(row);
                } else {
      
                  const row = document.createElement('tr');
                  row.className = 'travelline-row';
      
                  //Set row to a random colour
                  const colourIndex = newColour();
                  row.style.color = busColours[colourIndex];
      
                  // direction - should display'No Departures for the '
                  const directionCell = document.createElement('td');
                  directionCell.className = 'travelline-direction';
                  directionCell.textContent = item.direction + " for the ";
      
                  // service
                  const serviceCell = document.createElement('td');
                  serviceCell.className = 'travelline-service';
                  serviceCell.textContent = item.service;
      
                  // assemble row
                  row.appendChild(directionCell);    
                  row.appendChild(serviceCell);
                  table.appendChild(row);          
                }
              }); //limited results end
      
              stopWrapper.appendChild(table);
              container.appendChild(stopWrapper);
            
            });  
          return container;
        }
      });
      

      node_helper.js

      const NodeHelper = require("node_helper");
      //const fetch = require("node-fetch");	// Ensure fetch is available
      const { URL } = require("url"); 		// Ensure URL is available
      const { parseStringPromise } = require("xml2js");
      
      module.exports = NodeHelper.create({
      	async socketNotificationReceived(notification, payload) {
      		
      		console.log(`There are ${payload.stopIDs.length} stop IDs in the array`);
      		if (notification === "FETCH_ROUTES") {
      			try {
      				const authHeader = "Basic " + Buffer.from(`${payload.username}:${payload.password}`).toString("base64");
      				const siriBodies = this.buildSiriBodyArray(payload.stopIDs, payload.username, payload.timestamp);
      
          			// store results for all stops
      				const allResults = [];
      
          			// loop over each stop body
      				for (const [index, siriBody] of siriBodies.entries()) {
      					//console.log("siriBody is: ", siriBody[0]);
      					try {
      						const response = await fetch(payload.apiBase, {
      							method: "POST",
      							headers: {
      								"Content-Type": "application/xml",
      								"Authorization": authHeader
      							},
      							body: siriBody
      						});
      						if (!response.ok) {
      							console.error(`Fetch failed for stop ${payload.stopIDs[index][0]}: ${response.status}`);
      							continue;
      						}
      						const xmlText = await response.text();
      						const result = await parseStringPromise(xmlText, { explicitArray: false });
      
              				// extract departures for this stop. stopIDs [stopID, StopName, target bus, randomColours]
      						let times = this.getTimes(result, payload.stopIDs[index]);
      						console.log("times is", times)
      						if (times[0] === "No Departures"){
      							console.log("Times returned and no departures or target bus not found");
      							const theService = times[1]
      							const theStop = times[2];
      							times = [{
      								service: theService,
          							direction: 'No Departures',
          							aimed: 0,
          							raw: 0,
          							stopID: '',
          							stopName: theStop,
          							randomColour: true
          						}];
      						}
              				allResults.push(times); // Removed spread operator to seperate the data from each stop
              			} catch (err) {
              				console.error(`Error fetching stop ${payload.stopIDs[index][0]}:`, err);
              			}
              		}
          			// finally send combined result
              		//console.log(allResults);
              		this.sendSocketNotification("UPDATED_ROUTES", allResults);
              	} catch (error) {
              		console.error("General fetch error:", error);
              	}
              }
          },
      
          getTimes: function(receivedData, stopID) {
          	const departures = receivedData?.Siri?.ServiceDelivery?.StopMonitoringDelivery?.MonitoredStopVisit;
          	if (!departures){
          		console.log("No departures found at this stop");
          		return ["No Departures",stopID[2], stopID[1]];	// return 'No Departures', bus name, stop name
          	}
      
          	const departuresArray = Array.isArray(departures) ? departures : [departures];
          	const myBus = departuresArray.filter(d => d.MonitoredVehicleJourney?.PublishedLineName === stopID[2]); //target);
          	if (myBus.length === 0) {
          		console.log("No target bus found at this stop");
          		return ["No Departures",stopID[2], stopID[1]];	// return 'No Departures', bus name, stop name
          		}
          	return myBus.map(d => {
          		const call = d.MonitoredVehicleJourney.MonitoredCall;   	
          		const rawTime = call.ExpectedDepartureTime || call.AimedDepartureTime;
          		const dateObj = new Date(rawTime);
          		const isValidDate = rawTime && !isNaN(dateObj.getTime());
        			const friendlyTime = isValidDate
          			? dateObj.toLocaleTimeString('en-GB', {
          				hour: '2-digit', 
          				minute: '2-digit' 
          			})
          			: "N/A";
      
          		return {
          			service: stopID[2],		// target bus,
          			direction: d.MonitoredVehicleJourney.DirectionName,
          			aimed: friendlyTime,
          			raw: rawTime,
            			stopID: stopID[0],		// stopID
            			stopName: stopID[1],	// Description or stop name for the table heading
            			randomColour: stopID[3]	// Random colour
        			};
      		});
          },
      
          buildSiriBodyArray: function(stopIDs, username, timestamp){
          	if (stopIDs.length == 0) {
          		console.log("The stopID array is empty");
          		return undefined;
          	};
          	const siriBodys = [];
          	stopIDs.forEach((item, index) =>{
          		siriBodys.push(`<?xml version="1.0" encoding="UTF-8"?>
      				<Siri version="1.0" xmlns="http://www.siri.org.uk/">
       					<ServiceRequest>
          					<RequestTimestamp>${timestamp}</RequestTimestamp>
          					<RequestorRef>${username}</RequestorRef>
          					<StopMonitoringRequest version="1.0">
            						<RequestTimestamp>${timestamp}</RequestTimestamp>
            						<MessageIdentifier>msg-001</MessageIdentifier>
            						<MonitoringRef>${item[0]}</MonitoringRef>
          					</StopMonitoringRequest>
        					</ServiceRequest>
          	</Siri>`);
          	});
          	return siriBodys;
          }
      
      });
      
      posted in Troubleshooting
      S
      Scott-M
    • RE: My module stops MMM-Pages working

      @sdetweil
      I seem to have lost the connection to my remote pi so will have a look tomorrow when I get my hands on it

      I think it is the original pages, will have a look at the fork

      posted in Troubleshooting
      S
      Scott-M
    • My module stops MMM-Pages working

      My MagicMirror was working well, MMM-CalendarExt3 taking up the most of the display and fixed with MMM-Pages.
      The clock & weather at the top on page0 and some bus timetables on page1 and these switch around with the calendar being static.

      I was never happy with the way the bus times displayed and after lots of tweaking of the module I was using I decided to write my own to do what I wanted. It works and does what I want but MMM-Pages does nothing with my module active, all the modules are displayed all the time. If I disable my module, it all works as it should.

      I hadn’t noticed until now because I was working on it with only my module in the config.

      I know I have not posed any code or much detail yet but can if I have an idea of what would be useful. I can update here rather than paste pages of possible unrequired code now.

      This is my first attempt at writing a module, or anything in JS! so it has been a slow process. If there is something basic that I have not done that MMM-Pages requires then that is a good start.

      posted in Troubleshooting
      S
      Scott-M
    • RE: MMM-CalendarExt3 and MMM-MonthlyCalendar blank

      @sdetweil

      All good, works fine.

      posted in Troubleshooting
      S
      Scott-M
    • RE: MMM-CalendarExt3 and MMM-MonthlyCalendar blank

      @sdetweil
      So, I have tried the following, each on a freshlly flashed OS.

      1 - Copied

      sudo apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" full-upgrade
      

      directly from the raspberry.sh script, updated and then upgraded using the above and it worked.

      2 - Modified the script as below and ran it locally so I can see the output in terminal during the upgrade part, and it worked, no errors durring the upgrade. And Magic Mirror Ran successfully.

      	if [ $upgrade -eq $true ]; then
      	   sudo apt-get install pv -y >>$logfile
      	   echo "apt-get upgrade  started" >> $logfile
      	   sudo apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" full-upgrade | tee -a $logfile
      	   #upgrade_result=$(sudo apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" full-upgrade | pv -l -p)
      	   #upgrade_result=$(sudo apt-get --assume-yes full-upgrade  2>&1 | pv -l -p)
      		 #upgrade_rc=$?
      		 #echo apt-get upgrade result ="rc=$upgrade_rc $upgrade_result" >> $logfile
      	fi
      
      

      3 - Copied your raspberry.sh script to the home directory unmodified and ran it locally and it also works. Log file still reads

      apt-get upgrade result =rc=0
      

      But it worked, is the what you would expect in the log?

      4 - Run the script again directly using.

      bash -c "$(curl -sL https://raw.githubusercontent.com/sdetweil/MagicMirror_scripts/v231/raspberry.sh)"
      

      This works and the log file reads the same

      apt-get upgrade result =rc=0
      

      Running apg-get full-upgrade again show that there is nothing new to do.

      Bottom line, it works!

      Attempt no 2 running the modified script locally, the whole upgrade output went to the log file.

      posted in Troubleshooting
      S
      Scott-M
    • RE: MMM-CalendarExt3 and MMM-MonthlyCalendar blank

      @sdetweil

      MagicMirror did run though…

      posted in Troubleshooting
      S
      Scott-M
    • RE: MMM-CalendarExt3 and MMM-MonthlyCalendar blank

      @sdetweil

      It seemed to get through the upgrade stage very quickly and

      apt-get upgrade  started
      apt-get upgrade result =rc=0 Reading package lists...
      Building dependency tree...
      

      in the log seems to show the upgrade didn’t work. I will run the script locally on the pi and send the output to the termnial to see what is happening but it won’t be tonight, I have to go out now.

      Update tomorrow…

      posted in Troubleshooting
      S
      Scott-M