Head first developing MM module for extreme beginners


  • Module Developer

    Head first developing MM module for extreme beginners

    Today morning, I’ve got two messages from two members of this forum at the same time. Both of two asked me the same thing - “How can I show my contents on MM with my module?”
    So, here is a simple guide for developing MM module. This is not a royal road or perfect answer, but I hope this could be a help

    This article is not about using git or any other deeper knowledge. I assume you are familiar with javascript(nodeJS) at least.

    Make a workspace

    cd ~/MagicMirror/modules
    mkdir MMM-Test
    cd MMM-Test
    touch MMM-Test.js  
    

    MMM-Test.js is your main module script. It will be called by MM core at running.

    Let’s make something.

    configuration

    First, add your module into MM framework. open ~/MagicMirror/config/config.js and add your module.

    modules: [
     {
      module: "MMM-Test",
      position: "top_left",
      config: {}
     },
    ]
    

    For simple things, remove all other modules except your MMM-Test.

    MMM-Test.js

    open MMM-Test.js and edit.

    nano MMM-Test.js
    

    In that, copy this and paste into MMM-Test.js.

    Module.register("MMM-Timetable", {
      defaults: {},
      start: function () {},
      getDom: function() {},
      notificationReceived: function() {},
      socketNotificationReceived: function() {},
    })
    
    

    There are many other things to study but currently, we will focus on these 5 things.

    getDom()

    This function will render your content on MM screen.
    Let’s do something.

    getDom: function() {
      var element = document.createElement("div")
      element.className = "myContent"
      element.innerHTML = "Hello, World!"
      return element
    },
    

    <div> element is created and returned to MM for rendering.
    When MM needs your module’s output being refreshed, this function will be called by MM core.
    See how it works.

    cd ~/MagicMirror
    npm start dev
    

    0_1533642942933_guide_1.png

    defaults:{}

    You can use default configuration values. Let’s do it

    defaults: {
      foo: "I'm alive!"
    },
    getDom: function() {
      var element = document.createElement("div")
      element.className = "myContent"
      element.innerHTML = "Hello, World! " + this.config.foo
      return element
    },
    

    your configuration value foo could be used like this.config.foo.
    this in this module, it directs module itself. you can access various module values with it.
    See how it works.

    0_1533642980707_guide_2.png

    And you can customize configuration values with config.js
    set your config.js like this;

    {
      module: "MMM-Test",
      position: "top_left",
      config: {
        foo: "I'm the King of the world!"
      }
    },
    

    The result will be like this;
    0_1533643071611_guide_3.png

    start() & updateDom()

    This function will be executed when your module is loaded successfully.
    Let’s change something.

    start: function (){
      this.count = 0
      var timer = setInterval(()=>{
        this.updateDom()
        this.count++
      }, 1000)
    },
    
    getDom: function() {
      var element = document.createElement("div")
      element.className = "myContent"
      element.innerHTML = "Hello, World! " + this.config.foo
      var subElement = document.createElement("p")
      subElement.innerHTML = "Count:" + this.count
      subElement.id = "COUNT"
      element.appendChild(subElement)
      return element
    },
    

    0_1533643168301_guide_4.png

    After your module is loaded, your module’s output will be updated per every 1 second.
    Whenever you want to update your output, you can call .updateDom() to let MM also call your .getDom() to redraw the output. (Don’t try to call getDom() directly by yourself. It will not work as your expectation. Rendering is delegated to the MM core.)

    By the way, start() is not a good place to control drawing, because start() is called before your DOM objects are first rendered. Above code is somewhat bad behavior.

    notificationReceived()

    MM and modules are communicating each other with notification. Your module can receive notification and send it also.

    start: function (){
      this.count = 0
    },
    
    getDom: function() {
      var element = document.createElement("div")
      element.className = "myContent"
      element.innerHTML = "Hello, World! " + this.config.foo
      var subElement = document.createElement("p")
      subElement.innerHTML = "Count:" + this.count
      subElement.id = "COUNT"
      element.appendChild(subElement)
      return element
    },
    
    notificationReceived: function(notification, payload, sender) {
      switch(notification) {
        case "DOM_OBJECTS_CREATED":
          var timer = setInterval(()=>{
            this.updateDom()
            this.count++
          }, 1000)
          break
      }
    },
    

    The result looks so same with previous codes. But your timer will be started after when “DOM_OBJECTS_CREATED” notification is received but not when the module is loaded.
    DOM_OBJECTS_CREATED notification is emitted when all of modules are loaded and rendered(with getDom()) first time. At the moment, you can manipulate your output in your module.

    Another way to control output

    Once your DOM is ready(after first calling getDom() and DOM_OBJECTS_CREATED notification is emitted), you can access your DOM with script.

    notificationReceived: function(notification, payload, sender) {
      switch(notification) {
        case "DOM_OBJECTS_CREATED":
          var timer = setInterval(()=>{
            //this.updateDom()
            var countElm = document.getElementById("COUNT")
            countElm.innerHTML = "Count:" + this.count
            this.count++
          }, 1000)
          break
      }
    },
    

    Instead updateDom(), direct access to DOM is used.

    node_helper.js

    But your MMM-Test.js has some limitation. Because it is run on MM framework and Browser(Electron/Chromium or Midori)
    When you need more than what browser can provide, you should use node_helper.js
    By example, your MMM-Test.js cannot read or write local files by usual way (Of course, there is a hack, but that is not the point at this moment.).
    But your node_helper.js can. Or when you need to use other various node modules, node_helper.js exists for that purpose.

    Let’s make node_helper.js

    nano node_helper.js
    

    Copy this code and paste.

    var NodeHelper = require("node_helper")
    
    module.exports = NodeHelper.create({
      start: function() {},
      socketNotificationReceived: function() {},
    })
    

    This is a default template of node_helper.js. Now add some codes.

    var NodeHelper = require("node_helper")
    
    module.exports = NodeHelper.create({
      start: function() {
        this.countDown = 10000000
      },
      socketNotificationReceived: function(notification, payload) {
        switch(notification) {
          case "DO_YOUR_JOB":
            this.sendSocketNotification("I_DID", (this.countDown - payload))
            break
        }
      },
    })
    

    Like MMM-Test.js, start() will be executed when node_helper.js is loaded and connected to your module.
    Between MMM-Test.js and node_helper.js, socketNotification is used for communication. (similar with notification)
    You can get socketNotification with socketNotificationReceived() and can send it by sendSocketNotification().

    Then, you should modify MMM-Test.js

    getDom: function() {
      var element = document.createElement("div")
      element.className = "myContent"
      element.innerHTML = "Hello, World! " + this.config.foo
      var subElement = document.createElement("p")
      subElement.id = "COUNT"
      element.appendChild(subElement)
      return element
    },
    
    notificationReceived: function(notification, payload, sender) {
      switch(notification) {
        case "DOM_OBJECTS_CREATED":
          var timer = setInterval(()=>{
            this.sendSocketNotification("DO_YOUR_JOB", this.count)
            this.count++
          }, 1000)
          break
      }
    },
    
    socketNotificationReceived: function(notification, payload) {
      switch(notification) {
        case "I_DID":
          var elem = document.getElementById("COUNT")
          elem.innerHTML = "Count:" + payload
          break
      }
    },
    

    Result will be like this;

    0_1533643217699_guide_5.png

    Now you made your MMM-Test.js to work with node_helper.js
    Of course, this is not so good example to show what node_helper.js can do.
    But I believe you can understand how node_helper.js and MMM-Test.js work with each other.

    Well. You’ve learned 90% of how to develop MM module. the remains are described in https://github.com/MichMich/MagicMirror/blob/master/modules/README.md
    Good luck to you.



  • I cant get the text to appear when I run it, I made the Test.js file in the MagicMirror modules folder and copied in what was posted and put the config in the config.js file but when I run it the screens all black, here’s what happens when I try to run it

    pi@raspberrypi:~/MagicMirror/modules $ cd ~/MagicMirror
    pi@raspberrypi:~/MagicMirror $ npm start dev

    magicmirror@2.8.0 start /home/pi/MagicMirror
    sh run-start.sh “dev”

    Starting MagicMirror: v2.8.0
    Loading config …
    Loading module helpers …
    No helper found for module: MMM-Test.
    All module helpers loaded.
    Starting server on port 8080 …
    Server started …
    Sockets connected & modules started …
    Launching application.
    Shutting down server…
    pi@raspberrypi:~/MagicMirror $

    any help would be greatly appreciated I dont see what I’m missing


  •