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