74_UnifiSwitch.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. ##############################################################################
  2. # $Id: 74_UnifiSwitch.pm 17015 2018-07-22 15:16:09Z wuehler $
  3. # 74_UnifiSwitch.pm
  4. # CHANGED
  5. ##############################################################################
  6. # V 0.1
  7. # - feature: 74_UnifiSwitch: initial version
  8. # V 0.11
  9. # - feature: 74_UnifiSwitch: state des USW wird gesetzt
  10. # Port-Nummern < 9 mit führender 0
  11. # Model als Internal
  12. # port-state als reading hinzugefügt
  13. # V 0.90
  14. # - feature: 74_UnififSwitch: setter for poe-Mode
  15. # added commandref
  16. # V 0.91
  17. # - fixed: 74_UnififSwitch: fixed wording in commandref
  18. # added new state-mappings
  19. # V 0.92
  20. # - fixed: 74_UnififSwitch: fixed possible log-error in eq in line 135
  21. #
  22. # TODOs:
  23. # - state des USW für weiter state-Numbers korrekt in Worte übersetzen
  24. package main;
  25. my $version="0.92";
  26. # Laden evtl. abhängiger Perl- bzw. FHEM-Module
  27. use strict;
  28. use warnings;
  29. use POSIX;
  30. use JSON qw(decode_json);
  31. ### Forward declarations ####################################################{
  32. sub UnifiSwitch_Initialize($$);
  33. sub UnifiSwitch_Define($$);
  34. sub UnifiSwitch_Undef($$);
  35. sub UnifiSwitch_Attr(@);
  36. sub UnifiSwitch_Set($@);
  37. sub UnifiSwitch_Get($@);
  38. sub UnifiSwitch_Parse($$);
  39. sub UnifiSwitch_Whoami();
  40. sub UnifiSwitch_Whowasi();
  41. sub UnifiSwitch_Initialize($$) {
  42. my ($hash) = @_;
  43. $hash->{DefFn} = "UnifiSwitch_Define";
  44. $hash->{UndefFn} = "UnifiSwitch_Undef";
  45. $hash->{ParseFn} = "UnifiSwitch_Parse";
  46. $hash->{AttrFn} = "UnifiSwitch_Attr";
  47. $hash->{SetFn} = "UnifiSwitch_Set";
  48. $hash->{GetFn} = "UnifiSwitch_Get";
  49. $hash->{AttrList} = $readingFnAttributes;
  50. # TODO: notwendig?
  51. $hash->{Match} = "^UnifiSwitch";
  52. # TODO ATTR wird nicht übernommen
  53. $hash->{AutoCreate}={"UnifiSwitch.*" => { ATTR => "event-on-change-reading:.* event-min-interval:.*:300",
  54. FILTER => "%NAME",
  55. autocreateThreshold => "1:1"} };
  56. }
  57. sub UnifiSwitch_Define($$) {
  58. my ( $hash, $def) = @_;
  59. my @a = split("[ \t][ \t]*", $def);
  60. my $name = $a[0];
  61. Log3 $name, 3, "UnifiSwitch_Define - executed. 0 ";
  62. # zweites Argument ist die eindeutige Geräteadresse
  63. my $address = $a[2];
  64. if(defined($modules{UnifiSwitch}{defptr}{$address})){
  65. return "Switch with name $address already defined in ".($modules{UnifiSwitch}{defptr}{$address}{NAME});
  66. }
  67. $hash->{CODE}=$address;
  68. $hash->{VERSION}=$version;
  69. # Adresse rückwärts dem Hash zuordnen (für ParseFn)
  70. $modules{UnifiSwitch}{defptr}{$address} = $hash;
  71. Log3 $name, 3, "UnifiSwitch_Define - Adress: ".$address;
  72. AssignIoPort($hash);
  73. }
  74. sub UnifiSwitch_Undef($$){
  75. my ($hash, $name) = @_;
  76. Log3 $name, 3, "$name (UnifiSwitch_Undef) - executed.".$hash->{CODE};
  77. if(defined($hash->{CODE}) && defined($modules{UnifiSwitch}{defptr}{$hash->{CODE}})){
  78. delete($modules{UnifiSwitch}{defptr}{$hash->{CODE}});
  79. }
  80. return undef;
  81. }
  82. sub UnifiSwitch_Attr(@){
  83. my @a = @_;
  84. return undef;
  85. }
  86. sub UnifiSwitch_Set($@){
  87. my ($hash,@a) = @_;
  88. my ($name,$setName,$setVal,$setVal2) = @a;
  89. Log3 $name, 5, "$name: set called with $setName " . ($setVal ? $setVal : "")." ". ($setVal2 ? $setVal2 : "") if ($setName ne "?");
  90. # TODO: ggf. auf disabled des Unifi-devices prüfen?! Wie bekomme ich den io_hash?
  91. #if(Unifi_CONNECTED($hash) eq 'disabled' && $setName !~ /clear/) {
  92. # return "Unknown argument $setName, choose one of clear:all,readings";
  93. # Log3 $name, 5, "$name: set called with $setName but device is disabled!" if($setName ne "?");
  94. # return undef;
  95. #}
  96. if($setName !~ /clear|poeMode/) {
  97. return "Unknown argument $setName, choose one of "
  98. ."clear:readings poeMode "; #TODO: PortNamen sowie die Modes als Auswahl anhängen
  99. } else {
  100. Log3 $name, 4, "$name: set $setName";
  101. if ($setName eq 'poeMode') {
  102. return "usage: $setName <port> <off|auto|passive|passthrough|restart>" if( !$setVal );
  103. my $apRef;
  104. my $ap = $hash->{usw};
  105. return "switch has no poe-ports!" if( !$ap->{port_table} );
  106. $apRef = $ap;
  107. if( $setVal !~ m/\d+/ ) {
  108. for my $port (@{$apRef->{port_table}}) {
  109. next if( $port->{name} !~ $setVal );
  110. $setVal = $port->{port_idx};
  111. last;
  112. }
  113. }
  114. return "port-ID musst be numeric" if( $setVal !~ m/\d+/ );
  115. return "port musst be in [1..". scalar @{$apRef->{port_table}} ."] " if( $setVal < 1 || $setVal > scalar @{$apRef->{port_table}} );
  116. return "switch '$apRef->{name}' has no port $setVal" if( !defined(@{$apRef->{port_table}}[$setVal-1] ) );
  117. return "port $setVal of switch '$apRef->{name}' is not poe capable" if( !@{$apRef->{port_table}}[$setVal-1]->{port_poe} );
  118. my $port_overrides = $apRef->{port_overrides};
  119. my $idx;
  120. my $i = 0;
  121. for my $entry (@{$port_overrides}) {
  122. if( defined $entry->{port_idx} && ($entry->{port_idx} eq $setVal) ) {
  123. $idx = $i;
  124. last;
  125. }
  126. ++$i;
  127. }
  128. if( !defined($idx) ) {
  129. push @{$port_overrides}, {port_idx => $setVal+0};
  130. $idx = scalar @{$port_overrides};
  131. }
  132. if( $setVal2 eq 'off' ) {
  133. $port_overrides->[$idx]{poe_mode} = "off";
  134. IOWrite($hash, "Unifi_RestJson_Send", $apRef->{device_id}, $port_overrides);
  135. } elsif( $setVal2 eq 'auto' || $setVal2 eq 'poe+' ) {
  136. #return "port $setVal not auto poe capable" if( @{$apRef->{port_table}}[$setVal-1]->{poe_caps} & 0x03 ) ;
  137. $port_overrides->[$idx]{poe_mode} = "auto";
  138. IOWrite($hash, "Unifi_RestJson_Send", $apRef->{device_id}, $port_overrides );
  139. } elsif( $setVal2 eq 'passive' ) {
  140. #return "port $setVal not passive poe capable" if( @{$apRef->{port_table}}[$setVal-1]->{poe_caps} & 0x04 ) ;
  141. $port_overrides->[$idx]{poe_mode} = "pasv24";
  142. IOWrite($hash, "Unifi_RestJson_Send", $apRef->{device_id}, $port_overrides);
  143. } elsif( $setVal2 eq 'passthrough' ) {
  144. #return "port $setVal not passthrough poe capable" if( @{$apRef->{port_table}}[$setVal-1]->{poe_caps} & 0x08 ) ;
  145. $port_overrides->[$idx]{poe_mode} = "passthrough";
  146. IOWrite($hash, "Unifi_RestJson_Send", $apRef->{device_id}, $port_overrides);
  147. } elsif( $setVal2 eq 'restart' ) {#TODO: Was wir hier gemacht? Funktioniert das noch?
  148. IOWrite($hash, "Unifi_RestJson_Send", {cmd => 'power-cycle', mac => $apRef->{mac}, port_idx => $setVal+0});
  149. } else {
  150. return "unknwon poe mode $setVal2";
  151. }
  152. }elsif ($setName eq 'clear') {
  153. if ($setVal eq 'readings' || $setVal eq 'all') {
  154. for (keys %{$hash->{READINGS}}) {
  155. delete $hash->{READINGS}->{$_} if($_ ne 'state');
  156. }
  157. }
  158. }
  159. }
  160. return undef;
  161. }
  162. sub UnifiSwitch_Get($@){
  163. my @a = @_;
  164. return undef;
  165. }
  166. sub UnifiSwitch_Parse($$) {
  167. my ($io_hash, $message) = @_;
  168. my ($name,$self) = ($io_hash->{NAME},UnifiSwitch_Whoami());
  169. my $i1=index($message,"_")+1;
  170. my $i2=index($message,"{")-$i1;
  171. my $address = substr($message, $i1, $i2);
  172. Log3 $name, 5, "$name ($self) - executed. UnifiSwitch: Adress: ".$address;
  173. my $message_json=substr($message,$i1+$i2);
  174. Log3 $name, 5, "$name ($self) - executed. UnifiSwitch: message_json: ".$message_json;
  175. # wenn bereits eine Gerätedefinition existiert (via Definition Pointer aus Define-Funktion)
  176. if(my $hash = $modules{UnifiSwitch}{defptr}{$address}){
  177. # Nachricht für $hash verarbeiten
  178. my $apRef = decode_json($message_json);
  179. $hash->{usw} = $apRef;
  180. if( $apRef->{type} eq 'usw' ){
  181. if ($apRef->{state} eq "1"){
  182. $hash->{STATE} = "connected";
  183. }elsif($apRef->{state} eq "2"){
  184. $hash->{STATE} = "managed by other";
  185. }elsif($apRef->{state} eq "4"){
  186. $hash->{STATE} = "upgrading";
  187. }elsif($apRef->{state} eq "5"){
  188. $hash->{STATE} = "provisioning";
  189. }else{
  190. $hash->{STATE} = "unknown: ".$apRef->{state}; # TODO: Weitere states setzen wenn state-id bekannt
  191. }
  192. $hash->{MODEL}=$apRef->{model};
  193. readingsBeginUpdate($hash);
  194. if( $apRef->{port_table} ){
  195. for my $port (@{$apRef->{port_table}}) {
  196. my $port_id=$port->{port_idx} > 9 ? "port_".$port->{port_idx} : "port_0".$port->{port_idx};
  197. readingsBulkUpdate($hash,$port_id."_name",$port->{name});
  198. if(defined $port->{speed} && looks_like_number($port->{speed})){
  199. readingsBulkUpdate($hash,$port_id."_state",$port->{speed} > 0 ? $port->{speed}." Mbps" : "disconnected");
  200. }else{
  201. readingsBulkUpdate($hash,$port_id."_state","unknown");
  202. }
  203. if( $port->{port_poe} ){
  204. readingsBulkUpdate($hash,$port_id."_poe_mode",$port->{poe_mode});
  205. readingsBulkUpdate($hash,$port_id."_poe_power",$port->{poe_power});
  206. readingsBulkUpdate($hash,$port_id."_poe_voltage",$port->{poe_voltage});
  207. readingsBulkUpdate($hash,$port_id."_poe_current",$port->{poe_current});
  208. }
  209. }
  210. }
  211. readingsEndUpdate($hash,1);
  212. }
  213. Log3 $name, 5, "$name ($self) - return: ".$hash->{NAME};
  214. return $hash->{NAME}; # Rückgabe des Gerätenamens, für welches die Nachricht bestimmt ist.
  215. }
  216. else{
  217. # Keine Gerätedefinition verfügbar
  218. # Daher Vorschlag define-Befehl: <NAME> <MODULNAME> <ADDRESSE>
  219. Log3 $name, 3, "$name ($self) - return: UNDEFINED UnifiSwitch_".$address." UnifiSwitch $address";
  220. return "UNDEFINED ".$address." UnifiSwitch $address";
  221. }
  222. }
  223. ###############################################################################
  224. sub UnifiSwitch_Whoami() { return (split('::',(caller(1))[3]))[1] || ''; }
  225. sub UnifiSwitch_Whowasi() { return (split('::',(caller(2))[3]))[1] || ''; }
  226. # Eval-Rückgabewert für erfolgreiches
  227. # Laden des Moduls
  228. 1;
  229. # Beginn der Commandref
  230. =pod
  231. =item device
  232. =item summary Show info and control UnifiSwitch (USW) (Unifi-Device required)
  233. =item summary_DE Zeigt Infos zum UnifiSwitch (USW) an und steuert diesen.
  234. =begin html
  235. <a name="UnifiSwitch"></a>
  236. <h3>UnifiSwitch</h3>
  237. <ul>
  238. UnifiSwitch is the FHEM module for the Ubiquiti Networks (UBNT) Switch - USW.<br>
  239. <br>
  240. You can use the readings or set features to control your unifi-switch.
  241. <br>
  242. <h4>Prerequisites</h4>
  243. <ul>
  244. <li>
  245. A connected Unifi-Device as IODev.
  246. </li>
  247. <li>
  248. The Perl module JSON is required. <br>
  249. On Debian/Raspbian: <code>apt-get install libjson-perl </code><br>
  250. Via CPAN: <code>cpan install JSON</code>
  251. </li>
  252. </ul>
  253. <h4>Define</h4>
  254. <ul>
  255. <code>define &lt;name&gt; UnifiSwitch &lt;ip&gt; &lt;nameOfSwitch&gt;</code>
  256. <br>Normaly this device will be autocreated!<br>
  257. <br>
  258. &lt;name&gt;:
  259. <ul>
  260. <code>The FHEM device name for the device.</code><br>
  261. </ul>
  262. &lt;nameOfSwitch&gt;:
  263. <ul>
  264. <code>The name of the switch in unifi-controller.</code><br>
  265. </ul>
  266. </ul>
  267. <h4>Set</h4>
  268. <ul>
  269. <li><code>set &lt;name&gt; clear &lt;readings|all&gt;</code><br>
  270. Clears the readings or all. </li>
  271. <br>
  272. <li><code>set &lt;name&gt; poeMode &lt;port&gt; &lt;off|auto|passive|passthrough|restart&gt;</code><br>
  273. Set PoE mode for &lt;port&gt;. </li>
  274. </ul>
  275. <h4>Readings</h4>
  276. <ul>
  277. Note: All readings generate events. You can control this with <a href="#readingFnAttributes">these global attributes</a>.
  278. <li>Each port has the readings name and state. POE-ports have more readings.</li>
  279. <li>name
  280. <ul>The name of the port as defined in UnifiController.</ul>
  281. </li>
  282. <li>state
  283. <ul>The connection state of the port. Can be disconnected or in Mbps/Gbps.</ul>
  284. </li>
  285. <li>poe_current
  286. <ul>The current of the port.</ul>
  287. </li>
  288. <li>poe_mode
  289. <ul>The poe-mode of the port.</ul>
  290. </li>
  291. <li>poe_power
  292. <ul>The power of the port.</ul>
  293. </li>
  294. <li>poe_voltage
  295. <ul>The voltage of the port.</ul>
  296. </li>
  297. </ul>
  298. <br>
  299. </ul>
  300. =end html
  301. # Ende der Commandref
  302. =cut