Automatické vypínání 3D tiskárny

Aneb jedna z dalších modifikací 3D tiskárny - naučíme jí se sama vypnout. Způsobů, jak tohle řešit je vícero, navíc i záleží, jak konkrétně daná tiskárna s komponenty okolo vypadá. Moje sestava je navíc ještě trochu specifická v jedné věci - všechny tiskárny mám doplněné RaspberryPi s běžícím OctoPrintem, přičemž Raspberry je přes DC/DC měnič napájená přímo ze zdroje od tiskárny. To sebou nese jednu komplikaci - Raspberry by se správně mělo napřed softwarově vypnout, než se mu (se zbytkem tiskárny) natvrdo odpojí napájení. Takže, následující postup řeší tuhle situaci a je to jedno, patrně z mnoha, řešení.

Požadavky a co by to mělo dělat

Jak již padlo, Raspberry by se napřed měla vypnout korektně, softwarově. Od toho se to celé odvíjí. Vypnutí Raspberry s OctoPrintem také může nastávat ve dvou situacích. Buďto jej chceme vypnout ručně (jsme u ovládání). Nebo tiskárna zrovna tiskne a chceme, aby se celá vypla po dokončení tisku. V prvním případě vypnutí OctoPrintu je manuální činnost, kdy prostě v menu zvolíme možnost shutdown. Elektronika by pak na to měla reagovat; tedy dát Raspberry chvíli náskok, aby se korektně vypnula a pak shodit napájení. Druhý případ je trochu složitější. Zde musíme donutit Raspberry/OctoPrint, aby se po skončení tisku a dochlazení hotendu tiskárny korektně vypnul a následně, stejně jako v předchozím případě, po nějaké prodlevě se odpojilo napájení. Tuhle variantu naštěstí řeší celkem šikovný plugin.

Prerekvizity aneb co je potřeba

Kdo si hraje nezlobí, takže v mém případě jsem řešení postavil nad softwarem pro domácí automatizaci :-) Máme teda: - 3D tiskárnu :-) - RaspberryPi s běžícím OctoPrintem a napájenou stejným zdrojem jako od tiskárny - Spínací modul Sonoff POW R2 a naflashovaným firmwarem Tasmota. Postup bude identicky fungovat i na jiné sonoffí moduly (basic nebo klasické spínací zásuvky). A samozřejmě se dá použít cokoliv jiného, ale už na to nebude doslova pasovat návod :-) - Běžící MQTT broker a nainstalovaný Node-RED - Funkční síťové prostředí s WiFi. Tedy zásuvka vidí na MQTT brokera, Node-RED vidí na MQTT brokera, OctoPrint vidí na HTTP rozhraní Node-RED

O spínané zásuvce...

Sonoffí moduly s Tasmota firmwarem mají dvě velké výhody: jsou levné (zmíněný POW R2 se dá koupit asi za 15 dolarů a kromě funkce spínané zásuvky umí i měřit spotřebu a příkon) a s tímhle opensource firmwarem poměrně snadno použitelné z čehokoliv. Naopak s originálním čínským firmwarem zas tak použitelné nejsou (tedy, asi to můžete ovládat z telefonu přes Čínu, ale donutit to spolupracovat s něčím dalším bude těžké... ne-li nemožné). Nevýhoda je, že přeflashování firmwaru je uživatelsky ne zrovna přívětivé. V tomto případě nechci zabíhat příliš do detailů, nicméně postup vypadá ve zkratce takto: modul se rozebere a připájí se do něj kolíky na UART rozhraní. Stáhne se firmware (nebo zdrojáky, dá se to flashovat z Arduino IDE), modul se připojí UART USB převodníkem na 3,3V (který je potřeba vlastnit) k počítači a přeflashuje. Následně se modul zapne, připojíme se na WiFi síť, kterou vysílá, prohlížečem mu nastavíme naší WiFi, a... funguje to. Existuje snazší varianta - v českých e-shopech se dá zakoupit modul, ve kterém je tasmota firmware už naflashován. Jen tím zase padá ten bod, že je to levné...

Nastavení spínané zásuvky

Nastavení zásuvky, ve které je Tasmota firmware, je poměrně dosti snadné. Otevřeme si configuration -> MQTT. Do Host vyplníme adresu, kde nám běží MQTT broker. A pak je ještě dobré změnit položku Topic na něco unikátního, pod čím tuto zásuvku identifikujeme. Defaultně zde je sonoff, nastavil jsem sonoff-rebelix. Vysloveně nutné to měnit není, ale když se v síti sejde více zásuvek se stejným topicem, tak budete ovládat všechny zaráz :-)

Kreslení flow Node-RED

Node-RED bude dělat ten centrální mozek. Vytvoříme v něm HTTP Input; ten způsobí, že se naše flow spustí po zaslání správného HTTP Requestu. Zbytek flow je jednoduchý - počkáme chvíli (nastavil jsem 30 sekund), než se Raspberry korektně vypne a následně přes MQTT pošleme spínané zásuvce pokyn k vypnutí. Flow tedy vypadá takto:

Pro vysvětlenou, první pole je zmíněný HTTP input. V něm se nastaví na jakou URL adresu a jakou HTTP metodu bude reagovat. URL je v podstatě jedno, jen je potřeba z OctoPrintu volat pak tu správnou. HTTP metoda je v podstatě také jedno (ta se tam nastavuje taky), ale doporučuji nepoužívat GET, aby prostým otevřením dané URL adresy omylem v prohlížeči nedošlo k vypnutí tiskárny. Horní část provádí vypínání - 30 sekund počká, následně MQTT příkaz na vypnutí zásuvky (posíláme do topicu cmnd/sonoff-rebelixbox/Power hodnotu Off - topic je zase závislý na tom, co jsme nastavili u zásuvky). Ve spodní části je template node s hodnotou OK a HTTP Response. Tato část je poměrně důležitá - bez ní totiž Node-RED zpátky na HTTP request nic nepošle a druhá strana (v našem případě OctoPrint) čeká na odpověď. A čeká a čeká... Jenže timer mezitím běží, takže následek je, že Raspberry se nevypne, ale Node-RED po 30 sekundách napájení stejně urve.

Pro ty, kteří jsou líní to klikat, zde je export z Node-RED, který stačí zkopírovat do schránky a naimportovat:
  1. [{"id":"56b6c2a5.688eec","type":"http in","z":"31fd693c.e1f4d6","name":"","url":"/octoprint-shutdown/rebelix-box","method":"post","upload":false,"swaggerDoc":"","x":466,"y":231,"wires":[["9e4a9dee.5e189","e70b2a8e.a07098"]]},{"id":"3e025a87.730966","type":"mqtt out","z":"31fd693c.e1f4d6","name":"sonoff-rebelixbox","topic":"cmnd/sonoff-rebelixbox/Power","qos":"","retain":"","broker":"c52fb11a.5e5cb","x":1091.5,"y":225,"wires":[]},{"id":"9e4a9dee.5e189","type":"trigger","z":"31fd693c.e1f4d6","op1":"","op2":"Off","op1type":"nul","op2type":"str","duration":"30","extend":false,"units":"s","reset":"","bytopic":"all","name":"","x":733,"y":226,"wires":[["3e025a87.730966"]]},{"id":"e70b2a8e.a07098","type":"template","z":"31fd693c.e1f4d6","name":"http res","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"OK","output":"str","x":706,"y":334,"wires":[["5d09dc49.33a584"]]},{"id":"5d09dc49.33a584","type":"http response","z":"31fd693c.e1f4d6","name":"","statusCode":"","headers":{},"x":892,"y":335,"wires":[]},{"id":"c52fb11a.5e5cb","type":"mqtt-broker","z":"","name":"turris","broker":"localhost","port":"1883","clientid":"nodered","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

Konfigurace OctoPrintu

A nastává poslední, nejsložitější konfigurace. Otevřeme si konfiguraci OctoPrintu. Jako první otevřeme pluginy a doinstalujeme plugin ShutDown Printer od devildanta (pozor, je tam ještě jeden plugin se stejným názvem, který ale nemá požadovanou funkcionalitu). Následně si v sekci OCTOPRINT otevřeme Server, zajímat nás bude pole Shutdown system. V něm obvykle je tohle:
  1. sudo shutdown -h now
Změníme to takto (přičemž node-red-host vyměníme za IP adresu nebo hostname, kde nám běží Node-RED nastavený dle postupu výše. Případně číslo za dvojtečkou vyměníme za port, kde to běží, pokud je jiný):
  1. curl -X POST <a href="http://node-red-host:1880/octoprint-shutdown/rebelix/">http://node-red-host:1880/octoprint-shutdown/rebelix/</a> && sudo shutdown -h now
Pozornost ještě zaslouží parametr za -X, který u curlu specifikuje HTTP metodu. Já jsem použil (a v nastavení výše je) POST. Pokud jste nastavili něco jiného, změňte to zde. Následně nastavíme ShutDown plugin, v sekci PLUGINS tedy otevřeme Shutdown Printer. V něm jsou na začátku 3 sekce s módy, které se použijí při vypnutí. Necháme zapnutý (activate this mode) pouze Mode API Custom. Zaškrtneme příslušnou HTTP metodu (stejně jako výše) a do URL vložíme to samé co výše (v mém případě: http://node-red-host:1880/octoprint-shutdown/rebelix/ ) V events catch nastavíme, jestli se tiskárna sama vypne i při pokaženém nebo stornovaném tisku. Následně zapneme Enable temperature target a pod teplotou níže nastavíme teplotu, při jejímž dosažení teprve dojde k vypnutí tiskárny (tedy po dochlazení). Ve firmwarech se defaultně obvykle aktivní chlazení vypíná při poklesu pod 50°C, do políčka jsem tedy nastavil 40 (o kousek níže). A důležité: do extra command/script vypíšeme:
  1. sudo shutdown -h now
Jinak nedojde k vypnutí samotné Raspberry a zase ji vypneme natvrdo.

3, 2, 1, ...

A nyní to můžeme vyzkoušet. Pokud tedy v OctoPrintu vybereme možnost shutdown, začne se vypínat. A v rozhraní Node-RED by měl zmodrovat čtverec u timeru (což znamená že běží). Po dané době by mělo dojít k vypnutí zásuvky a tedy kompletnímu odstavení tiskárny. Podobně následně můžeme vyzkoušet automatické vypnutí po tisku. Na test není potřeba nic tisknout, stačí si ručně ohřát hotend na teplotu přes nastavený vypínací limit a následně spustit a hned zrušit tisk (pokud je shutdown plugin nastaven tak, aby vypínal i při stornování tisku). Po dochlazení hotendu pod stanovenou mez by se v rozhraní OctoPrintu měl zobrazit odpočet pro možnost zrušení vypnutí a po jeho vypršení by se mělo opět provést vypnutí OctoPrintu, zmodrání timeru v Node-RED a tak dále. Hurá :-)

Šlo to udělat líp?

Popsané řešení je v některých částech trochu krkolomné - zejména v konfigurační části samotného OctoPrintu. A popravdě to není první způsob, kterým jsem to chtěl vyřešit...

Jako první řešení, v jehož funkčnost jsem doufal, bylo použití MQTT pluginu do OctoPrintu. Zmíněný plugin přes MQTT odesílá všechny možné věci z OctoPrintu. A mimo jiné odesílá i události. A jedna zajímavá událost je shutdown, kterou OctoPrint vygeneruje při svém vypínání. Původní snaha teda byla v Node-RED odchytit tuhle událost a spustit vypínací sekvenci na ní. Značnou výhodou bylo, že na straně OctoPrintu se pouze nastavil MQTT broker v konfiguraci pluginu a to bylo vše. Při vypnutí OctoPrintu jakýmkoliv způsobem mělo dojít ke spuštění sekvence a tedy odstavení tiskárny. Problém nastal při testování, kdy na jedné tiskárně to fungovalo perfektně, na druhé asi v 50% případů. Patrně se jedná o nějaký bug, ale ani nevím v které komponentě a vlastně si celkově nejsem jistý, proč se to děje. Moje domněnka je (jelikož tiskárna, na které to funguje problematicky, měla novější Raspberry 3, na které OctoPrint běhá výrazně rychleji), že OctoPrint se někdy ukončí rychleji, než událost probublá do pluginu a odešle se na MQTT brokera. Každopádně to nefungovalo spolehlivě, tak jsem hledal jiné alternativy...

Aktuální zvolené řešení funguje spolehlivě, ale má také pár nedostatků. Předně je volání HTTP endpointu definováno na dvou místech - v shutdown pluginu a v příkazu pro vypnutí. Není to tedy úplně čisté řešení. S tím se nese další nevýhoda - vypnutí nezafunguje, pokud se Raspberry vypne nějakým jiným způsobem - třeba z konzole přes SSH. Jako čistější a složitější řešení by tedy tohle šlo vyřešit asi dvěma způsoby. Buď napsat nějaký hodně jednoduchý plugin do OctoPrintu, který HTTP endpoint zavolá při shutdownu. Nebo do Raspberry docpat nějaký initscript, který udělá téže při vypínání systému. To by bylo čistší řešení, patrně fungující vždy. Nicméně nezkoušel jsem to a takhle od stolu bych čekal při tomto způsobu ještě jednu zradu - v obou těchto případech dojde k zavolání endpointu i v případě, kdy se Raspberry pouze restartuje. Pak existuje ještě jedno řešení ve spojení se zmíněným MQTT pluginem, které má ale také menší chybu :-) MQTT plugin posílá při ukončování ještě jeden event - mqtt disconnect. Jeho posílání funguje stejně nespolehlivě jako shutdown event, nicméně narozdíl od shutdown eventů je tohle nastaveno jako last will. Tedy MQTT broker tenhle topic postne i v případě, že se klient natvrdo odpojí a vytimeoutuje. Vypínání by v tomto případě tedy fungovalo spolehlivě, byť někdy s nějakou delší prodlevou (vypršení timeoutu). Zásadní nevýhoda ale je, že tenhle event se vygeneruje vždycky, když se OctoPrint odpojí od MQTT brokera. Takže k vypnutí tiskárny patrně dojde i při nějakém restartu MQTT brokera, problému na síti kdy se Raspberry odpojí a tak podobně. Takže já raději zůstávám dále u toho bastlu, který není úplně čistý, ale funguje spolehlivě a jen v těch případech, kdy to chci :-)