Mobile notifications for 3D Printers

Let's take our 3d printing mobile

I build my first 3D Printer! The Creality Ender 3 may be a budget option without flashy features, but it doesn’t have to stay this way! I don’t want to babysit my 3D printer constantly, so for the last 3 weeks, I have been working on mobile notifications for 3D printers. It’s time to showcase what I have cooked up! It works with any Octoprint enabled printer.

Awesome mobile notifications for 3D printers

Neither Octoprint’s web interface, or existing solutions (Pushbullet, Telegram) were close to what I would like to have, so I decided to take the matter in my own hands. I’m pleased to say, that the project got even more features than I initially set!

Features:

  • support for 6 filament types (PLA, ABS, TPU, PETT, PETG, HIPS)
  • support for different spool sizes (just enter weight)
  • filament cost calculation (per print)
  • energy cost calculation (per print)
  • total cost of the print
  • supports 2 energy tariffs
  • Live view and snapshots in Android notifications
  • Print ETA time
  • Print Calculated time
  • Progress in % and progress bar
  • Support for multiple printers and phones
  • Print data export (name, printer, cost breakdown, time)
  • Commands (pause, resume, cancel)
  • Auto ON/OFF
  • 180 degree time-lapse *
  • Custom lights*
  • Custom Enclosure *

*these are features I’m still working on

As you can see, this mobile notifications for 3D printers system has a pretty extensive list of features that can be added to ANY Octoprint enabled printer. There are a couple of things that you will need to read and learn first.

Requirements

To create your own mobile notifications for 3D printers you will need to get yourself familiar with the following reading list (skip links if you are familiar with the topic). You can simply just download ready made files, but before you ask questions – please check the articles.

I know the list is extensive already, but this is a very difficult tutorial to breakdown already. If you want to have something awesome, you will have to commit some time.

Limitations

I’m working with file events without indexing the names, so this only works with a single file uploaded to Octoprint. It’s not a big deal as you have to remove the print first to start a new one.

Shopping list:

I gave you some options, pick your best prices. I listed the printer from banggood.com as they are my sponsor for the 3D Printing action so give them some love!

Let’s make mobile notifications for 3D printers

First of all, this is an overview. I’m not able to write a step by step guide to every single action. It meant to guide you through the process giving you enough info to get you started with the existing files. The NodeRED Flow should work off the box.

Are you ready for this?

You will need a Raspberry Pi with a running Octoprint. I will leave the Octoprint config for you – there is plenty of tutorials there. Open settings and download the MQTT plugin. I’m using it to intercept the printer’s events. There is also API key to make the note of. You will need to feed this into the flow.

Look for MQTT plugin

I will assume you know how the Perfect Notifications work, you also completed the NodeRED for beginners.

Hardware

There was a reason I talked about Sonoff POW R2. I knew exactly where it is going to end up. I flashed it with Tasmota to get MQTT, spliced the 3D Printer’s cable and mounted the Sonoff using 3M tape. It will stay there.

I won’t be powering the Rasbperry Pi running the Octoprint from the printer’s power supply as I have other plans for that. It looks neat, I’m happy with that.

NodeRED Flow from hell

Support NotEnoughTech
Creality Ender 3 – 3D printer
Excellent quality at the budget price!

Playing with flow variables

A lot of data is saved locally. All the settings or current print values are stored using the methodflow.set(). To preserve these, I enabled saving values. It’s crucial you do the same, just follow instructions in this post.

In the essense, each time I have a piece of data coming from the printer. I will save it as variable. These will get overridden with new prints, and I reference it a lot across my functions.

Settings

Configure the parameters

Mobile notifications for 3D Printers comes with config flow. These inject nodes (I may replace it with a custom Octoprint interface if I learn how) set up the environment for the print. You can preset:

  • filament type and diameter
  • filament size and cost
  • energy tariff time and cost
  • phone API for notifications

I was not sure where I can find the info about flament profile in Octoprint, otherwise I would link the selection of the filament with the data coming from the printer.

Either way, it’s easier to configure something like this, than go through each function node looking for the references. There is also a message that will let you know if something is not ready for calculations.

Enable/Disable flows

If you seen the video, you know that I have different notification for warm up, and another one for the actual print update. I needed a way to control when these would be showing up, even if Octoprint events were treating the warm-up period and printing as one.

I came up with a neat solution when I would use print progress change to disable the warm-up messages. I used that method of letting the payloads through a couple of times before and it works well. You will see a bunch of XXXX Enable nodes. These are there to control what shows up when and usually it’s activated from a relevant node.

Events – the heart of the flow

This is the brains of the operation

Octoprint can post a lot of data each time something happens at the server level. I used that to obtain most of the events and info about the print. The main events I monitor are:

  • FirmwareData (printer connected get details)
  • FileAdded (check if settings are set)
  • FileSelected (turn on the 3d printer, get print info)
  • FileRemoved (turn off the 3d printer)
  • PrintStarted (show preheat info and estimated cost)
  • Printing (update the notification every 30sec or 1%)
  • PrintPaused (display resume variant)
  • PrintResumed (display the standard variant)
  • PrintCancelled (cancel the print and clear variables, remove notifications)
  • PrintDone (calculate the power and total cost, display a new message)

These are available using MQTT topic: octoprint/event/. Each action has the corresponding behaviour in the NodeRED.

Pre-print information

This will loop until file details are available

When the Octoprint is connected (FirmwareData), the information about the printer is saved as flow variable.

FUNCTION NODE: on connect
flow.set("PrinterName", msg.payload.name);
flow.set("PrinterModel", msg.payload.data.MACHINE_TYPE);
flow.set("PrinterFirmware", msg.payload.data.FIRMWARE_NAME);

A file can be loaded to the Octoprint. Mobile notifications for 3D Printers will check if all settings has been selected. The flow for file info will get enabled, otherwise an android notification is sent with the prompt to set up much needed details.

FUNCTION NODE: File added
var printer = flow.get("PrinterName");
var model = flow.get("PrinterModel");
var firmware = flow.get("PrinterFirmware");

var density = flow.get("filamentDensity");
var price = flow.get("filamentPriceG");
var filamentDiameter = flow.get("filamentDiameter");

if(printer === undefined || model === undefined || firmware === undefined || density === undefined || price === undefined || filamentDiameter === undefined){
var key = flow.get('CurrentPhone');
var url = "https://autoremotejoaomgcd.appspot.com/sendmessage";
var command = "NOT20";

var option1, option2, option3, option4 = " ";

if(printer === undefined || model === undefined || firmware === undefined){
    option4 = " and reconnect the printer"
}

if(density === undefined){
    option1 = " Filament type ";
}
if(price === undefined){
    option2 = ", Filament price and weight  ";
}
if(filamentDiameter === undefined){
    option3 = ", Filament diameter  ";
}
var title = "Print settings not configured";
var titleexpanded = "Please configure the follwing settings";

var text = "Set up the variable" + option1 + option2 + option3 + option4;
var textexpanded = "Set up the variable" + option1 + option2 + option3 + option4;

var body = {
  "text": {
    "text": text,
    "textexpanded": textexpanded
  },
  "title": {
    "title": title,
    "titleexpanded": titleexpanded
  },
  "icons": {
    "navbaricon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "bigicon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "smallicon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "iconexpanded": "https://image.flaticon.com/icons/png/512/44/44238.png"
  },
  "notificationid": "Ender3",
  "persistent": false,
  "cancel": false,
  "priority": 3,
  
  "color": "#b9512c",
  "backgroundcolor": "#fafafa",
};

var x = JSON.stringify(body);
var encodedBody = encodeURIComponent(x);

msg.url = url + "?key=" + key + "&message=" +command + "=:="+ encodedBody;
flow.set("dataPresent",false);
return [msg,null];
}
flow.set("dataPresent",true);
return [null, msg];

Once the file is selected, NodeRED will keep spamming the Octoprint every 2 seconds until it gets the details of the print. Make sure to add your API key from Octoprint. I saved it as a global variable using my credentials system.

Query Octoprint: function nodes
Query OctoprintPrint StatsSave Data
//check octoprint for API key - see credential tutorial https://notenoughtech.com/home-automation/nodered-home-automation/serving-credentials-with-nodered/
var APIKey = global.get("API_octoprint");

msg.url = "octoprint.local/api/job?apikey=" + APIKey;
return msg;
var key = flow.get('CurrentPhone');
var url = "https://autoremotejoaomgcd.appspot.com/sendmessage";
var command = "NOT20";


flow.set("pausePrePrintInfo", false);


var title = flow.get("PrinterModel") + " has a new file";
var titleexpanded = flow.get("PrinterName")+ " "+ flow.get("PrinterModel")+  " has loaded new file.";

var text = "The print ETA is "+ flow.get("PrintTime");
var textexpanded = "The print ETA is "+ flow.get("PrintTime")+ ", and will use "+flow.get("PrintFilament")+" of filament.";





var body = {
  "text": {
    "text": text,
    "textexpanded": textexpanded
  },
  "title": {
    "title": title,
    "titleexpanded": titleexpanded
  },
  "icons": {
    "navbaricon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "bigicon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "smallicon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "iconexpanded": "https://image.flaticon.com/icons/png/512/44/44238.png"
  },
  "notificationid": "Ender3",
  "persistent": true,
  "cancel": false,
  "priority": 2,
  
  "color": "#b9512c",
  "backgroundcolor": "#fafafa",
  
  "buttons": [
    {
      "button1": {
        "icon": "iconURL",
        "label": "LiveView",
        "command": "=Ender3=liveview"
      },
      
      "button2": {
        "icon": "iconURL",
        "label": "STOP",
        "command": "=Ender3=stop"
      }
    }
  ]
};

var x = JSON.stringify(body);
var encodedBody = encodeURIComponent(x);

msg.url = url + "?key=" + key + "&message=" +command + "=:="+ encodedBody;
return msg;
//calculate print time
function timeConvert(duration) {
   var seconds = parseInt(duration % 60);
    var minutes = parseInt((duration / 60) % 60);
    var hours = parseInt((duration / 3600) % 24);

  hours = (hours < 10) ? "0" + hours : hours;
  minutes = (minutes < 10) ? "0" + minutes : minutes;
  seconds = (seconds < 10) ? "0" + seconds : seconds;

  return hours + "h:" + minutes + "m:" + seconds + "s";
}

//calculate filament in meters
function filamentConvert(lenght){
    var x = Math.floor(lenght);
    var m = parseInt(x / 1000);
    var cm = parseInt(x % 100);
    return m + "m:" + cm +"cm";
}


//ETA time
if(msg.payload.job.estimatedPrintTime === null){
    var printtime = "not available";
}
if(msg.payload.job.estimatedPrintTime !== null){
   var printtime1 = Math.floor(msg.payload.job.estimatedPrintTime);
   var printtime = timeConvert(printtime1);
}

//Calculated time
if(msg.payload.progress.printTimeLeft === null){
    var printtimeleft = "not available";
}
if(msg.payload.progress.printTimeLeft !== null){
   var printtimeleft1 = Math.floor(msg.payload.progress.printTimeLeft);
   var printtimeleft = timeConvert(printtimeleft1);
}

//Filament
if(msg.payload.job.filament === null){
    var filamentused = "not available";
}
if(msg.payload.job.filament !== null){
   var filamentused1 = Math.floor(msg.payload.job.filament.tool0.length);
   //flow.set("filamentInmm",filamentused1);
   var filamentused = filamentConvert(filamentused1);
    
}
// set flow variables
flow.set("PrintTime",printtime);
flow.set("PrintETA",printtimeleft);
flow.set("PrintFilament",filamentused);

//debug
msg.payload = {
    "printtime":printtime,
    "printtimeleft": printtimeleft,
    "filamentused": filamentused
}
return msg;

The details from these tasks are saved as variables and the NodeRED is ready to get the printing command. This loop is not accessible unless all variables are set in the settings menu.

Print - pre heat phase

Getting temps

To print, the extruder and bed have to reach certain temperatures. The temps are available in the bed: octoprint/temperature/bed and an extruder: octoprint/temperature/tool0 topics. This flow is activated in the preheat flow triggered by PrintStarted event. At the same time, the printer is turned on by Sonoff POW R2.

FUNCTION NODE: Upload Values
Query OctoprintPreHeat
if(msg.topic === "octoprint/temperature/bed"){
    var bedT = Math.floor(msg.payload.actual);
    flow.set("TempBed", bedT);
}

if(msg.topic === "octoprint/temperature/tool0"){
    var extT = Math.floor(msg.payload.actual);
    flow.set("TempExtruder", extT);
}
// debug
/*
msg.payload = {
    "bed": bedT,
    "extruder":extT
}*/
return msg;
var key = flow.get('CurrentPhone');
var url = "https://autoremotejoaomgcd.appspot.com/sendmessage";
var command = "NOT20";

flow.set("pausePrePrintInfo",true);



var density = flow.get("filamentDensity");
var length =  flow.get("filamentInmm");
var price = flow.get("filamentPriceG");
var filamentDiameter = flow.get("filamentDiameter");
var currency = "£";

var cost1 = ((3.14 * (Math.pow((filamentDiameter/2),2)) * length/10)* density) * price;
var cost2 = Math.round(cost1 * 100) / 100;
var cost = currency + cost2;
flow.set("CostOfFilament", cost2);


var title = flow.get("PrinterModel") + " is getting ready to print!";
var titleexpanded = "New file has been loaded to "+ flow.get("PrinterModel") + ". The printer is warming up!";

var text = "ETA print time "+ flow.get("PrintTime")+ "Filament needed "+flow.get("PrintFilament");
//var textexpanded = "The print ETA is "+ flow.get("PrintTime")+ ", and will use "+flow.get("PrintFilament")+" of filament. This operation will cost you " + cost;

var textexpanded = "

The print ETA: "+flow.get("PrintTime")+ "
Filament used: "+flow.get("PrintFilament")+ "
Cost: "+cost+ "
Bed Temp: "+flow.get("TempBed")+ " ºC
Extruder Temp: "+flow.get("TempExtruder")+ " ºC

"; var body = { "text": { "text": text, "textexpanded": textexpanded }, "title": { "title": title, "titleexpanded": titleexpanded }, "icons": { "navbaricon": "https://image.flaticon.com/icons/png/512/44/44238.png", "bigicon": "https://image.flaticon.com/icons/png/512/44/44238.png", "smallicon": "https://image.flaticon.com/icons/png/512/44/44238.png", "iconexpanded": "https://image.flaticon.com/icons/png/512/44/44238.png" }, "notificationid": "Ender3", "persistent": false, "cancel": false, "priority": 0, "cost": cost, "color": "#b9512c", "backgroundcolor": "#fafafa", "buttons": [ { "button1": { "icon": "iconURL", "label": "Cancel", "command": "=Ender3=stop" } } ] }; var x = JSON.stringify(body); var encodedBody = encodeURIComponent(x); msg.url = url + "?key=" + key + "&message=" +command + "=:="+ encodedBody; return msg;

This will continue updating the notification every 30 sec until the printing is in progress.

Turn Printer on/off and enable printer

Since the printer is now powered on, we should start monitoring the power use. This is why you see the Printer data on/off (power data) toggles. It actually took me several hours to figure out the best approach if you want to have more than a single tariff.

Sonoff POW R2
Support NotEnoughTech
Buy Sonoff POW R2
Control a power outlet and monitor the power use at the same time

Printing in progress

Calculating the use of power using multiple tariffs had proven to be complicated at first. Instead of creating the time ranges, I decided to use an alternative solution. In reality, I probably run my prints crossing 2 different power rates. To to calculate this efficient as possible, I will calculate how much I pay per minute (at current rates) and then sum up the cost in total. This will save me calculating the power use in tariff 1 vs tariff 2 and the tariff N.

FUNCTION NODE: Calculate power
var power = msg.payload.StatusSNS.ENERGY.Power;
var price = flow.get("ElectricityCost");
var total = flow.get("CostOfPower");

//check if array exists
if(!total || !total.length){
        total = [];
    }
    

var costPerMinute =  power/1000 * price / 60;
total.push(costPerMinute);
flow.set("CostOfPower", total);

//debug
msg.costPerMinute = total;

return msg;

The calculation will continue until the end of the printing process. I will add the values from the total[] array when the final notification is being created.

Print is happening

Extruder is hot, so is the print bed. The print is being created layer by layer. At this point I would like to get a new notification. I will cancel the warm-up message flow and start the print updates.

These will be pushed to the phone every 30 sec or 1% whichever is slower. If you open your ports, you will be able to receive the pictures from Octoprint and use it in your Android notification.

FUNCTION NODE: Printing
UpdatePrint pausedPrint cancelledCancelPauseResumeCancel
var key = flow.get('CurrentPhone');
var url = "https://autoremotejoaomgcd.appspot.com/sendmessage";
var command = "NOT20";

flow.set("pausePrePrintInfo", false);

var streamURL = global.get("octoURL") + ":8080/?action=snapshot";


var title = "Printing progress: "+ flow.get("PrintProgress")+"%. on"     +flow.get("PrinterModel");
var titleexpanded = "Printing progress: "+ flow.get("PrintProgress")+"%. on"     +flow.get("PrinterModel");

var text = "ETA "+ flow.get("PrintETA");
var textexpanded = "ETA: "+flow.get("PrintTime")+
            ". Time left: "+flow.get("PrintETA");
            

var progress = flow.get("PrintProgress");


var body = {
  "text": {
    "text": text,
    "textexpanded": textexpanded
  },
  "title": {
    "title": title,
    "titleexpanded": titleexpanded
  },
  "icons": {
    "navbaricon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "bigicon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "smallicon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "iconexpanded": "https://image.flaticon.com/icons/png/512/44/44238.png"
  },
  "notificationid": "Ender3",
  "persistent": true,
  "cancel": false,
  "priority": 0,
  "progress": progress,
  "maxprogress": 100,
  "color": "#b9512c",
  "backgroundcolor": "#fafafa",
  "picture": streamURL,
  "url": streamURL,
  "buttons": [
    {
      "button1": {
        "icon": "iconURL",
        "label": "LiveView",
        "command": "=Ender3=liveview"
      },
      "button2": {
        "icon": "iconURL",
        "label": "Pause",
        "command": "=Ender3=pause"
      },
      "button3": {
        "icon": "iconURL",
        "label": "STOP",
        "command": "=Ender3=stop"
      }
    }
  ]
};

var x = JSON.stringify(body);
var encodedBody = encodeURIComponent(x);

msg.url = url + "?key=" + key + "&message=" +command + "=:="+ encodedBody;
return msg;
var key = flow.get('CurrentPhone');
var url = "https://autoremotejoaomgcd.appspot.com/sendmessage";
var command = "NOT20";


var title = "Printing on "+flow.get("PrinterModel")+ " is PAUSED";
var titleexpanded = flow.get("PrinterName")+ " "+ flow.get("PrinterModel")+  "is PAUSED.";

var text = "ETA "+ flow.get("PrintETA");
var textexpanded = "

The print ETA: "+flow.get("PrintTime")+ "
Time left: "+flow.get("PrintETA")+ "
Progress: "+flow.get("PrintProgress")+ "%
Bed Temp: "+flow.get("TempBed")+ " ºC
Extruder Temp: "+flow.get("TempExtruder")+ " ºC

"; var progress = flow.get("PrintProgress"); var body = { "text": { "text": text, "textexpanded": textexpanded }, "title": { "title": title, "titleexpanded": titleexpanded }, "icons": { "navbaricon": "https://image.flaticon.com/icons/png/512/44/44238.png", "bigicon": "https://image.flaticon.com/icons/png/512/44/44238.png", "smallicon": "https://image.flaticon.com/icons/png/512/44/44238.png", "iconexpanded": "https://image.flaticon.com/icons/png/512/44/44238.png" }, "notificationid": "Ender3", "persistent": true, "cancel": false, "priority": 0, "progress": progress, "maxprogress": 100, "color": "#b9512c", "backgroundcolor": "#fafafa", "picture": "http://hometime.ddns.net:8080/?action=snapshot", "url": "http://hometime.ddns.net:8080/?action=snapshot", "buttons": [ { "button1": { "icon": "iconURL", "label": "LiveView", "command": "=Ender3=liveview" }, "button2": { "icon": "iconURL", "label": "Resume", "command": "=Ender3=resume" }, "button3": { "icon": "iconURL", "label": "STOP", "command": "=Ender3=stop" } } ] }; var x = JSON.stringify(body); var encodedBody = encodeURIComponent(x); msg.url = url + "?key=" + key + "&message=" +command + "=:="+ encodedBody; return msg;
var key = flow.get('CurrentPhone');
var url = "https://autoremotejoaomgcd.appspot.com/sendmessage";
var command = "NOT20";

flow.set("printUpdate", false);
flow.set("PrintProgress",0);
flow.set("CostOfPrint", null);
flow.set("CostOfPower", null);
flow.set("CostOfFilament", null);
flow.set("pausePrePrintInfo", false);



var title = flow.get("PrinterModel");
var titleexpanded = "The printing job on "+flow.get("PrinterModel")+ " has been cancelled";

var text = "The print has been cancelled";
var textexpanded = "The print has been cancelled";

var body = {
  "text": {
    "text": text,
    "textexpanded": textexpanded
  },
  "title": {
    "title": title,
    "titleexpanded": titleexpanded
  },
  "icons": {
    "navbaricon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "bigicon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "smallicon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "iconexpanded": "https://image.flaticon.com/icons/png/512/44/44238.png"
  },
  "notificationid": "Ender3a",
  "persistent": false,
  "cancel": false,
  "priority": 3,
  
  "color": "#b9512c",
  "backgroundcolor": "#fafafa"
};

var x = JSON.stringify(body);
var encodedBody = encodeURIComponent(x);

msg.url = url + "?key=" + key + "&message=" +command + "=:="+ encodedBody;
return msg;
var key = flow.get('CurrentPhone');
var url = "https://autoremotejoaomgcd.appspot.com/sendmessage";
var command = "NOT20";

flow.set("printUpdate", false);
flow.set("PrintProgress", 0);


var body = {
  "notificationid": "Ender3",
  "cancel": true
};

var x = JSON.stringify(body);
var encodedBody = encodeURIComponent(x);

msg.url = url + "?key=" + key + "&message=" +command + "=:="+ encodedBody;
return msg;
msg.url = "http://octoprint.local/api/job?apikey="+global.get("API_octoprint");
msg.payload={"command":"pause",
             "action": "pause"
};
return msg;

msg.url = "http://octoprint.local/api/job?apikey="+global.get("API_octoprint");
msg.payload={"command":"pause",
             "action": "resume"
};
return msg;
msg.url = "http://octoprint.local/api/job?apikey="+global.get("API_octoprint");
msg.payload={"command": "cancel"};
return msg;

Commands handling

Print complete

Now, that the print is complete, we can perform the final task. Create a notification that will give us the print summary.

FUNCTION NODE: Printing complete
print doneprint completed
flow.set("printUpdate", false);
flow.set("PrintProgress",0);
flow.set("pausePrePrintInfo", false);

return msg;
//calculate print time
function timeConvert(duration) {
   var seconds = parseInt(duration % 60);
    var minutes = parseInt((duration / 60) % 60);
    var hours = parseInt((duration / 3600) % 24);

  hours = (hours < 10) ? "0" + hours : hours;
  minutes = (minutes < 10) ? "0" + minutes : minutes;
  seconds = (seconds < 10) ? "0" + seconds : seconds;

  return hours + "h:" + minutes + "m:" + seconds + "s";
}


//message
var key = flow.get('CurrentPhone');
var url = "https://autoremotejoaomgcd.appspot.com/sendmessage";
var command = "NOT20";

var totaltime = timeConvert(msg.payload.time);
var filename = msg.payload.filename;
flow.set("FileName", filename);
flow.set("FinishedPrintTime", totaltime);

//cost calculation
var sum = flow.get("CostOfPower");

function add(accumulator, a) {
    return accumulator + a;
}

var costOfElectricity = sum.reduce(add);
var costOfFilament = flow.get("CostOfFilament");
if(costOfElectricity <0.01){
    costOfElectricity = 0.01;
}
var costOfPrint =  Math.round(costOfElectricity * 100) / 100 + costOfFilament;

//clear cost valuses  and store total cost
flow.set("CostOfPrint", costOfPrint);
flow.set("CostOfPower", null);
flow.set("CostOfFilament", null);

// format messages
var title = flow.get("PrinterModel") + " the print job is finished.";
var titleexpanded = flow.get("PrinterModel")+  " has completed the job.";

var text = flow.get("PrinterModel")+ " has finished printing" + ". The total cost is " +costOfPrint;
var textexpanded = "The print is ready. Please collect it from "+ flow.get("PrinterModel")+ 
    ". The print cost breakdown is: Electricity £"+costOfElectricity + " + Filament £"+ costOfFilament + " = £" +costOfPrint;


// AR Body
var body = {
  "text": {
    "text": text,
    "textexpanded": textexpanded
  },
  "title": {
    "title": title,
    "titleexpanded": titleexpanded
  },
  "icons": {
    "navbaricon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "bigicon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "smallicon": "https://image.flaticon.com/icons/png/512/44/44238.png",
    "iconexpanded": "https://image.flaticon.com/icons/png/512/44/44238.png"
  },
  "notificationid": "Ender3a",
  "persistent": false,
  "cancel": false,
  "priority": 3,
  "printStats": filename+"|"+totaltime+ "|"+costOfPrint+"|"+ Math.round(costOfElectricity * 100) / 100 +"|"+ costOfFilament,
  "color": "#b9512c",
  "backgroundcolor": "#fafafa",
  "buttons": [
    {
      "button1": {
        "icon": "iconURL",
        "label": "PICTURE",
        "command": "=Ender3=liveview"
      },
      "button2": {
        "icon": "iconURL",
        "label": "Download timelapse",
        "command": "=Ender3=downloadTL"
      },
       "button3": {
        "icon": "iconURL",
        "label": "Save Print Stats",
        "command": "=Ender3=export"
      },
    }
  ]
  
};

//send Perfect Notification 2.0
var x = JSON.stringify(body);
var encodedBody = encodeURIComponent(x);
msg.url = url + "?key=" + key + "&message=" +command + "=:="+ encodedBody;

return msg;

Android Notifications

For the most part, I'm following the Perfect Notifications write up. There are small changes regardless of the messages displayed by Tasker. The differences are caused by the fields being populated by variables, but not being in use.

In AutoNotification, if you add the "progress bar" variable and set it to "null" the progress bar will still display as empty. This is why I have several versions of the same notification. I also use the most important variable in Tasker way of passing the information - it's a great way of dealing with Tasker sensitive data.

TASKER PROFILE: Perfect AutoNotification AR
    Profile: Perfect AutoNotification AR 
    	Event: AutoRemote [ Configuration:NOT20 ]
    Enter: PN Create Notification 
    	
    	A1: Variable Set [ Name:%Test To:%arcomm Recurse Variables:Off Do Maths:Off Append:Off ] If [ %arcomm Set ]
    	
    	A2: Variable Set [ Name:%Test To:%aacomm Recurse Variables:Off Do Maths:Off Append:Off ] If [ %aacomm Set ]
    	A3: AutoTools Json Read [ Configuration:Input Format: Json
    Json: %Test
    Fields: text.text,text.textexpanded,title.title,title.titleexpanded,icons.navbaricon,icons.bigicon,icons.iconexpanded,buttons[0].button1.label,buttons[0].button1.icon,buttons[0].button1.command,buttons[0].button2.label,buttons[0].button2.icon,buttons[0].button2.command,
    buttons[0].button3.label,buttons[0].button3.icon,buttons[0].button3.command,priority,persistent,notificationid,progress,maxprogress,picture,url,cancel,printStats
    Separator: , Timeout (Seconds):60 ] 
    	
    	A4: AutoNotification [ Configuration:Title: %title_title
    Text: %text_text
    Url: l
    Icon: %icons_bigicon
    Status Bar Icon Manual: %icons_navbaricon
    Status Bar Text Size: 16
    Background Color: %color
    Colorize Background: true
    Id: %notificationid
    Priority: %priority
    Persistent: %persistent
    Title Expanded: %title_titleexpanded
    Text Expanded: %text_textexpanded
    Icon Expanded: %icons.iconexpanded
    Separator: ,
    Button 1: %buttons_button1_command
    Label 1: %buttons_button1_label
    Action Icon 1 Manual: %buttons_button1_icon
    Button 2: %buttons_button2_command
    Label 2: %buttons_button2_label
    Action Icon 2 Manual: %buttons_button2_icon
    Button 3: %buttons_button3_command
    Label 3: %buttons_button3_label
    Action Icon 3 Manual: %buttons_button3_icon Timeout (Seconds):20 ] If [ %cancel ~ false & %maxprogress !Set ]
    	
    	A5: AutoNotification [ Configuration:Title: %title_title
    Text: %text_text
    Url: %url
    Icon: %icons_bigicon
    Status Bar Icon Manual: %icons_navbaricon
    Status Bar Text Size: 16
    Background Color: %color
    Colorize Background: true
    Id: %notificationid
    Priority: %priority
    Max Progress: 100
    Progress: %progress
    Persistent: %persistent
    Title Expanded: %title_titleexpanded
    Picture: %picture
    Skip Picture Cache: true
    Text Expanded: %text_textexpanded
    Icon Expanded: %icons.iconexpanded
    Separator: ,
    Button 1: %buttons_button1_command
    Label 1: %buttons_button1_label
    Action Icon 1 Manual: %buttons_button1_icon
    Button 2: %buttons_button2_command
    Label 2: %buttons_button2_label
    Action Icon 2 Manual: %buttons_button2_icon
    Button 3: %buttons_button3_command
    Label 3: %buttons_button3_label
    Action Icon 3 Manual: %buttons_button3_icon Timeout (Seconds):20 ] If [ %cancel ~ false & %maxprogress Set ]
    	
    	A6: AutoNotification Cancel [ Configuration:Id: %notificationid
    Cancel Persistent: true Timeout (Seconds):20 ] If [ %cancel ~ true ]
    	A7: Variable Set [ Name:%LastPrint To:%printstats Recurse Variables:Off Do Maths:Off Append:Off ] If [ %printstats Set ]

I will need 2 notification types. One with the print update (progress bar) and the template for other notifications. In addition to this I have added the AutoNotification Cancel, to be able to remove the persistent settings.

Lastly I have added an option to gather information about the last completed print. It's a variable that is pushed to an array when the print has been completed.

AutoNotification commands

I also made a list of custom commands. All respond to the =Ender3= prefix and each action has a differen IF condition. For Pause/Resume/Cancel these are simple HTTP requests sent back to NodeRED.

TASKER PROFILE: Ender 3 Commands
    Profile: Ender3 Commands 
    	Event: AutoNotification [ Configuration:Event Behaviour
    Filter: =Ender3=* (regex) ]
    Enter: Ender3 Commands 
    	
    	A1: HTTP Post [ Server:Port:%HTTPrequest Path:/Ender Data / File:printer=Pause Cookies: User Agent: Timeout:10 Content Type: Output File: Trust Any Certificate:On ] If [ %anmessage ~R pause ]
    	
    	A2: HTTP Post [ Server:Port:%HTTPrequest Path:/Ender Data / File:printer=Resume Cookies: User Agent: Timeout:10 Content Type: Output File: Trust Any Certificate:On ] If [ %anmessage ~R resume ]
    	
    	A3: HTTP Post [ Server:Port:%HTTPrequest Path:/Ender Data / File:printer=Cancel Cookies: User Agent: Timeout:10 Content Type: Output File: Trust Any Certificate:On ] If [ %anmessage ~R stop ]
    	
    	A4: AutoTools Chrome Custom Tabs [ Configuration:Url: http://hometime.ddns.net/webcam/?action=stream Timeout (Seconds):300 ] If [ %anmessage ~R liveview ]
    	
    	A5: Array Push [ Variable Array:%PrintedItems Position:1 Value:%LastPrint Fill Spaces:Off ] If [ %anmessage ~R export ]

To see the LiveView I used a custom Chrome window with a defined URL. (you can get the URL in the Octoprint setting - forward port 8080 to have access to the camera on the go). Lastly, to save the print details to an array (feel free to use that data in whatever way you wish) I used Push to Array action.

Over all the Tasker part of this relies on the Perfect Notification tutorial. I have added extra JSON fields when I felt it's needed, but that's pretty much all. It show how good that Tasker project is and how flexible are these notifications.

Conclusion

This has been one of the more challenging projects I took on. I learned new things, I feel more comfortable with JavaScript and I'm looking forward to countless hours I spend with my 3D printer. I still have some work to do, so I will continue bug hunting, and working on the 180-degree camera jig and some extra LED lights. So if you are interested - follow me for updates. If you enjoy mobile notifications - consider buying me a coffee... I spent most of my budget on that boost! Questions? Hit the Reddit post here.

Support NotEnoughTech
A lot of time and effort goes into keeping NotEnoughTech alive! If my work helped you out, consider buying me a coffee or check out exclusive rewards available to Patreon supporters.
SHARE