From 455700203a3c8bb7d9dd2df5bec7f25320f9206b Mon Sep 17 00:00:00 2001 From: "michael.winkler" <> Date: Sun, 22 Dec 2019 15:00:44 +0000 Subject: [PATCH] 37_OctoPrint.pm: new Modul git-svn-id: https://svn.fhem.de/fhem/trunk@20802 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/70_OctoPrint.pm | 541 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 541 insertions(+) create mode 100644 fhem/FHEM/70_OctoPrint.pm diff --git a/fhem/FHEM/70_OctoPrint.pm b/fhem/FHEM/70_OctoPrint.pm new file mode 100644 index 000000000..768a5cec1 --- /dev/null +++ b/fhem/FHEM/70_OctoPrint.pm @@ -0,0 +1,541 @@ +# $Id$ +############################################################################ +# 2018-10-12, v0.0.11 +# +# v0.0.11 +# - BUGFIX: https://forum.fhem.de/index.php/topic,81929.msg844975.html#msg844975 +# +# v0.0.10 +# - BUGFIX: Readings mit 0 wurden nicht geschrieben +# - CHANGE: readingsBulkUpdateIfChanged to readingsBulkUpdate +# +# v0.0.9 +# - BUGFIX: Reading "online" +# +# v0.0.8 +# - FEATURE: Reading "online" +# +# v0.0.7 +# - BUGFIX: Logeinträge PERL WARNING: Use of uninitialized value $value in string eq at fhem.pl line 4547 +# +# v0.0.6 +# - BUGFIX: https://forum.fhem.de/index.php/topic,81929.msg780110.html#msg780110 +# +# v0.0.5 +# - BUGFIX: https://forum.fhem.de/index.php/topic,81929.msg756900.html#msg756900 +# +# v0.0.3 +# - BUGFIX: Readings anzeigen von Umlauten +# - CHANGE: Send Data nonBlocking +# - FEATURE: Add Support psucontrol inkl. set Befehle turnPSUOn, turnPSUOff und togglePSU (Aktivierung über Attribut "plugin_psucontrol") +# Neue Set Befehle move_axis_x, move_axis_y, move_axis_z und extrude +# +# v0.0.2 BETA +# - FEATURE: Navigieren (gohome) +# Shutdown / Reboot / Restart (OctoPrint) +# send_gcode z.B. M500 +# +# v0.0.1 BETA +# - FEATURE: Read div. Readings +# start/stop/connect/disconnect printer +# +# 70_OctoPrint.pm +# Copyright by Michael Winkler +# e-mail: michael.winkler at online.de +# +# This file is part of fhem. +# +# Fhem is free software: you can redistribute it andor modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +############################################################################## + +package main; + +use strict; +use warnings; +use HttpUtils; +use Encode; +use Time::Piece; +use LWP::UserAgent; + +no warnings "all"; + +sub OctoPrint_Set($@); +sub OctoPrint_GetStatus($;$); +sub OctoPrint_Define($$); +sub OctoPrint_Undefine($$); + +################################### +sub OctoPrint_Initialize($) { + my ($hash) = @_; + + Log3 $hash, 5, "OctoPrint_Initialize: Entering"; + + $hash->{SetFn} = "OctoPrint_Set"; + $hash->{DefFn} = "OctoPrint_Define"; + $hash->{UndefFn} = "OctoPrint_Undefine"; + + $hash->{AttrList} = "apikey plugin_psucontrol:0,1 " . $readingFnAttributes; + + return; +} + +sub OctoPrint_Define($$) { + my ( $hash, $def ) = @_; + my @a = split( "[ \t][ \t]*", $def ); + my $name = $hash->{NAME}; + + Log3 $name, 0, "OctoPrint $name [OctoPrint_Define] start device"; + + eval { require XML::Simple; }; + return "Please install Perl XML::Simple to use module OctoPrint" + if ($@); + + if ( int(@a) < 3 ) { + my $msg = "Wrong syntax: define OctoPrint [] []"; + Log3 $name, 4, $msg; + return $msg; + } + + $hash->{TYPE} = "OctoPrint"; + + my $address = $a[2]; + $hash->{helper}{ADDRESS} = $address; + + # use port 80 if not defined + my $port = $a[3] || 80; + $hash->{helper}{PORT} = $port; + + # use interval of 45sec if not defined + my $interval = $a[4] || 45; + $hash->{INTERVAL} = $interval; + + $hash->{helper}{CMD_QUEUE} = (); + delete($hash->{helper}{HTTP_CONNECTION}) if(exists($hash->{helper}{HTTP_CONNECTION})); + + # set default settings on first define + if ($init_done) { + $attr{$name}{icon} = 'it_printer'; + } + + # start the status update timer + InternalTimer( gettimeofday() + 2, "OctoPrint_GetStatus", $hash, 1 ); + + return; +} + +sub OctoPrint_Undefine($$) { + my ( $hash, $arg ) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 5, "OctoPrint $name [OctoPrint_Undefine] called function"; + + # Stop the internal GetStatus-Loop and exit + RemoveInternalTimer($hash); + + return; +} + +sub OctoPrint_GetStatus($;$) { + my ( $hash, $update ) = @_; + my $name = $hash->{NAME}; + my $interval = $hash->{INTERVAL}; + + Log3 $name, 5, "OctoPrint $name [OctoPrint_GetStatus] called function"; + if ($update ne '') {Log3 $name, 5, "OctoPrint $name [OctoPrint_GetStatus] Update = $update";} + + #RemoveInternalTimer($hash); + InternalTimer( gettimeofday() + $interval, "OctoPrint_GetStatus", $hash, 0 ); + + return if ( AttrVal( $name, "disable", 0 ) == 1 ); + + if ( !$update ) { + OctoPrint_RefreshData($hash) + } + + return; +} + +sub OctoPrint_RefreshData($){ + my ( $hash ) = @_; + my $name = $hash->{NAME}; + OctoPrint_SendCommand( $hash, "readings_job" ); + OctoPrint_SendCommand( $hash, "readings_printer" ); + OctoPrint_SendCommand( $hash, "getPSUState", "" ) if (AttrVal( $name, "plugin_psucontrol", 0 ) == 1); +} + +sub OctoPrint_Set($@) { + my ( $hash, @a ) = @_; + my $name = $hash->{NAME}; + + shift @a; + my $command = shift @a; + my $parameter = join(' ',@a); + + #2017.07.21 - Log nur schreiben wenn set nicht initialisiert wird + if ($command ne '?') { + Log3 $name, 5, "Octoprint $name [OctoPrint_Set] called function"; + + Log3 $name, 3, "Octoprint $name [OctoPrint_Set] [" . $command . "] set"; + } + + return "No Argument given" if ( !defined( $command ) ); + + my $usage = "Unknown argument " . $command . ", choose one of job:start,cancel printer:connect,disconnect,gohome powermode:shutdown,restart,reboot send_gcode move_axis_z move_axis_y move_axis_x extrude "; + + $usage .= "turnPSUOn:noArg turnPSUOff:noArg togglePSU:noArg " if (AttrVal( $name, "plugin_psucontrol", 0 ) == 1); + + # job informationen + if ( lc( $command ) eq "job" ) { + OctoPrint_SendCommand( $hash, "job" , $parameter ); + } + elsif ( lc( $command ) eq "printer" ) { + OctoPrint_SendCommand( $hash, "printer_" . $parameter ); + } + elsif ( lc( $command ) eq "powermode" ) { + OctoPrint_SendCommand( $hash, "octoprint_" . $parameter ); + } + elsif ( lc( $command ) eq "send_gcode" ) { + OctoPrint_SendCommand( $hash, "send_gcode", $parameter ); + } + elsif ( lc( $command ) eq "move_axis_z" ) { + OctoPrint_SendCommand( $hash, "move_axis_z", $parameter ); + } + elsif ( lc( $command ) eq "move_axis_x" ) { + OctoPrint_SendCommand( $hash, "move_axis_x", $parameter ); + } + elsif ( lc( $command ) eq "move_axis_y" ) { + OctoPrint_SendCommand( $hash, "move_axis_y", $parameter ); + } + elsif ( lc( $command ) eq "extrude" ) { + OctoPrint_SendCommand( $hash, "extrude", $parameter ); + } + elsif ($command eq "turnPSUOn" ) { + OctoPrint_SendCommand( $hash, "turnPSUOn", "" ); + } + elsif ($command eq "turnPSUOff" ) { + OctoPrint_SendCommand( $hash, "turnPSUOff", "" ); + } + elsif ($command eq "togglePSU" ) { + OctoPrint_SendCommand( $hash, "togglePSU", "" ); + } + # return usage hint + else { + return $usage; + } + + return; +} + +sub OctoPrint_ReceiveCommand($$$) { + my ( $param, $err, $data ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $service = $param->{service}; + my $cmd = $param->{cmd}; + my $type = ( $param->{type} ) ? $param->{type} : ""; + my $return; + my $line; + my $UnixDate = time(); + + if (eval {require JSON;1;} ne 1) {Log3 $name, 3, "OctoPrint $name [OctoPrint_ReceiveCommand] missing JSON modul";} + + Log3 $name, 5, "OctoPrint $name [OctoPrint_ReceiveCommand] called function"; + Log3 $name, 5, "OctoPrint $name [OctoPrint_ReceiveCommand] [$service] Data = $data"; + + #my $dJSON; + my $dJSON = eval { JSON->new->utf8(0)->decode($data) }; + #eval { $dJSON = decode_json($data); 1; }; + + Log3 $name, 5, "OctoPrint $name [OctoPrint_ReceiveCommand] [$service] JSON = $dJSON"; + + $hash->{helper}{RUNNING_REQUEST} = 0; + + delete($hash->{helper}{HTTP_CONNECTION}) unless($param->{keepalive}); + + readingsBeginUpdate($hash); + + # Job Informationen + if ($dJSON eq "") { + Log3 $name, 5, "OctoPrint $name [OctoPrint_ReceiveCommand] [$service] JSON = NODATA"; + if ($err) { + readingsBulkUpdate($hash, "online", "false" ) ; + Log3 $name, 5, "OctoPrint $name [OctoPrint_ReceiveCommand] ERROR = $err"; + } + else { + readingsBulkUpdate($hash, "online", "true" ) ; + } + } + elsif ($service eq "readings_job"){ + OctoPrint_expandJSON($hash,$name,"",$dJSON); + } + + elsif ($service eq "readings_printer"){ + OctoPrint_expandJSON($hash,$name,"",$dJSON); + } + + elsif ($service eq "getPSUState"){ + readingsBulkUpdate($hash, "PSUIsOn", $dJSON->{"isPSUOn"} ) ; + #OctoPrint_expandJSON($hash,$name,"",$dJSON); + } + else{ + OctoPrint_RefreshData($hash) + } + + readingsEndUpdate( $hash, 1 ); + + OctoPrint_HD_HandleCmdQueue($hash); + + undef $return; + return; +} + +sub OctoPrint_SendCommand($$;$$) { + my ( $hash, $service, $cmd, $type ) = @_; + my $name = $hash->{NAME}; + my $address = $hash->{helper}{ADDRESS}; + my $port = $hash->{helper}{PORT}; + my $ApiKey = AttrVal( $name, "apikey", "0" ); + my $serviceurl; + my $param; + my $senddata = ""; + my $method = "GET"; + + Log3 $name, 5, "OctoPrint $name [OctoPrint_SendCommand] called function CMD = $cmd "; + + my $http_proto; + if ( $port eq "443" ) { + $http_proto = "https"; + Log3 $name, 5, "OctoPrint $name [OctoPrint_SendCommand] port 443 implies using HTTPS"; + } + elsif ( AttrVal( $name, "https", "0" ) eq "1" ) { + Log3 $name, 5, "OctoPrint $name [OctoPrint_SendCommand] explicit use of HTTPS"; + $http_proto = "https"; + if ( $port eq "80" ) { + $port = "443"; + Log3 $name, 5, + "OctoPrint $name [OctoPrint_SendCommand] implicit change of from port 80 to 443"; + } + } + else { + Log3 $name, 5, "OctoPrint $name [OctoPrint_SendCommand] using unencrypted connection via HTTP"; + $http_proto = "http"; + } + + my $URL; + + # Check Service and change serviceurl + if ($service eq "readings_job") { + $serviceurl = "job?"; + } + elsif ($service eq "extrude") { + $serviceurl = "printer/tool?"; + $senddata = '{"command": "extrude","amount": '.$cmd.'}'; + $method = "POST"; + } + elsif ($service eq "move_axis_z") { + $serviceurl = "printer/printhead?"; + $senddata = '{"command": "jog","z": '.$cmd.'}'; + $method = "POST"; + } + elsif ($service eq "move_axis_x") { + $serviceurl = "printer/printhead?"; + $senddata = '{"command": "jog","x": '.$cmd.'}'; + $method = "POST"; + } + elsif ($service eq "move_axis_y") { + $serviceurl = "printer/printhead?"; + $senddata = '{"command": "jog","y": '.$cmd.'}'; + $method = "POST"; + } + elsif ($service eq "readings_printer") { + $serviceurl = "printer?exclude=state,sd"; + } + elsif ($service eq "job") { + $serviceurl = "job?"; + $senddata = '{"command": "' . $cmd . '"}'; + $method = "POST"; + } + elsif ($service eq "printer_connect") { + $serviceurl = "connection?"; + $senddata = '{"command": "connect"}'; + $method = "POST"; + } + elsif ($service eq "printer_disconnect") { + $serviceurl = "connection?"; + $senddata = '{"command": "disconnect"}'; + $method = "POST"; + } + elsif ($service eq "printer_gohome") { + $serviceurl = "printer/printhead?"; + $senddata = '{"command": "home","axes": ["x","y","z"]}'; + $method = "POST"; + } + elsif ($service eq "octoprint_restart") { + $serviceurl = "system/commands/core/restart?"; + $senddata = '{"action": "restart"}'; + $method = "POST"; + } + elsif ($service eq "octoprint_reboot") { + $serviceurl = "system/commands/core/reboot?"; + $senddata = '{"action": "reboot"}'; + $method = "POST"; + } + elsif ($service eq "octoprint_shutdown") { + $serviceurl = "system/commands/core/shutdown?"; + $senddata = '{"action": "shutdown"}'; + $method = "POST"; + } + elsif ($service eq "send_gcode") { + $serviceurl = "printer/command?"; + $senddata = '{"command": "' . $cmd . '"}'; + $method = "POST"; + } + elsif ($service eq "getPSUState" || $service eq "turnPSUOn" || $service eq "turnPSUOff" || $service eq "togglePSU") { + $serviceurl = "plugin/psucontrol?"; + $senddata = '{"command": "'.$service.'"}'; + $method = "POST"; + } + else{ + $serviceurl = $service; + } + + $URL = $http_proto . "://" . $address . ":" . $port . "/api/" . $serviceurl ."&apikey=" .$ApiKey ; + + #2017.07.19 - Übergabe SendCommandQuery + $param = { + url => $URL, + service => $service, + cmd => $cmd, + type => $type, + data => $senddata, + method => $method, + header => 'Content-Type: application/json', #"User-Agent: None\r\nContent-Type: application/json ;charset=utf-8\r\n", + callback => \&OctoPrint_ReceiveCommand, + }; + + OctoPrint_HD_SendCommand($hash,$param); + + return; +} + +sub OctoPrint_HD_SendCommand($$) { + my ($hash, $param) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 5, "OctoPrint $name [OctoPrint_HD_SendCommand] - append to queue " .$param->{url}; + + # In case any URL changes must be made, this part is separated in this function". + + push @{$hash->{helper}{CMD_QUEUE}}, $param; + + OctoPrint_HD_HandleCmdQueue($hash); +} + +sub OctoPrint_HD_HandleCmdQueue($) { + my ($hash, $param) = @_; + my $name = $hash->{NAME}; + my $http_noshutdown = AttrVal( $name, "http-noshutdown", "0" ); + my $http_timeout = AttrVal( $name, "timeout", "2" ); + + if(not($hash->{helper}{RUNNING_REQUEST}) and @{$hash->{helper}{CMD_QUEUE}}) + { + + my $params = { + url => $param->{url}, + timeout => $http_timeout, + noshutdown => $http_noshutdown, + keepalive => 0, + data => $param->{data}, + hash => $hash, + header => "agent: FHEM/1.0\r\nUser-Agent: FHEM/1.0\r\nContent-Type: application/json", + callback => \&OctoPrint_ReceiveCommand + }; + + my $request = pop @{$hash->{helper}{CMD_QUEUE}}; + + map {$hash->{helper}{HTTP_CONNECTION}{$_} = $params->{$_}} keys %{$params}; + map {$hash->{helper}{HTTP_CONNECTION}{$_} = $request->{$_}} keys %{$request}; + + $hash->{helper}{RUNNING_REQUEST} = 1; + + Log3 $name, 5, "OctoPrint $name [OctoPrint_HD_HandleCmdQueue] - send command url = " .$hash->{helper}{HTTP_CONNECTION}{url}; + Log3 $name, 5, "OctoPrint $name [OctoPrint_HD_HandleCmdQueue] - send command data = " .$hash->{helper}{HTTP_CONNECTION}{data}; + Log3 $name, 5, "OctoPrint $name [OctoPrint_HD_HandleCmdQueue] - send command head = " .$hash->{helper}{HTTP_CONNECTION}{header}; + HttpUtils_NonblockingGet($hash->{helper}{HTTP_CONNECTION}); + } +} + +sub OctoPrint_expandJSON($$$$;$$) { + my ($hash,$dhash,$sPrefix,$ref,$prefix,$suffix) = @_; + my ($name,$type) = ($hash->{NAME},$hash->{TYPE}); + + $prefix = "" if( !$prefix ); + $suffix = "" if( !$suffix ); + $suffix = "_$suffix" if( $suffix ); + + if( ref( $ref ) eq "ARRAY" ) { + while( my ($key,$value) = each @{ $ref } ) { + OctoPrint_expandJSON($hash,$name,"",$value, $prefix.sprintf("%02i",$key+1)."_"); + } + } + + elsif( ref( $ref ) eq "HASH" ) { + while( my ($key,$value) = each %{ $ref } ) { + if( ref( $value ) ) { + OctoPrint_expandJSON($hash,$name,"",$value,$prefix.$key.$suffix."_"); + } + else { + (my $reading = $sPrefix.$prefix.$key.$suffix) =~ s/[^A-Za-z\d_\.\-\/]/_/g; + + #my $unicode = decode('ISO-8859-1',$value); + #readingsBulkUpdate($hash, $reading, encode('UTF-8', $unicode) ) if($value ne ""); + readingsBulkUpdate($hash, $reading, encode('UTF-8', $value) ) if($value ne ""); + #readingsBulkUpdate($hash, $reading, $value ) if($value ne ""); + } + } + } +} + +################################### + +1; + +=pod +=item device +=item summary control for OctoPrint +=item summary_DE Steuerung von OctoPrint +=begin html + +

+ +

+

+ OctoPrint +

+ +=end html + +=begin html_DE + +

+ +

+

+ OctoPrint +

+ +=end html_DE + +=cut \ No newline at end of file