76_SMAInverter.pm 82 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835
  1. #################################################################################################################
  2. # $Id: 76_SMAInverter.pm 16934 2018-07-02 20:11:29Z DS_Starter $
  3. #################################################################################################################
  4. #
  5. #
  6. # Copyright notice
  7. #
  8. # Published according Creative Commons : Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)
  9. # Details: https://creativecommons.org/licenses/by-nc-sa/3.0/
  10. #
  11. # Credits:
  12. # - based on 77_SMASTP.pm by Volker Kettenbach with following credits:
  13. # - based on an Idea by SpenZerX and HDO
  14. # - Waldmensch for various improvements
  15. # - sbfspot (https://sbfspot.codeplex.com/)
  16. # - rewritten by Thomas Schoedl (sct14675) with inputs from Volker, waldmensch and DS_Starter
  17. #
  18. # Description:
  19. # This is an FHEM-Module for SMA Inverters.
  20. # Tested on Sunny Tripower 6000TL-20 and Sunny Island 4.4
  21. #
  22. # Requirements:
  23. # This module requires:
  24. # - Perl Module: IO::Socket::INET
  25. # - Perl Module: DateTime
  26. #
  27. #
  28. #################################################################################################################
  29. # Versions History by DS_Starter
  30. #
  31. # 2.10.0 29.06.2018 Internal MODEL added
  32. # 2.9.2 08.10.2017 adapted to use extended abortArg (Forum:77472)
  33. # 2.9.1 24.04.2017 fix for issue #24 (Wrong INV_TYPE for STP10000TL-20) and fix for issue #25 (unpack out of range for SB1.5-1VL-40)
  34. # 2.9.0 23.04.2017 fixed issue #22: wrong logon command for SunnyBoy systems
  35. # 2.8.3 19.04.2017 enhanced inverter Type-Hash
  36. # 2.8.2 23.03.2017 changed SMA_logon sub
  37. # 2.8.1 06.12.2016 SMAInverter version as internal
  38. # 2.8 05.12.2016 changed commandsections to make sure getting only data from inverters with preset
  39. # $inv_susyid and $inv_serial
  40. # 2.7.4 04.12.2016 change loading of IO::Socket::INET, DateTime
  41. # 2.7.3 04.12.2016 commandref adapted
  42. # 2.7.2 03.12.2016 use Time::HiRes qw(gettimeofday tv_interval)
  43. # 2.7.1 02.12.2016 showproctime improved
  44. # 2.7 02.12.2016 showproctime added
  45. # 2.6.1 29.11.2016 getstatus_DoParse changed due to inititialized issues
  46. # 2.6 28.11.2016 bugfix warnings ParseDone redefine at startup, uninitialized value $avg if FHEM was
  47. # restarted in sleeptime, switched avg_energy to avg_power, commandref updated
  48. # 2.5.2 27.11.2016 bugfix average calc, bugfix warnings at startup
  49. # 2.5.1 26.11.2016 calc of averagebuf changed to 5, 10, 15 minutes
  50. # 2.5 26.11.2016 averagebuf changed, Attr timeout added
  51. # 2.4 26.11.2016 create ringbuffer for calculating average energy last 5, 10, 15 cycles
  52. # 2.3 25.11.2016 bugfixing
  53. # 2.2 24.11.2016 further optimize of non-blocking operation
  54. # 2.1 24.11.2016 avg_energy_lastcycles added
  55. # 2.0 24.11.2016 switched module to non-blocking operation
  56. # 1.8.4 23.11.2016 prepare non-blocking operation
  57. # 1.8.3 23.11.2016 readings opertime_start, opertime_stop
  58. # 1.8.2 22.11.2016 eliminate global vars, prepare non-blocking operation
  59. # 1.8.1 22.11.2016 eliminate global vars, create command array
  60. # 1.8 21.11.2016 eliminate $r_OK, $r_FAIL, create command-array
  61. # 1.7 21.11.2016 devtypes completed, minor bugfixes, commandref completed
  62. # 1.6.1 19.11.2016 bugfix perl warning during fhem start
  63. # 1.6 09.11.2016 added operation control by sunrise,sunset, Attr offset, suppressSleep added
  64. # 1.5 08.11.2016 added device classes hash
  65. # 1.4 07.11.2016 compatibility to SBFSpot improved, bilingual dependend on attr "language" of global-device,
  66. # added hash of SMA device types
  67. # 1.3 07.11.2016 Attr SBFSpotComp added to get compatibility mode with SBFSpot
  68. # 1.2 06.11.2016 function get data added, log output level changed to 4 in sub SMAInverter_Attr,
  69. # some code changes
  70. # 1.1 06.11.2016 Attr mode manual, automatic added
  71. # 1.0 06.11.2016 Attr disable added,
  72. # $globalName replaced by $name in all expressions (due to module redesign to non-blocking later)
  73. package main;
  74. use strict;
  75. use warnings;
  76. eval "use IO::Socket::INET;1" or my $MissModulSocket = "IO::Socket::INET";
  77. eval "use DateTime;1" or my $MissModulDateTime = "DateTime";
  78. use Time::HiRes qw(gettimeofday tv_interval);
  79. use Blocking;
  80. use Time::Local;
  81. my $SMAInverterVersion = "2.10.0";
  82. # Inverter Data fields and supported commands flags.
  83. # $inv_SPOT_ETODAY # Today yield
  84. # $inv_SPOT_ETOTAL # Total yield
  85. # $inv_SPOT_PDC1 # DC power input 1
  86. # $inv_SPOT_PDC2 # DC power input 2
  87. # $inv_SPOT_PAC1 # Power L1
  88. # $inv_SPOT_PAC2 # Power L2
  89. # $inv_SPOT_PAC3 # Power L3
  90. # $inv_PACMAX1 # Nominal power in Ok Mode
  91. # $inv_PACMAX2 # Nominal power in Warning Mode
  92. # $inv_PACMAX3 # Nominal power in Fault Mode
  93. # $inv_PACMAX1_2 # Maximum active power device (Some inverters like SB3300/SB1200)
  94. # $inv_SPOT_PACTOT # Total Power
  95. # $inv_ChargeStatus # Battery Charge status
  96. # $inv_SPOT_UDC1 # DC voltage input
  97. # $inv_SPOT_UDC2 # DC voltage input
  98. # $inv_SPOT_IDC1 # DC current input
  99. # $inv_SPOT_IDC2 # DC current input
  100. # $inv_SPOT_UAC1 # Grid voltage phase L1
  101. # $inv_SPOT_UAC2 # Grid voltage phase L2
  102. # $inv_SPOT_UAC3 # Grid voltage phase L3
  103. # $inv_SPOT_IAC1 # Grid current phase L1
  104. # $inv_SPOT_IAC2 # Grid current phase L2
  105. # $inv_SPOT_IAC3 # Grid current phase L3
  106. # $inv_BAT_UDC # Battery Voltage
  107. # $inv_BAT_IDC # Battery Current
  108. # $inv_BAT_CYCLES # Battery recharge cycles
  109. # $inv_BAT_TEMP # Battery temperature
  110. # $inv_SPOT_FREQ # Grid Frequency
  111. # $inv_CLASS # Inverter Class
  112. # $inv_TYPE # Inverter Type
  113. # $inv_SPOT_OPERTM # Operation Time
  114. # $inv_SPOT_FEEDTM # Feed-in time
  115. # $inv_TEMP # Inverter temperature
  116. # $inv_GRIDRELAY # Grid Relay/Contactor Status
  117. # $inv_STATUS # Inverter Status
  118. # Aufbau Wechselrichter Type-Hash
  119. my %SMAInverter_devtypes = (
  120. 0000 => "Unknown Inverter Type",
  121. 9015 => "SB 700",
  122. 9016 => "SB 700U",
  123. 9017 => "SB 1100",
  124. 9018 => "SB 1100U",
  125. 9019 => "SB 1100LV",
  126. 9020 => "SB 1700",
  127. 9021 => "SB 1900TLJ",
  128. 9022 => "SB 2100TL",
  129. 9023 => "SB 2500",
  130. 9024 => "SB 2800",
  131. 9025 => "SB 2800i",
  132. 9026 => "SB 3000",
  133. 9027 => "SB 3000US",
  134. 9028 => "SB 3300",
  135. 9029 => "SB 3300U",
  136. 9030 => "SB 3300TL",
  137. 9031 => "SB 3300TL HC",
  138. 9032 => "SB 3800",
  139. 9033 => "SB 3800U",
  140. 9034 => "SB 4000US",
  141. 9035 => "SB 4200TL",
  142. 9036 => "SB 4200TL HC",
  143. 9037 => "SB 5000TL",
  144. 9038 => "SB 5000TLW",
  145. 9039 => "SB 5000TL HC",
  146. 9066 => "SB 1200",
  147. 9067 => "STP 10000TL-10",
  148. 9068 => "STP 12000TL-10",
  149. 9069 => "STP 15000TL-10",
  150. 9070 => "STP 17000TL-10",
  151. 9084 => "WB 3600TL-20",
  152. 9085 => "WB 5000TL-20",
  153. 9086 => "SB 3800US-10",
  154. 9098 => "STP 5000TL-20",
  155. 9099 => "STP 6000TL-20",
  156. 9100 => "STP 7000TL-20",
  157. 9101 => "STP 8000TL-10",
  158. 9102 => "STP 9000TL-20",
  159. 9103 => "STP 8000TL-20",
  160. 9104 => "SB 3000TL-JP-21",
  161. 9105 => "SB 3500TL-JP-21",
  162. 9106 => "SB 4000TL-JP-21",
  163. 9107 => "SB 4500TL-JP-21",
  164. 9108 => "SCSMC",
  165. 9109 => "SB 1600TL-10",
  166. 9131 => "STP 20000TL-10",
  167. 9139 => "STP 20000TLHE-10",
  168. 9140 => "STP 15000TLHE-10",
  169. 9157 => "Sunny Island 2012",
  170. 9158 => "Sunny Island 2224",
  171. 9159 => "Sunny Island 5048",
  172. 9160 => "SB 3600TL-20",
  173. 9168 => "SC630HE-11",
  174. 9169 => "SC500HE-11",
  175. 9170 => "SC400HE-11",
  176. 9171 => "WB 3000TL-21",
  177. 9172 => "WB 3600TL-21",
  178. 9173 => "WB 4000TL-21",
  179. 9174 => "WB 5000TL-21",
  180. 9175 => "SC 250",
  181. 9176 => "SMA Meteo Station",
  182. 9177 => "SB 240-10",
  183. 9171 => "WB 3000TL-21",
  184. 9172 => "WB 3600TL-21",
  185. 9173 => "WB 4000TL-21",
  186. 9174 => "WB 5000TL-21",
  187. 9179 => "Multigate-10",
  188. 9180 => "Multigate-US-10",
  189. 9181 => "STP 20000TLEE-10",
  190. 9182 => "STP 15000TLEE-10",
  191. 9183 => "SB 2000TLST-21",
  192. 9184 => "SB 2500TLST-21",
  193. 9185 => "SB 3000TLST-21",
  194. 9186 => "WB 2000TLST-21",
  195. 9187 => "WB 2500TLST-21",
  196. 9188 => "WB 3000TLST-21",
  197. 9189 => "WTP 5000TL-20",
  198. 9190 => "WTP 6000TL-20",
  199. 9191 => "WTP 7000TL-20",
  200. 9192 => "WTP 8000TL-20",
  201. 9193 => "WTP 9000TL-20",
  202. 9254 => "Sunny Island 3324",
  203. 9255 => "Sunny Island 4.0M",
  204. 9256 => "Sunny Island 4248",
  205. 9257 => "Sunny Island 4248U",
  206. 9258 => "Sunny Island 4500",
  207. 9259 => "Sunny Island 4548U",
  208. 9260 => "Sunny Island 5.4M",
  209. 9261 => "Sunny Island 5048U",
  210. 9262 => "Sunny Island 6048U",
  211. 9278 => "Sunny Island 3.0M",
  212. 9279 => "Sunny Island 4.4M",
  213. 9281 => "STP 10000TL-20",
  214. 9282 => "STP 11000TL-20",
  215. 9283 => "STP 12000TL-20",
  216. 9284 => "STP 20000TL-30",
  217. 9285 => "STP 25000TL-30",
  218. 9301 => "SB1.5-1VL-40",
  219. 9302 => "SB2.5-1VL-40",
  220. 9303 => "SB2.0-1VL-40",
  221. 9304 => "SB5.0-1SP-US-40",
  222. 9305 => "SB6.0-1SP-US-40",
  223. 9306 => "SB8.0-1SP-US-40",
  224. 9307 => "Energy Meter",
  225. );
  226. # Wechselrichter Class-Hash DE
  227. my %SMAInverter_classesDE = (
  228. 8000 => "Alle Geräte",
  229. 8001 => "Solar-Wechselrichter",
  230. 8002 => "Wind-Wechselrichter",
  231. 8007 => "Batterie-Wechselrichter",
  232. 8033 => "Verbraucher",
  233. 8064 => "Sensorik allgemein",
  234. 8065 => "Stromzähler",
  235. 8128 => "Kommunikationsprodukte",
  236. );
  237. # Wechselrichter Class-Hash EN
  238. my %SMAInverter_classesEN = (
  239. 8000 => "All Devices",
  240. 8001 => "Solar Inverters",
  241. 8002 => "Wind Turbine Inverter",
  242. 8007 => "Batterie Inverters",
  243. 8033 => "Consumer",
  244. 8064 => "Sensor System in General",
  245. 8065 => "Electricity meter",
  246. 8128 => "Communication products",
  247. );
  248. ###############################################################
  249. # SMAInverter Initialize
  250. ###############################################################
  251. sub SMAInverter_Initialize($) {
  252. my ($hash) = @_;
  253. $hash->{DefFn} = "SMAInverter_Define";
  254. $hash->{UndefFn} = "SMAInverter_Undef";
  255. $hash->{GetFn} = "SMAInverter_Get";
  256. $hash->{AttrList} = "interval " .
  257. "detail-level:0,1,2 " .
  258. "disable:1,0 " .
  259. "mode:manual,automatic ".
  260. "offset ".
  261. "suppressSleep:1,0 ".
  262. "SBFSpotComp:1,0 " .
  263. "showproctime:1,0 ".
  264. "timeout " .
  265. "target-susyid " .
  266. "target-serial " .
  267. $readingFnAttributes;
  268. $hash->{AttrFn} = "SMAInverter_Attr";
  269. }
  270. ###############################################################
  271. # SMAInverter Define
  272. ###############################################################
  273. sub SMAInverter_Define($$) {
  274. my ($hash, $def) = @_;
  275. my @a = split("[ \t][ \t]*", $def);
  276. return "Error: Perl module ".$MissModulSocket." is missing.
  277. Install it on Debian with: sudo apt-get install libio-socket-multicast-perl" if($MissModulSocket);
  278. return "Error: Perl module ".$MissModulDateTime." is missing.
  279. Install it on Debian with: sudo apt-get install libdatetime-perl" if($MissModulDateTime);
  280. return "Wrong syntax: use define <name> SMAInverter <inv-userpwd> <inv-hostname/inv-ip > " if ((int(@a) < 4) and (int(@a) > 5));
  281. my $name = $hash->{NAME};
  282. $hash->{LASTUPDATE} = 0;
  283. $hash->{INTERVAL} = $hash->{HELPER}{INTERVAL} = AttrVal($name, "interval", 60);
  284. $hash->{VERSION} = $SMAInverterVersion;
  285. $hash->{HELPER}{FAULTEDCYCLES} = 0;
  286. delete($hash->{HELPER}{AVERAGEBUF}) if($hash->{HELPER}{AVERAGEBUF});
  287. # protocol related defaults
  288. $hash->{HELPER}{MYSUSYID} = 233; # random number, has to be different from any device in local network
  289. $hash->{HELPER}{MYSERIALNUMBER} = 123321123; # random number, has to be different from any device in local network
  290. $hash->{HELPER}{DEFAULT_TARGET_SUSYID} = 0xFFFF; # 0xFFFF is any susyid
  291. $hash->{HELPER}{DEFAULT_TARGET_SERIAL} = 0xFFFFFFFF; # 0xFFFFFFFF is any serialnumber
  292. $hash->{HELPER}{PKT_ID} = 0x8001; # Packet ID
  293. $hash->{HELPER}{MAXBYTES} = 300; # constant MAXBYTES scalar 300
  294. my ($IP,$Host,$Caps);
  295. my $Pass = $a[2]; # to do: check 1-12 Chars
  296. # extract IP or Hostname from $a[3]
  297. if (!defined $Host) {
  298. if ( $a[3] =~ /^([A-Za-z0-9_.])/ ) {
  299. $Host = $a[3];
  300. }
  301. }
  302. if (!defined $Host) {
  303. return "Argument:{$a[3]} not accepted as Host or IP. Read device specific help file.";
  304. }
  305. $hash->{PASS} = $Pass;
  306. $hash->{HOST} = $Host;
  307. InternalTimer(gettimeofday()+5, "SMAInverter_GetData", $hash, 0); # Start Hauptroutine
  308. return undef;
  309. }
  310. ###############################################################
  311. # SMAInverter Undefine
  312. ###############################################################
  313. sub SMAInverter_Undef($$) {
  314. my ($hash, $name) = @_;
  315. RemoveInternalTimer($hash);
  316. BlockingKill($hash->{HELPER}{RUNNING_PID});
  317. return undef;
  318. }
  319. ###############################################################
  320. # SMAInverter Get
  321. ###############################################################
  322. sub SMAInverter_Get($$) {
  323. my ($hash, @a) = @_;
  324. return "\"get X\" needs at least an argument" if ( @a < 2 );
  325. my $name = shift @a;
  326. my $opt = shift @a;
  327. my $timeout = AttrVal($name, "timeout", 60);
  328. my $getlist = "Unknown argument $opt, choose one of ".
  329. "data:noArg ";
  330. return "module is disabled" if(IsDisabled($name));
  331. if ($opt eq "data") {
  332. SMAInverter_GetData($hash);
  333. } else {
  334. return "$getlist";
  335. }
  336. return undef;
  337. }
  338. ###############################################################
  339. # SMAInverter Attr
  340. ###############################################################
  341. sub SMAInverter_Attr(@) {
  342. my ($cmd,$name,$aName,$aVal) = @_;
  343. # $cmd can be "del" or "set"
  344. # $name is device name
  345. # aName and aVal are Attribute name and value
  346. my $hash = $defs{$name};
  347. my $do;
  348. if ($aName eq "mode") {
  349. if ($cmd eq "set" && $aVal eq "manual") {
  350. $hash->{INTERVAL} = $aVal;
  351. } else {
  352. $hash->{INTERVAL} = $hash->{HELPER}{INTERVAL};
  353. }
  354. InternalTimer(time+5, 'SMAInverter_GetData', $hash, 0);
  355. }
  356. if ($aName eq "disable") {
  357. if($cmd eq "set") {
  358. $do = ($aVal) ? 1 : 0;
  359. }
  360. $do = 0 if($cmd eq "del");
  361. my $val = ($do == 1 ? "disabled" : "initialized");
  362. readingsSingleUpdate($hash, "state", $val, 1);
  363. if ($do == 0) {
  364. my $mode = AttrVal($name, "mode", "automatic");
  365. RemoveInternalTimer($hash);
  366. InternalTimer(time+5, 'SMAInverter_GetData', $hash, 0);
  367. } else {
  368. RemoveInternalTimer($hash);
  369. }
  370. }
  371. if ($aName eq "detail-level") {
  372. delete $defs{$name}{READINGS};
  373. }
  374. if ($aName eq "SBFSpotComp") {
  375. delete $defs{$name}{READINGS};
  376. }
  377. if ($aName eq "interval") {
  378. if ($cmd eq "set") {
  379. $hash->{HELPER}{INTERVAL} = $aVal;
  380. $hash->{INTERVAL} = $aVal if(AttrVal($name, "mode", "") ne "manual");
  381. delete($hash->{HELPER}{AVERAGEBUF}) if($hash->{HELPER}{AVERAGEBUF});
  382. Log3 $name, 3, "$name - Set $aName to $aVal";
  383. } else {
  384. $hash->{INTERVAL} = $hash->{HELPER}{INTERVAL} = 60;
  385. }
  386. }
  387. if ($cmd eq "set" && $aName eq "offset") {
  388. if($aVal !~ /^\d+$/ || $aVal < 0 || $aVal > 7200) { return "The Value of $aName is not valid. Use value between 0 ... 7200 !";}
  389. }
  390. if ($cmd eq "set" && $aName eq "timeout") {
  391. unless ($aVal =~ /^[0-9]+$/) { return " The Value for $aName is not valid. Use only figures 1-9 !";}
  392. }
  393. return;
  394. }
  395. ###############################################################
  396. # Hauptschleife Datenabruf
  397. ###############################################################
  398. sub SMAInverter_GetData($) {
  399. my ($hash) = @_;
  400. my $name = $hash->{NAME};
  401. my $interval = AttrVal($name, "interval", 60);
  402. my $timeout = AttrVal($name, "timeout", 60);
  403. RemoveInternalTimer($hash, "SMAInverter_GetData");
  404. if ($init_done != 1) {
  405. InternalTimer(gettimeofday()+5, "SMAInverter_GetData", $hash, 0);
  406. return;
  407. }
  408. return if(IsDisabled($name));
  409. if (exists($hash->{HELPER}{RUNNING_PID})) {
  410. Log3 ($name, 3, "SMAInverter $name - WARNING - old process $hash->{HELPER}{RUNNING_PID}{pid} will be killed now to start a new BlockingCall");
  411. BlockingKill($hash->{HELPER}{RUNNING_PID});
  412. }
  413. Log3 ($name, 4, "$name - ###############################################################");
  414. Log3 ($name, 4, "$name - ########## Begin of new SMAInverter get data cycle ##########");
  415. Log3 ($name, 4, "$name - ###############################################################");
  416. Log3 ($name, 4, "$name - timeout cycles since module start: $hash->{HELPER}{FAULTEDCYCLES}");
  417. # decide of operation
  418. if(AttrVal($name,"mode","automatic") eq "automatic") {
  419. # automatic operation mode
  420. InternalTimer(gettimeofday()+$interval, "SMAInverter_GetData", $hash, 0);
  421. }
  422. $hash->{HELPER}{RUNNING_PID} = BlockingCall("getstatus_DoParse", "$name", "getstatus_ParseDone", $timeout, "getstatus_ParseAborted", $hash);
  423. $hash->{HELPER}{RUNNING_PID}{loglevel} = 4;
  424. return;
  425. }
  426. ###############################################################
  427. # non-blocking Inverter Datenabruf
  428. ###############################################################
  429. sub getstatus_DoParse($) {
  430. my ($name) = @_;
  431. my $hash = $defs{$name};
  432. my $interval = AttrVal($name, "interval", 60);
  433. my $sc = AttrVal($name, "SBFSpotComp", 0);
  434. my ($sup_EnergyProduction,
  435. $sup_SpotDCPower,
  436. $sup_SpotACPower,
  437. $sup_MaxACPower,
  438. $sup_MaxACPower2,
  439. $sup_SpotACTotalPower,
  440. $sup_ChargeStatus,
  441. $sup_SpotDCVoltage,
  442. $sup_SpotACVoltage,
  443. $sup_BatteryInfo,
  444. $sup_SpotGridFrequency,
  445. $sup_TypeLabel,
  446. $sup_OperationTime,
  447. $sup_InverterTemperature,
  448. $sup_GridRelayStatus,
  449. $sup_DeviceStatus);
  450. my ($inv_TYPE, $inv_CLASS,
  451. $inv_SPOT_ETODAY, $inv_SPOT_ETOTAL,
  452. $inv_susyid,
  453. $inv_serial,
  454. $inv_SPOT_PDC1, $inv_SPOT_PDC2,
  455. $inv_SPOT_PAC1, $inv_SPOT_PAC2, $inv_SPOT_PAC3, $inv_SPOT_PACTOT,
  456. $inv_PACMAX1, $inv_PACMAX2, $inv_PACMAX3, $inv_PACMAX1_2,
  457. $inv_ChargeStatus,
  458. $inv_SPOT_UDC1, $inv_SPOT_UDC2,
  459. $inv_SPOT_IDC1, $inv_SPOT_IDC2,
  460. $inv_SPOT_UAC1, $inv_SPOT_UAC2, $inv_SPOT_UAC3,
  461. $inv_SPOT_IAC1, $inv_SPOT_IAC2, $inv_SPOT_IAC3,
  462. $inv_BAT_UDC, $inv_BAT_IDC,
  463. $inv_BAT_CYCLES,
  464. $inv_BAT_TEMP,
  465. $inv_SPOT_FREQ, $inv_SPOT_OPERTM, $inv_SPOT_FEEDTM, $inv_TEMP, $inv_GRIDRELAY, $inv_STATUS,);
  466. my @row_array;
  467. my @array;
  468. my $avg = 0;
  469. my ($ist,$bst,$irt,$brt,$rt);
  470. # Background-Startzeit
  471. $bst = [gettimeofday];
  472. Log3 ($name, 4, "$name -> Start BlockingCall getstatus_DoParse");
  473. # set dependency from surise/sunset used for inverter operation time
  474. my $offset = AttrVal($name,"offset",0);
  475. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
  476. my ($sunrise_h,$sunrise_m,$sunrise_s) = split(":",sunrise_abs('-'.$offset));
  477. my ($sunset_h,$sunset_m,$sunset_s) = split(":",sunset_abs('+'.$offset));
  478. my $oper_start = DateTime->new(year=>$year+1900,month=>$mon+1,day=>$mday,hour=>$sunrise_h,minute=>$sunrise_m,second=>$sunrise_s,time_zone=>'local');
  479. my $oper_stop = DateTime->new(year=>$year+1900,month=>$mon+1,day=>$mday,hour=>$sunset_h,minute=>$sunset_m,second=>$sunset_s,time_zone=>'local');
  480. my $dt_now = DateTime->now(time_zone=>'local');
  481. Log3 $name, 4, "$name - current time: ".$dt_now->dmy('.')." ".$dt_now->hms;
  482. Log3 $name, 4, "$name - operation time begin: ".$oper_start->dmy('.')." ".$oper_start->hms;
  483. Log3 $name, 4, "$name - operation time end: ".$oper_stop->dmy('.')." ".$oper_stop->hms;
  484. my $opertime_start = $oper_start->dmy('.')." ".$oper_start->hms;
  485. my $opertime_stop = $oper_stop->dmy('.')." ".$oper_stop->hms;
  486. if (($oper_start <= $dt_now && $dt_now <= $oper_stop) || AttrVal($name,"suppressSleep",0)) {
  487. # normal operation or suppressed sleepmode
  488. # Abfrage Inverter Startzeit
  489. $ist = [gettimeofday];
  490. # Get the current attributes
  491. my $detail_level = AttrVal($name, "detail-level", 0);
  492. # Aufbau Command-Array
  493. my @commands = ("sup_TypeLabel", # Check TypeLabel
  494. "sup_EnergyProduction", # Check EnergyProduction
  495. "sup_SpotDCPower", # Check SpotDCPower
  496. "sup_SpotACPower", # Check SpotACPower
  497. "sup_SpotACTotalPower", # Check SpotACTotalPower
  498. "sup_ChargeStatus" # Check BatteryChargeStatus
  499. );
  500. if($detail_level > 0) {
  501. # Detail Level 1 or 2 >> get voltage and current levels
  502. push(@commands, "sup_SpotDCVoltage"); # Check SpotDCVoltage
  503. push(@commands, "sup_SpotACVoltage"); # Check SpotACVoltage
  504. push(@commands, "sup_BatteryInfo"); # Check BatteryInfo
  505. }
  506. if($detail_level > 1) {
  507. # Detail Level 2 >> get all data
  508. push(@commands, "sup_SpotGridFrequency"); # Check SpotGridFrequency
  509. push(@commands, "sup_OperationTime"); # Check OperationTime
  510. push(@commands, "sup_InverterTemperature"); # Check InverterTemperature
  511. push(@commands, "sup_MaxACPower"); # Check MaxACPower
  512. push(@commands, "sup_MaxACPower2"); # Check MaxACPower2
  513. push(@commands, "sup_GridRelayStatus"); # Check GridRelayStatus
  514. push(@commands, "sup_DeviceStatus"); # Check DeviceStatus
  515. }
  516. if(SMA_logon($hash->{HOST}, $hash->{PASS}, $hash)) {
  517. Log3 $name, 5, "$name - Logged in now";
  518. foreach my $i(@commands) {
  519. if ($i eq "sup_TypeLabel") {
  520. ($sup_TypeLabel,$inv_TYPE,$inv_CLASS,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x58000200, 0x00821E00, 0x008220FF);
  521. }
  522. elsif ($i eq "sup_EnergyProduction") {
  523. ($sup_EnergyProduction,$inv_SPOT_ETODAY,$inv_SPOT_ETOTAL,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x54000200, 0x00260100, 0x002622FF);
  524. }
  525. elsif ($i eq "sup_SpotDCPower") {
  526. ($sup_SpotDCPower,$inv_SPOT_PDC1,$inv_SPOT_PDC2,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x53800200, 0x00251E00, 0x00251EFF);
  527. }
  528. elsif ($i eq "sup_SpotACPower") {
  529. ($sup_SpotACPower,$inv_SPOT_PAC1,$inv_SPOT_PAC2,$inv_SPOT_PAC3,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x51000200, 0x00464000, 0x004642FF);
  530. }
  531. elsif ($i eq "sup_SpotACTotalPower") {
  532. ($sup_SpotACTotalPower,$inv_SPOT_PACTOT,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x51000200, 0x00263F00, 0x00263FFF);
  533. }
  534. elsif ($i eq "sup_ChargeStatus") {
  535. ($sup_ChargeStatus,$inv_ChargeStatus,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x51000200, 0x00295A00, 0x00295AFF);
  536. }
  537. elsif ($i eq "sup_SpotDCVoltage") {
  538. ($sup_SpotDCVoltage,$inv_SPOT_UDC1,$inv_SPOT_UDC2,$inv_SPOT_IDC1,$inv_SPOT_IDC2,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x53800200, 0x00451F00, 0x004521FF);
  539. }
  540. elsif ($i eq "sup_SpotACVoltage") {
  541. ($sup_SpotACVoltage,$inv_SPOT_UAC1,$inv_SPOT_UAC2,$inv_SPOT_UAC3,$inv_SPOT_IAC1,$inv_SPOT_IAC2,$inv_SPOT_IAC3,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x51000200, 0x00464800, 0x004655FF);
  542. }
  543. elsif ($i eq "sup_BatteryInfo") {
  544. ($sup_BatteryInfo,$inv_BAT_CYCLES,$inv_BAT_TEMP,$inv_BAT_UDC,$inv_BAT_IDC,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x51000200, 0x00491E00, 0x00495DFF);
  545. }
  546. elsif ($i eq "sup_SpotGridFrequency") {
  547. ($sup_SpotGridFrequency,$inv_SPOT_FREQ,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x51000200, 0x00465700, 0x004657FF);
  548. }
  549. elsif ($i eq "sup_OperationTime") {
  550. ($sup_OperationTime,$inv_SPOT_OPERTM,$inv_SPOT_FEEDTM,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x54000200, 0x00462E00, 0x00462FFF);
  551. }
  552. elsif ($i eq "sup_InverterTemperature") {
  553. ($sup_InverterTemperature,$inv_TEMP,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x52000200, 0x00237700, 0x002377FF);
  554. }
  555. elsif ($i eq "sup_MaxACPower") {
  556. ($sup_MaxACPower,$inv_PACMAX1,$inv_PACMAX2,$inv_PACMAX3,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x51000200, 0x00411E00, 0x004120FF);
  557. }
  558. elsif ($i eq "sup_MaxACPower2") {
  559. ($sup_MaxACPower2,$inv_PACMAX1_2,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x51000200, 0x00832A00, 0x00832AFF);
  560. }
  561. elsif ($i eq "sup_GridRelayStatus") {
  562. ($sup_GridRelayStatus,$inv_GRIDRELAY,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x51800200, 0x00416400, 0x004164FF);
  563. }
  564. elsif ($i eq "sup_DeviceStatus") {
  565. ($sup_DeviceStatus,$inv_STATUS,$inv_susyid,$inv_serial) = SMA_command($hash, $hash->{HOST}, 0x51800200, 0x00214800, 0x002148FF);
  566. }
  567. }
  568. # nothing more to do, just log out
  569. SMA_logout($hash,$hash->{HOST});
  570. # Inverter Laufzeit ermitteln
  571. $irt = tv_interval($ist);
  572. # Aufbau Ergebnis-Array
  573. push(@row_array, "modulstate normal"."\n");
  574. push(@row_array, "opertime_start ".$opertime_start."\n");
  575. push(@row_array, "opertime_stop ".$opertime_stop."\n");
  576. # Durchschnittswerteberechnung Energieerzeugung der letzten 5, 10, 15 Messungen
  577. my ($sum05, $sum10, $sum15);
  578. my $cnt05 = int(300/$interval); # Anzahl der Zyklen innerhalb 5 Minuten
  579. my $cnt10 = int(600/$interval); # Anzahl der Zyklen innerhalb 10 Minuten
  580. my $cnt15 = int(900/$interval); # Anzahl der Zyklen innerhalb 15 Minuten = Summe aller Messzyklen
  581. my $cntsum = $cnt15+1; # Sicherheitszuschlag Summe Anzahl aller Zyklen
  582. my @averagebuf;
  583. if ($sup_TypeLabel && $sup_EnergyProduction && $inv_CLASS eq 8001) {
  584. # only for this block because of warnings if values not set at restart
  585. no warnings 'uninitialized';
  586. if (!$hash->{HELPER}{AVERAGEBUF}) {
  587. for my $count (0..$cntsum) {
  588. # fill with new values
  589. $inv_SPOT_PACTOT = $inv_SPOT_PACTOT?$inv_SPOT_PACTOT:0;
  590. push(@averagebuf, $inv_SPOT_PACTOT);
  591. }
  592. } else {
  593. @averagebuf = split(/,/, $hash->{HELPER}{AVERAGEBUF})
  594. }
  595. # rechtes Element aus average buffer löschen
  596. pop(@averagebuf);
  597. # und links mit neuem Wert füllen
  598. unshift(@averagebuf, $inv_SPOT_PACTOT);
  599. $avg = join(',', @averagebuf);
  600. # calculate average energy and write to array for generate readings
  601. my $k = 1;
  602. my $avgsum = $averagebuf[0];
  603. while ($k < $cntsum) {
  604. $avgsum = $avgsum + $averagebuf[$k] if($averagebuf[$k]);
  605. if ($k == $cnt05) {
  606. $sum05 = $avgsum;
  607. Log3 $name, 5, "$name - CNT05: $cnt05 SUM05: $sum05";
  608. }
  609. if ($k == $cnt10) {
  610. $sum10 = $avgsum;
  611. Log3 $name, 5, "$name - CNT10: $cnt10 SUM10: $sum10";
  612. }
  613. if ($k == $cnt15) {
  614. $sum15 = $avgsum;
  615. Log3 $name, 5, "$name - CNT15: $cnt15 SUM15: $sum15";
  616. }
  617. $k++;
  618. }
  619. my $AvP05 = int( $sum05 / ($cnt05+1) );
  620. my $AvP10 = int( $sum10 / ($cnt10+1) );
  621. my $AvP15 = int( $sum15 / ($cnt15+1) );
  622. Log3 $name, 5, "$name - Content of Averagebuffer:";
  623. Log3 $name, 5, "$name - $avg";
  624. Log3 $name, 5, "$name - avg_power_lastminutes_05 = $AvP05, avg_power_lastminutes_10 = $AvP10, avg_power_lastminutes_15 = $AvP15";
  625. push(@row_array, "avg_power_lastminutes_05 ".$AvP05."\n"); # Average Energy (last) 5 minutes
  626. push(@row_array, "avg_power_lastminutes_10 ".$AvP10."\n"); # Average Energy (last) 10 minutes
  627. push(@row_array, "avg_power_lastminutes_15 ".$AvP15."\n"); # Average Energy (last) 15 minutes
  628. use warnings;
  629. }
  630. if ($sc) { # SBFSpot Kompatibilitätsmodus
  631. if($sup_EnergyProduction) {
  632. push(@row_array, "etotal ".($inv_SPOT_ETOTAL/1000)."\n");
  633. push(@row_array, "etoday ".($inv_SPOT_ETODAY/1000)."\n");
  634. }
  635. if($sup_SpotDCPower) {
  636. push(@row_array, "string_1_pdc ".sprintf("%.3f",$inv_SPOT_PDC1/1000)."\n");
  637. push(@row_array, "string_2_pdc ".sprintf("%.3f",$inv_SPOT_PDC2/1000)."\n");
  638. }
  639. if($sup_SpotACPower) {
  640. push(@row_array, "phase_1_pac ".sprintf("%.3f",$inv_SPOT_PAC1/1000)."\n");
  641. push(@row_array, "phase_2_pac ".sprintf("%.3f",$inv_SPOT_PAC2/1000)."\n");
  642. push(@row_array, "phase_3_pac ".sprintf("%.3f",$inv_SPOT_PAC3/1000)."\n");
  643. }
  644. if($sup_SpotACTotalPower) {
  645. push(@row_array, "total_pac ".sprintf("%.3f",$inv_SPOT_PACTOT/1000)."\n");
  646. push(@row_array, "state ".sprintf("%.3f",$inv_SPOT_PACTOT/1000)."\n");
  647. }
  648. if($sup_ChargeStatus) {
  649. push(@row_array, "chargestatus ".$inv_ChargeStatus."\n");
  650. }
  651. if($inv_CLASS && $inv_CLASS eq 8007) {
  652. if($inv_SPOT_PACTOT < 0) {
  653. push(@row_array, "power_out "."0"."\n");
  654. push(@row_array, "power_in ".(-1 * $inv_SPOT_PACTOT)."\n");
  655. } else {
  656. push(@row_array, "power_out ".$inv_SPOT_PACTOT."\n");
  657. push(@row_array, "power_in "."0"."\n");
  658. }
  659. }
  660. if($detail_level > 0) {
  661. # For Detail Level 1
  662. if($sup_SpotDCVoltage) {
  663. push(@row_array, "string_1_udc ".sprintf("%.2f",$inv_SPOT_UDC1)."\n");
  664. push(@row_array, "string_2_udc ".sprintf("%.2f",$inv_SPOT_UDC2)."\n");
  665. push(@row_array, "string_1_idc ".sprintf("%.3f",$inv_SPOT_IDC1)."\n");
  666. push(@row_array, "string_2_idc ".sprintf("%.3f",$inv_SPOT_IDC2)."\n");
  667. }
  668. if($sup_SpotACVoltage) {
  669. push(@row_array, "phase_1_uac ".sprintf("%.2f",$inv_SPOT_UAC1)."\n");
  670. push(@row_array, "phase_2_uac ".sprintf("%.2f",$inv_SPOT_UAC2)."\n");
  671. push(@row_array, "phase_3_uac ".sprintf("%.2f",$inv_SPOT_UAC3)."\n");
  672. push(@row_array, "phase_1_iac ".sprintf("%.3f",$inv_SPOT_IAC1)."\n");
  673. push(@row_array, "phase_2_iac ".sprintf("%.3f",$inv_SPOT_IAC2)."\n");
  674. push(@row_array, "phase_3_iac ".sprintf("%.3f",$inv_SPOT_IAC3)."\n");
  675. }
  676. if($sup_BatteryInfo) {
  677. push(@row_array, "bat_udc ".$inv_BAT_UDC."\n");
  678. push(@row_array, "bat_idc ".$inv_BAT_IDC."\n");
  679. }
  680. }
  681. if($detail_level > 1) {
  682. # For Detail Level 2
  683. if($sup_BatteryInfo) {
  684. push(@row_array,"bat_cycles ".$inv_BAT_CYCLES."\n");
  685. push(@row_array, "bat_temp ".$inv_BAT_TEMP."\n");
  686. }
  687. if($sup_SpotGridFrequency) {
  688. push(@row_array, "grid_freq. ".sprintf("%.2f",$inv_SPOT_FREQ)."\n");
  689. }
  690. if($sup_TypeLabel) {
  691. push(@row_array, "device_type ".devtype($inv_TYPE)."\n");
  692. push(@row_array, "device_class ".classtype($inv_CLASS)."\n");
  693. push(@row_array, "susyid ".$inv_susyid." - SN: ".$inv_serial."\n") if($inv_susyid && $inv_serial);
  694. push(@row_array, "device_name "."SN: ".$inv_serial."\n") if($inv_serial);
  695. push(@row_array, "serial_number ".$inv_serial."\n") if($inv_serial);
  696. }
  697. if($sup_MaxACPower) {
  698. push(@row_array, "pac_max_phase_1 ".$inv_PACMAX1."\n");
  699. push(@row_array, "pac_max_phase_2 ".$inv_PACMAX2."\n");
  700. push(@row_array, "pac_max_phase_3 ".$inv_PACMAX3."\n");
  701. }
  702. if($sup_MaxACPower2) {
  703. push(@row_array, "pac_max_phase_1_2 ".$inv_PACMAX1_2."\n");
  704. }
  705. if($sup_InverterTemperature) {
  706. push(@row_array, "device_temperature ".sprintf("%.1f",$inv_TEMP)."\n");
  707. }
  708. if($sup_OperationTime) {
  709. push(@row_array, "feed-in_time ".$inv_SPOT_FEEDTM."\n");
  710. push(@row_array, "operation_time ".$inv_SPOT_OPERTM."\n");
  711. }
  712. if($sup_GridRelayStatus) {
  713. push(@row_array, "gridrelay_status ".StatusText($inv_GRIDRELAY)."\n");
  714. }
  715. if($sup_DeviceStatus) {
  716. push(@row_array, "device_status ".StatusText($inv_STATUS)."\n");
  717. }
  718. }
  719. } else { # kein SBFSpot Compatibility Mode
  720. if($sup_EnergyProduction) {
  721. push(@row_array, "SPOT_ETOTAL ".$inv_SPOT_ETOTAL."\n");
  722. push(@row_array, "SPOT_ETODAY ".$inv_SPOT_ETODAY."\n");
  723. }
  724. if($sup_SpotDCPower) {
  725. push(@row_array, "SPOT_PDC1 ".$inv_SPOT_PDC1."\n");
  726. push(@row_array, "SPOT_PDC2 ".$inv_SPOT_PDC2."\n");
  727. }
  728. if($sup_SpotACPower) {
  729. push(@row_array, "SPOT_PAC1 ".$inv_SPOT_PAC1."\n");
  730. push(@row_array, "SPOT_PAC2 ".$inv_SPOT_PAC2."\n");
  731. push(@row_array, "SPOT_PAC3 ".$inv_SPOT_PAC3."\n");
  732. }
  733. if($sup_SpotACTotalPower) {
  734. push(@row_array, "SPOT_PACTOT ".$inv_SPOT_PACTOT."\n");
  735. push(@row_array, "state ".$inv_SPOT_PACTOT."\n");
  736. }
  737. if($sup_ChargeStatus) {
  738. push(@row_array, "ChargeStatus ".$inv_ChargeStatus."\n");
  739. }
  740. if($inv_CLASS && $inv_CLASS eq 8007) {
  741. if($inv_SPOT_PACTOT < 0) {
  742. push(@row_array, "POWER_OUT "."0"."\n");
  743. push(@row_array, "POWER_IN ".(-1 * $inv_SPOT_PACTOT)."\n");
  744. } else {
  745. push(@row_array, "POWER_OUT ".$inv_SPOT_PACTOT."\n");
  746. push(@row_array, "POWER_IN "."0"."\n");
  747. }
  748. }
  749. if($detail_level > 0) {
  750. # For Detail Level 1
  751. if($sup_SpotDCVoltage) {
  752. push(@row_array, "SPOT_UDC1 ".$inv_SPOT_UDC1."\n");
  753. push(@row_array, "SPOT_UDC2 ".$inv_SPOT_UDC2."\n");
  754. push(@row_array, "SPOT_IDC1 ".$inv_SPOT_IDC1."\n");
  755. push(@row_array, "SPOT_IDC2 ".$inv_SPOT_IDC2."\n");
  756. }
  757. if($sup_SpotACVoltage) {
  758. push(@row_array, "SPOT_UAC1 ".$inv_SPOT_UAC1."\n");
  759. push(@row_array, "SPOT_UAC2 ".$inv_SPOT_UAC2."\n");
  760. push(@row_array, "SPOT_UAC3 ".$inv_SPOT_UAC3."\n");
  761. push(@row_array, "SPOT_IAC1 ".$inv_SPOT_IAC1."\n");
  762. push(@row_array, "SPOT_IAC2 ".$inv_SPOT_IAC2."\n");
  763. push(@row_array, "SPOT_IAC3 ".$inv_SPOT_IAC3."\n");
  764. }
  765. if($sup_BatteryInfo) {
  766. push(@row_array, "BAT_UDC ".$inv_BAT_UDC."\n");
  767. push(@row_array, "BAT_IDC ".$inv_BAT_IDC."\n");
  768. }
  769. }
  770. if($detail_level > 1) {
  771. # For Detail Level 2
  772. if($sup_BatteryInfo) {
  773. push(@row_array, "BAT_CYCLES ".$inv_BAT_CYCLES."\n");
  774. push(@row_array, "BAT_TEMP ".$inv_BAT_TEMP."\n");
  775. }
  776. if($sup_SpotGridFrequency) {
  777. push(@row_array, "SPOT_FREQ ".$inv_SPOT_FREQ."\n");
  778. }
  779. if($sup_TypeLabel) {
  780. push(@row_array, "INV_TYPE ".devtype($inv_TYPE)."\n");
  781. push(@row_array, "INV_CLASS ".classtype($inv_CLASS)."\n");
  782. push(@row_array, "SUSyID ".$inv_susyid."\n") if($inv_susyid);
  783. push(@row_array, "Serialnumber ".$inv_serial."\n") if($inv_serial);
  784. }
  785. if($sup_MaxACPower) {
  786. push(@row_array, "INV_PACMAX1 ".$inv_PACMAX1."\n");
  787. push(@row_array, "INV_PACMAX2 ".$inv_PACMAX2."\n");
  788. push(@row_array, "INV_PACMAX3 ".$inv_PACMAX3."\n");
  789. }
  790. if($sup_MaxACPower2) {
  791. push(@row_array, "INV_PACMAX1_2 ".$inv_PACMAX1_2."\n");
  792. }
  793. if($sup_InverterTemperature) {
  794. push(@row_array, "INV_TEMP ".$inv_TEMP."\n");
  795. }
  796. if($sup_OperationTime) {
  797. push(@row_array, "SPOT_FEEDTM ".$inv_SPOT_FEEDTM."\n");
  798. push(@row_array, "SPOT_OPERTM ".$inv_SPOT_OPERTM."\n");
  799. }
  800. if($sup_GridRelayStatus) {
  801. push(@row_array, "INV_GRIDRELAY ".StatusText($inv_GRIDRELAY)."\n");
  802. }
  803. if($sup_DeviceStatus) {
  804. push(@row_array, "INV_STATUS ".StatusText($inv_STATUS)."\n");
  805. }
  806. }
  807. }
  808. } else {
  809. # Login failed/not possible
  810. push(@row_array, "state Login failed"."\n");
  811. push(@row_array, "modulstate login failed"."\n");
  812. }
  813. } else {
  814. # sleepmode at current time and not suppressed
  815. push(@row_array, "modulstate sleep"."\n");
  816. push(@row_array, "opertime_start ".$opertime_start."\n");
  817. push(@row_array, "opertime_stop ".$opertime_stop."\n");
  818. push(@row_array, "state done"."\n");
  819. }
  820. Log3 ($name, 5, "$name -> row_array before encoding:");
  821. foreach my $row (@row_array) {
  822. chomp $row;
  823. Log3 ($name, 5, "$name -> $row");
  824. }
  825. # encoding result
  826. my $rowlist = join('|', @row_array);
  827. $rowlist = encode_base64($rowlist,"");
  828. # Background-Laufzeit ermitteln
  829. $brt = tv_interval($bst);
  830. $rt = ($irt?$irt:'').",".$brt;
  831. Log3 ($name, 4, "$name -> BlockingCall getstatus_DoParse finished");
  832. return "$name|$rowlist|$avg|$rt";
  833. }
  834. ###############################################################
  835. # Auswertung non-blocking Inverter Datenabruf
  836. ###############################################################
  837. sub getstatus_ParseDone ($) {
  838. my ($string) = @_;
  839. my @a = split("\\|",$string);
  840. my $name = $a[0];
  841. my $hash = $defs{$name};
  842. my $rowlist = decode_base64($a[1]);
  843. $hash->{HELPER}{AVERAGEBUF} = $a[2] if($a[2]);
  844. my $rt = $a[3];
  845. my ($irt,$brt) = split(",", $rt);
  846. Log3 ($name, 4, "$name -> Start BlockingCall getstatus_ParseDone");
  847. # proctime Readings löschen
  848. if(!AttrVal($name, "showproctime", undef)) {
  849. delete($defs{$name}{READINGS}{inverter_processing_time});
  850. delete($defs{$name}{READINGS}{background_processing_time});
  851. } else {
  852. delete($defs{$name}{READINGS}{inverter_processing_time}) if(!$irt);
  853. }
  854. # Get current time
  855. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
  856. $hash->{LASTUPDATE} = sprintf "%02d.%02d.%04d / %02d:%02d:%02d" , $mday , $mon+=1 ,$year+=1900 , $hour , $min , $sec ;
  857. my @row_array = split("\\|", $rowlist);
  858. Log3 ($name, 5, "$name -> row_array after decoding:");
  859. foreach my $row (@row_array) {
  860. chomp $row;
  861. Log3 ($name, 5, "$name -> $row");
  862. }
  863. readingsBeginUpdate($hash);
  864. foreach my $row (@row_array) {
  865. chomp $row;
  866. my @a = split(" ", $row, 2);
  867. $hash->{MODEL} = $a[1] if($a[0] eq "device_type");
  868. readingsBulkUpdate($hash, $a[0], $a[1]);
  869. }
  870. readingsBulkUpdate($hash, "background_processing_time", sprintf("%.4f",$brt)) if(AttrVal($name, "showproctime", undef));
  871. readingsBulkUpdate($hash, "inverter_processing_time", sprintf("%.4f",$irt)) if(AttrVal($name, "showproctime", undef) && $irt);
  872. readingsEndUpdate($hash, 1);
  873. delete($hash->{HELPER}{RUNNING_PID});
  874. Log3 ($name, 4, "$name -> BlockingCall getstatus_ParseDone finished");
  875. return;
  876. }
  877. ###############################################################
  878. # Abbruchroutine Timeout Inverter Abfrage
  879. ###############################################################
  880. sub getstatus_ParseAborted(@) {
  881. my ($hash,$cause) = @_;
  882. my $name = $hash->{NAME};
  883. my $discycles = $hash->{HELPER}{FAULTEDCYCLES};
  884. $cause = $cause?$cause:"Timeout: process terminated";
  885. # count of timeouts since module start
  886. $discycles++;
  887. $hash->{HELPER}{FAULTEDCYCLES} = $discycles;
  888. Log3 ($name, 1, "SMAInverter $name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} $cause");
  889. readingsSingleUpdate($hash,"state",$cause, 1);
  890. delete($hash->{HELPER}{RUNNING_PID});
  891. return;
  892. }
  893. ##########################################################################
  894. # SMA Command Execution
  895. ##########################################################################
  896. sub SMA_command($$$$$) {
  897. # Parameters: $hash - host - command - first - last
  898. my ($hash,$host,$command,$first,$last) = @_;
  899. my $name = $hash->{NAME};
  900. my $cmdheader = "534D4100000402A00000000100";
  901. my $pktlength = "26"; # length = 38 for data commands
  902. my $esignature = "0010606509A0";
  903. my ($inv_TYPE, $inv_CLASS,
  904. $inv_SPOT_ETODAY, $inv_SPOT_ETOTAL,
  905. $inv_susyid,
  906. $inv_serial,
  907. $inv_SPOT_PDC1, $inv_SPOT_PDC2,
  908. $inv_SPOT_PAC1, $inv_SPOT_PAC2, $inv_SPOT_PAC3, $inv_SPOT_PACTOT,
  909. $inv_PACMAX1, $inv_PACMAX2, $inv_PACMAX3, $inv_PACMAX1_2,
  910. $inv_ChargeStatus,
  911. $inv_SPOT_UDC1, $inv_SPOT_UDC2,
  912. $inv_SPOT_IDC1, $inv_SPOT_IDC2,
  913. $inv_SPOT_UAC1, $inv_SPOT_UAC2, $inv_SPOT_UAC3,
  914. $inv_SPOT_IAC1, $inv_SPOT_IAC2, $inv_SPOT_IAC3,
  915. $inv_BAT_UDC, $inv_BAT_IDC,
  916. $inv_BAT_CYCLES,
  917. $inv_BAT_TEMP,
  918. $inv_SPOT_FREQ, $inv_SPOT_OPERTM, $inv_SPOT_FEEDTM, $inv_TEMP, $inv_GRIDRELAY, $inv_STATUS);
  919. my $mysusyid = $hash->{HELPER}{MYSUSYID};
  920. my $myserialnumber = $hash->{HELPER}{MYSERIALNUMBER};
  921. my ($cmd, $myID, $target_ID, $spkt_ID, $cmd_ID);
  922. my ($socket,$data,$size,$data_ID);
  923. my ($i, $temp); # Variables for loops and calculation
  924. # Seriennummer und SuSyID des Ziel-WR setzen
  925. my $default_target_susyid = $hash->{HELPER}{DEFAULT_TARGET_SUSYID};
  926. my $default_target_serial = $hash->{HELPER}{DEFAULT_TARGET_SERIAL};
  927. my $target_susyid = AttrVal($name, "target-susyid", $default_target_susyid);
  928. my $target_serial = AttrVal($name, "target-serial", $default_target_serial);
  929. # Define own ID and target ID and packet ID
  930. $myID = ByteOrderShort(substr(sprintf("%04X",$mysusyid),0,4)) . ByteOrderLong(sprintf("%08X",$myserialnumber));
  931. $target_ID = ByteOrderShort(substr(sprintf("%04X",$target_susyid),0,4)) . ByteOrderLong(sprintf("%08X",$target_serial));
  932. # Increasing Packet ID
  933. $hash->{HELPER}{PKT_ID} = $hash->{HELPER}{PKT_ID} + 1;
  934. $spkt_ID = ByteOrderShort(sprintf("%04X",$hash->{HELPER}{PKT_ID}));
  935. $cmd_ID = ByteOrderLong(sprintf("%08X",$command)) . ByteOrderLong(sprintf("%08X",$first)) . ByteOrderLong(sprintf("%08X",$last));
  936. #build final command to send
  937. $cmd = $cmdheader . $pktlength . $esignature . $target_ID . "0000" . $myID . "0000" . "00000000" . $spkt_ID . $cmd_ID . "00000000";
  938. # flush after every write
  939. $| = 1;
  940. # Create Socket and check if successful
  941. $socket = new IO::Socket::INET (PeerHost => $host, PeerPort => 9522, Proto => 'udp',); # open Socket
  942. if (!$socket) {
  943. # in case of error
  944. Log3 $name, 1, "$name - ERROR. Can't open socket to inverter: $!";
  945. return 0;
  946. };
  947. # Send Data
  948. $data = pack("H*",$cmd);
  949. $socket->send($data);
  950. Log3 $name, 3, "$name - Send request $cmd_ID to $host on port 9522";
  951. Log3 $name, 5, "$name - send: $cmd";
  952. # Receive Data and do a first check regarding length
  953. # receive data
  954. $socket->recv($data, $hash->{HELPER}{MAXBYTES});
  955. $size = length($data);
  956. # check if something was received
  957. if (defined $size) {
  958. my $received = unpack("H*", $data);
  959. Log3 $name, 5, "$name - Received: $received";
  960. }
  961. # Nothing received -> exit
  962. if (not defined $size) {
  963. Log3 $name, 1, "$name - Nothing received...";
  964. return 0;
  965. } else {
  966. # We have received something!
  967. if ($size > 58) {
  968. # Check all parameters of answer
  969. my $r_susyid = unpack("v*", substr $data, 20, 2);
  970. my $r_serial = unpack("V*", substr $data, 22, 4);
  971. my $r_pkt_ID = unpack("v*", substr $data, 40, 2);
  972. my $r_error = unpack("V*", substr $data, 36, 4);
  973. if (($r_susyid ne $mysusyid) || ($r_serial ne $myserialnumber) || ($r_pkt_ID ne $hash->{HELPER}{PKT_ID}) || ($r_error ne 0)) {
  974. # Response does not match the parameters we have sent, maybe different target
  975. Log3 $name, 3, "$name - Inverter answer does not match our parameters.";
  976. Log3 $name, 5, "$name - Request/Response: SusyID $mysusyid/$r_susyid, Serial $myserialnumber/$r_serial, Packet ID $hash->{HELPER}{PKT_ID}/$r_pkt_ID, Error $r_error";
  977. $socket->close();
  978. return 0;
  979. }
  980. } else {
  981. Log3 $name, 3, "$name - Format of inverter response does not fit.";
  982. $socket->close();
  983. return 0;
  984. }
  985. }
  986. # All seems ok, data received
  987. $inv_susyid = unpack("v*", substr $data, 28, 2);
  988. $inv_serial = unpack("V*", substr $data, 30, 4);
  989. $socket->close();
  990. if (AttrVal($name, "target-serial", undef)) {
  991. return 0 unless($target_serial eq $inv_serial);
  992. }
  993. if (AttrVal($name, "target-susyid", undef)) {
  994. return 0 unless($target_susyid eq $inv_susyid);
  995. }
  996. # Check the data identifier
  997. $data_ID = unpack("v*", substr $data, 55, 2);
  998. Log3 $name, 5, "$name - Data identifier $data_ID";
  999. if($data_ID eq 0x2601) {
  1000. $inv_SPOT_ETOTAL = unpack("V*", substr($data, 62, 4));
  1001. $inv_SPOT_ETODAY = unpack("V*", substr $data, 78, 4);
  1002. Log3 $name, 5, "$name - Found Data SPOT_ETOTAL=$inv_SPOT_ETOTAL and SPOT_ETODAY=$inv_SPOT_ETODAY";
  1003. return (1,$inv_SPOT_ETODAY,$inv_SPOT_ETOTAL,$inv_susyid,$inv_serial);
  1004. }
  1005. if($data_ID eq 0x251E) {
  1006. $inv_SPOT_PDC1 = unpack("V*", substr $data, 62, 4);
  1007. if($size < 90) {$inv_SPOT_PDC2 = 0; } else {$inv_SPOT_PDC2 = unpack("V*", substr $data, 90, 4); } # catch short response, in case PDC2 not supported
  1008. $inv_SPOT_PDC1 = ($inv_SPOT_PDC1 == 2147483648) ? 0 : $inv_SPOT_PDC1;
  1009. $inv_SPOT_PDC2 = ($inv_SPOT_PDC2 == 2147483648) ? 0 : $inv_SPOT_PDC2;
  1010. Log3 $name, 5, "$name - Found Data SPOT_PDC1=$inv_SPOT_PDC1 and SPOT_PDC2=$inv_SPOT_PDC2";
  1011. return (1,$inv_SPOT_PDC1,$inv_SPOT_PDC2,$inv_susyid,$inv_serial);
  1012. }
  1013. if($data_ID eq 0x4640) {
  1014. $inv_SPOT_PAC1 = unpack("l*", substr $data, 62, 4);
  1015. if($inv_SPOT_PAC1 eq -2147483648) {$inv_SPOT_PAC1 = 0; } # Catch 0x80000000 as 0 value
  1016. $inv_SPOT_PAC2 = unpack("l*", substr $data, 90, 4);
  1017. if($inv_SPOT_PAC2 eq -2147483648) {$inv_SPOT_PAC2 = 0; } # Catch 0x80000000 as 0 value
  1018. $inv_SPOT_PAC3 = unpack("l*", substr $data, 118, 4);
  1019. if($inv_SPOT_PAC3 eq -2147483648) {$inv_SPOT_PAC3 = 0; } # Catch 0x80000000 as 0 value
  1020. Log3 $name, 5, "$name - Found Data SPOT_PAC1=$inv_SPOT_PAC1 and SPOT_PAC2=$inv_SPOT_PAC2 and SPOT_PAC3=$inv_SPOT_PAC3";
  1021. return (1,$inv_SPOT_PAC1,$inv_SPOT_PAC2,$inv_SPOT_PAC3,$inv_susyid,$inv_serial);
  1022. }
  1023. if($data_ID eq 0x411E) {
  1024. $inv_PACMAX1 = unpack("V*", substr $data, 62, 4);
  1025. $inv_PACMAX2 = unpack("V*", substr $data, 90, 4);
  1026. $inv_PACMAX3 = unpack("V*", substr $data, 118, 4);
  1027. Log3 $name, 5, "$name - Found Data INV_PACMAX1=$inv_PACMAX1 and INV_PACMAX2=$inv_PACMAX2 and INV_PACMAX3=$inv_PACMAX3";
  1028. return (1,$inv_PACMAX1,$inv_PACMAX2,$inv_PACMAX3,$inv_susyid,$inv_serial);
  1029. }
  1030. if($data_ID eq 0x832A) {
  1031. $inv_PACMAX1_2 = unpack("V*", substr $data, 62, 4);
  1032. Log3 $name, 5, "$name - Found Data INV_PACMAX1_2=$inv_PACMAX1_2";
  1033. return (1,$inv_PACMAX1_2,$inv_susyid,$inv_serial);
  1034. }
  1035. if($data_ID eq 0x263F) {
  1036. $inv_SPOT_PACTOT = unpack("l*", substr $data, 62, 4);
  1037. if($inv_SPOT_PACTOT eq -2147483648) {$inv_SPOT_PACTOT = 0; } # Catch 0x80000000 as 0 value
  1038. Log3 $name, 5, "$name - Found Data SPOT_PACTOT=$inv_SPOT_PACTOT";
  1039. return (1,$inv_SPOT_PACTOT,$inv_susyid,$inv_serial);
  1040. }
  1041. if($data_ID eq 0x295A) {
  1042. $inv_ChargeStatus = unpack("V*", substr $data, 62, 4);
  1043. Log3 $name, 5, "$name - Found Data Battery Charge Status=$inv_ChargeStatus";
  1044. return (1,$inv_ChargeStatus,$inv_susyid,$inv_serial);
  1045. }
  1046. if($data_ID eq 0x451F) {
  1047. $inv_SPOT_UDC1 = unpack("l*", substr $data, 62, 4);
  1048. # catch shorter responses in case not second string supported
  1049. if($size < 146) {
  1050. $inv_SPOT_UDC2 = 0;
  1051. $inv_SPOT_IDC1 = unpack("l*", substr $data, 90, 4);
  1052. $inv_SPOT_IDC2 = 0;
  1053. } else {
  1054. $inv_SPOT_UDC2 = unpack("l*", substr $data, 90, 4);
  1055. $inv_SPOT_IDC1 = unpack("l*", substr $data, 118, 4);
  1056. $inv_SPOT_IDC2 = unpack("l*", substr $data, 146, 4);
  1057. }
  1058. if(($inv_SPOT_UDC1 eq -2147483648) || ($inv_SPOT_UDC1 eq 0xFFFFFFFF)) {$inv_SPOT_UDC1 = 0; } else {$inv_SPOT_UDC1 = $inv_SPOT_UDC1 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1059. if(($inv_SPOT_UDC2 eq -2147483648) || ($inv_SPOT_UDC2 eq 0xFFFFFFFF)) {$inv_SPOT_UDC2 = 0; } else {$inv_SPOT_UDC2 = $inv_SPOT_UDC2 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1060. if(($inv_SPOT_IDC1 eq -2147483648) || ($inv_SPOT_IDC1 eq 0xFFFFFFFF)) {$inv_SPOT_IDC1 = 0; } else {$inv_SPOT_IDC1 = $inv_SPOT_IDC1 / 1000; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1061. if(($inv_SPOT_IDC2 eq -2147483648) || ($inv_SPOT_IDC2 eq 0xFFFFFFFF)) {$inv_SPOT_IDC2 = 0; } else {$inv_SPOT_IDC2 = $inv_SPOT_IDC2 / 1000; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1062. Log3 $name, 5, "$name - Found Data SPOT_UDC1=$inv_SPOT_UDC1 and SPOT_UDC2=$inv_SPOT_UDC2 and SPOT_IDC1=$inv_SPOT_IDC1 and SPOT_IDC2=$inv_SPOT_IDC2";
  1063. return (1,$inv_SPOT_UDC1,$inv_SPOT_UDC2,$inv_SPOT_IDC1,$inv_SPOT_IDC2,$inv_susyid,$inv_serial);
  1064. }
  1065. if($data_ID eq 0x4648) {
  1066. $inv_SPOT_UAC1 = unpack("l*", substr $data, 62, 4);
  1067. $inv_SPOT_UAC2 = unpack("l*", substr $data, 90, 4);
  1068. $inv_SPOT_UAC3 = unpack("l*", substr $data, 118, 4);
  1069. $inv_SPOT_IAC1 = unpack("l*", substr $data, 146, 4);
  1070. $inv_SPOT_IAC2 = unpack("l*", substr $data, 174, 4);
  1071. $inv_SPOT_IAC3 = unpack("l*", substr $data, 202, 4);
  1072. if(($inv_SPOT_UAC1 eq -2147483648) || ($inv_SPOT_UAC1 eq 0xFFFFFFFF) || $inv_SPOT_UAC1 < 0) {$inv_SPOT_UAC1 = 0; } else {$inv_SPOT_UAC1 = $inv_SPOT_UAC1 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1073. if(($inv_SPOT_UAC2 eq -2147483648) || ($inv_SPOT_UAC2 eq 0xFFFFFFFF) || $inv_SPOT_UAC2 < 0) {$inv_SPOT_UAC2 = 0; } else {$inv_SPOT_UAC2 = $inv_SPOT_UAC2 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1074. if(($inv_SPOT_UAC3 eq -2147483648) || ($inv_SPOT_UAC3 eq 0xFFFFFFFF) || $inv_SPOT_UAC3 < 0) {$inv_SPOT_UAC3 = 0; } else {$inv_SPOT_UAC3 = $inv_SPOT_UAC3 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1075. if(($inv_SPOT_IAC1 eq -2147483648) || ($inv_SPOT_IAC1 eq 0xFFFFFFFF)) {$inv_SPOT_IAC1 = 0; } else {$inv_SPOT_IAC1 = $inv_SPOT_IAC1 / 1000; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1076. if(($inv_SPOT_IAC2 eq -2147483648) || ($inv_SPOT_IAC2 eq 0xFFFFFFFF)) {$inv_SPOT_IAC2 = 0; } else {$inv_SPOT_IAC2 = $inv_SPOT_IAC2 / 1000; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1077. if(($inv_SPOT_IAC3 eq -2147483648) || ($inv_SPOT_IAC3 eq 0xFFFFFFFF)) {$inv_SPOT_IAC3 = 0; } else {$inv_SPOT_IAC3 = $inv_SPOT_IAC3 / 1000; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1078. Log3 $name, 5, "$name - Found Data SPOT_UAC1=$inv_SPOT_UAC1 and SPOT_UAC2=$inv_SPOT_UAC2 and SPOT_UAC3=$inv_SPOT_UAC3 and SPOT_IAC1=$inv_SPOT_IAC1 and SPOT_IAC2=$inv_SPOT_IAC2 and SPOT_IAC3=$inv_SPOT_IAC3";
  1079. return (1,$inv_SPOT_UAC1,$inv_SPOT_UAC2,$inv_SPOT_UAC3,$inv_SPOT_IAC1,$inv_SPOT_IAC2,$inv_SPOT_IAC3,$inv_susyid,$inv_serial);
  1080. }
  1081. if($data_ID eq 0x491E) {
  1082. $inv_BAT_CYCLES = unpack("V*", substr $data, 62, 4);
  1083. $inv_BAT_TEMP = unpack("V*", substr $data, 90, 4) / 10;
  1084. $inv_BAT_UDC = unpack("V*", substr $data, 118, 4) / 100;
  1085. $inv_BAT_IDC = unpack("l*", substr $data, 146, 4);
  1086. if($inv_BAT_IDC eq -2147483648) {$inv_BAT_IDC = 0; } else { $inv_BAT_IDC = $inv_BAT_IDC / 1000;} # Catch 0x80000000 as 0 value
  1087. Log3 $name, 5, "$name - Found Data BAT_CYCLES=$inv_BAT_CYCLES and BAT_TEMP=$inv_BAT_TEMP and BAT_UDC=$inv_BAT_UDC and BAT_IDC=$inv_BAT_IDC";
  1088. return (1,$inv_BAT_CYCLES,$inv_BAT_TEMP,$inv_BAT_UDC,$inv_BAT_IDC,$inv_susyid,$inv_serial);
  1089. }
  1090. if($data_ID eq 0x2377) {
  1091. $inv_TEMP = unpack("l*", substr $data, 62, 4);
  1092. if($inv_TEMP eq -2147483648) {$inv_TEMP = 0; } else { $inv_TEMP = $inv_TEMP / 100;} # Catch 0x80000000 as 0 value
  1093. Log3 $name, 5, "$name - Found Data Inverter Temp=$inv_TEMP";
  1094. return (1,$inv_TEMP,$inv_susyid,$inv_serial);
  1095. }
  1096. if($data_ID eq 0x462E) {
  1097. $inv_SPOT_OPERTM = int(unpack("V*", substr $data, 62, 4) / 36) / 100;
  1098. $inv_SPOT_FEEDTM = int(unpack("V*", substr $data, 78, 4) / 36) / 100;
  1099. Log3 $name, 5, "$name - Found Data SPOT_OPERTM=$inv_SPOT_OPERTM and SPOT_FEEDTM=$inv_SPOT_FEEDTM";
  1100. return (1,$inv_SPOT_OPERTM,$inv_SPOT_FEEDTM,$inv_susyid,$inv_serial);
  1101. }
  1102. if($data_ID eq 0x4657) {
  1103. $inv_SPOT_FREQ = unpack("V*", substr $data, 62, 4);
  1104. if(($inv_SPOT_FREQ eq -2147483648) || ($inv_SPOT_FREQ eq 0xFFFFFFFF)) {$inv_SPOT_FREQ = 0; } else {$inv_SPOT_FREQ = $inv_SPOT_FREQ / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value
  1105. Log3 $name, 5, "$name - Found Data SPOT_FREQ=$inv_SPOT_FREQ";
  1106. return (1,$inv_SPOT_FREQ,$inv_susyid,$inv_serial);
  1107. }
  1108. if($data_ID eq 0x821E) {
  1109. $inv_CLASS = unpack("V*", substr $data, 102, 4) & 0x00FFFFFF;
  1110. $i = 142; # start address of INV_TYPE
  1111. $inv_TYPE = 0; # initialize to unknown inverter type
  1112. do {
  1113. $temp = unpack("V*", substr $data, $i, 4);
  1114. if(($temp & 0xFF000000) eq 0x01000000) { $inv_TYPE = $temp & 0x00FFFFFF; } # in some models a catalogue is transmitted, right model marked with: 0x01000000 OR INV_Type
  1115. $i = $i+4;
  1116. } while ((unpack("V*", substr $data, $i, 4) ne 0x00FFFFFE) && ($i<$size)); # 0x00FFFFFE is the end marker for attributes
  1117. Log3 $name, 5, "$name - Found Data CLASS=$inv_CLASS and TYPE=$inv_TYPE";
  1118. return (1,$inv_TYPE,$inv_CLASS,$inv_susyid,$inv_serial);
  1119. }
  1120. if($data_ID eq 0x4164) {
  1121. $i = 0;
  1122. $temp = 0;
  1123. $inv_GRIDRELAY = 0x00FFFFFD; # Code for No Information;
  1124. do {
  1125. $temp = unpack("V*", substr $data, 62 + $i*4, 4);
  1126. if(($temp & 0xFF000000) ne 0) { $inv_GRIDRELAY = $temp & 0x00FFFFFF; }
  1127. $i = $i + 1;
  1128. } while ((unpack("V*", substr $data, 62 + $i*4, 4) ne 0x00FFFFFE) && ($i < 5)); # 0x00FFFFFE is the end marker for attributes
  1129. Log3 $name, 5, "$name - Found Data INV_GRIDRELAY=$inv_GRIDRELAY";
  1130. return (1,$inv_GRIDRELAY,$inv_susyid,$inv_serial);
  1131. }
  1132. if($data_ID eq 0x2148) {
  1133. $i = 0;
  1134. $temp = 0;
  1135. $inv_STATUS = 0x00FFFFFD; # Code for No Information;
  1136. do {
  1137. $temp = unpack("V*", substr $data, 62 + $i*4, 4);
  1138. if(($temp & 0xFF000000) ne 0) { $inv_STATUS = $temp & 0x00FFFFFF; }
  1139. $i = $i + 1;
  1140. } while ((unpack("V*", substr $data, 62 + $i*4, 4) ne 0x00FFFFFE) && ($i < 5)); # 0x00FFFFFE is the end marker for attributes
  1141. Log3 $name, 5, "$name - Found Data inv_STATUS=$inv_STATUS";
  1142. return (1,$inv_STATUS,$inv_susyid,$inv_serial);
  1143. }
  1144. return 0;
  1145. }
  1146. ##########################################################################
  1147. # Login
  1148. ##########################################################################
  1149. sub SMA_logon($$$) {
  1150. # Parameters: host - passcode
  1151. my ($host,$pass,$hash) = @_;
  1152. my $cmdheader = "534D4100000402A00000000100";
  1153. my $pktlength = "3A"; # length = 58 for logon command
  1154. my $esignature = "001060650EA0";
  1155. my $name = $hash->{NAME};
  1156. my $mysusyid = $hash->{HELPER}{MYSUSYID};
  1157. my $myserialnumber = $hash->{HELPER}{MYSERIALNUMBER};
  1158. my $pkt_ID = $hash->{HELPER}{PKT_ID};
  1159. my ($cmd, $timestmp, $myID, $target_ID, $spkt_ID, $cmd_ID);
  1160. my ($socket,$data,$size);
  1161. # Seriennummer und SuSyID des Ziel-WR setzen
  1162. my $default_target_susyid = $hash->{HELPER}{DEFAULT_TARGET_SUSYID};
  1163. my $default_target_serial = $hash->{HELPER}{DEFAULT_TARGET_SERIAL};
  1164. my $target_susyid = AttrVal($name, "target-susyid", $default_target_susyid);
  1165. my $target_serial = AttrVal($name, "target-serial", $default_target_serial);
  1166. #Encode the password
  1167. my $encpasswd = "888888888888888888888888"; # template for password
  1168. for my $index (0..length $pass ) # encode password
  1169. {
  1170. if ( (hex(substr($encpasswd,($index*2),2)) + ord(substr($pass,$index,1))) < 256 ) {
  1171. substr($encpasswd,($index*2),2) = substr(sprintf ("%lX", (hex(substr($encpasswd,($index*2),2)) + ord(substr($pass,$index,1)))),0,2);
  1172. } else {
  1173. substr($encpasswd,($index*2),2) = substr(sprintf ("%lX", (hex(substr($encpasswd,($index*2),2)) + ord(substr($pass,$index,1)))),1,2);
  1174. }
  1175. }
  1176. # Get current timestamp in epoch format (unix format)
  1177. $timestmp = ByteOrderLong(sprintf("%08X",int(time())));
  1178. # Define own ID and target ID and packet ID
  1179. $myID = ByteOrderShort(substr(sprintf("%04X",$mysusyid),0,4)) . ByteOrderLong(sprintf("%08X",$myserialnumber));
  1180. $target_ID = ByteOrderShort(substr(sprintf("%04X",$target_susyid),0,4)) . ByteOrderLong(sprintf("%08X",$target_serial));
  1181. $pkt_ID = 0x8001; # Reset to 0x8001
  1182. $spkt_ID = ByteOrderShort(sprintf("%04X",$pkt_ID));
  1183. #Logon command
  1184. $cmd_ID = "0C04FDFF" . "07000000" . "84030000"; # Logon command + User group "User" + (maybe) Timeout
  1185. #build final command to send
  1186. $cmd = $cmdheader . $pktlength . $esignature . $target_ID . "0001" . $myID . "0001" . "00000000" . $spkt_ID . $cmd_ID . $timestmp . "00000000" . $encpasswd . "00000000";
  1187. # flush after every write
  1188. $| = 1;
  1189. # Create Socket and check if successful
  1190. $socket = new IO::Socket::INET (PeerHost => $host, PeerPort => 9522, Proto => 'udp',); # open Socket
  1191. if (!$socket) {
  1192. # in case of error
  1193. Log3 $name, 1, "$name - ERROR - Can't open socket to inverter: $!";
  1194. return 0;
  1195. };
  1196. # Send Data
  1197. $data = pack("H*",$cmd);
  1198. $socket->send($data);
  1199. Log3 $name, 4, "$name - Send login to $host on Port 9522 with password $pass ";
  1200. Log3 $name, 5, "$name - Send: $cmd ";
  1201. # Receive Data and do a first check regarding length
  1202. eval {
  1203. $socket->recv($data, $hash->{HELPER}{MAXBYTES});
  1204. $size = length($data);
  1205. };
  1206. # check if something was received
  1207. if (defined $size) {
  1208. my $received = unpack("H*", $data);
  1209. Log3 $name, 5, "$name - Received: $received";
  1210. }
  1211. # Nothing received -> exit
  1212. if (not defined $size) {
  1213. Log3 $name, 1, "$name - Nothing received...";
  1214. # send: cmd_logout
  1215. $socket->close();
  1216. SMA_logout($hash,$host);
  1217. return 0;
  1218. } else {
  1219. # We have received something!
  1220. if ($size > 62) {
  1221. # Check all parameters of answer
  1222. my $r_susyid = unpack("v*", substr $data, 20, 2);
  1223. my $r_serial = unpack("V*", substr $data, 22, 4);
  1224. my $r_pkt_ID = unpack("v*", substr $data, 40, 2);
  1225. my $r_cmd_ID = unpack("V*", substr $data, 42, 4);
  1226. my $r_error = unpack("V*", substr $data, 36, 4);
  1227. if (($r_pkt_ID ne $pkt_ID) || ($r_cmd_ID ne 0xFFFD040D) || ($r_error ne 0)) {
  1228. # Response does not match the parameters we have sent, maybe different target
  1229. Log3 $name, 1, "$name - Inverter answer does not match our parameters.";
  1230. Log3 $name, 5, "$name - Request/Response: SusyID $mysusyid/$r_susyid, Serial $myserialnumber/$r_serial, Packet ID $hash->{HELPER}{PKT_ID}/$r_pkt_ID, Command 0xFFFD040D/$r_cmd_ID, Error $r_error";
  1231. # send: cmd_logout
  1232. $socket->close();
  1233. SMA_logout($hash,$host);
  1234. return 0;
  1235. }
  1236. } else {
  1237. Log3 $name, 1, "$name - Format of inverter response does not fit.";
  1238. # send: cmd_logout
  1239. $socket->close();
  1240. SMA_logout($hash,$host);
  1241. return 0;
  1242. }
  1243. }
  1244. # All seems ok, logged in!
  1245. my $inv_susyid = unpack("v*", substr $data, 28, 2);
  1246. my $inv_serial = unpack("V*", substr $data, 30, 4);
  1247. $socket->close();
  1248. if (AttrVal($name, "target-serial", undef)) {
  1249. return 0 unless($inv_serial eq $target_serial);
  1250. }
  1251. if (AttrVal($name, "target-susyid", undef)) {
  1252. return 0 unless($inv_susyid eq $target_susyid);
  1253. }
  1254. Log3 $name, 4, "$name - logged in to inverter serial: $inv_serial, susyid: $inv_susyid";
  1255. return 1;
  1256. }
  1257. ##########################################################################
  1258. # Logout
  1259. ##########################################################################
  1260. sub SMA_logout($$) {
  1261. # Parameters: host
  1262. my ($hash,$host) = @_;
  1263. my $name = $hash->{NAME};
  1264. my $cmdheader = "534D4100000402A00000000100";
  1265. my $pktlength = "22"; # length = 34 for logout command
  1266. my $esignature = "0010606508A0";
  1267. my $mysusyid = $hash->{HELPER}{MYSUSYID};
  1268. my $myserialnumber = $hash->{HELPER}{MYSERIALNUMBER};
  1269. my $pkt_ID = $hash->{HELPER}{PKT_ID};
  1270. my ($cmd, $myID, $target_ID, $spkt_ID, $cmd_ID);
  1271. my ($socket,$data,$size);
  1272. # Seriennummer und SuSyID des Ziel-WR setzen
  1273. my $default_target_susyid = $hash->{HELPER}{DEFAULT_TARGET_SUSYID};
  1274. my $default_target_serial = $hash->{HELPER}{DEFAULT_TARGET_SERIAL};
  1275. my $target_susyid = AttrVal($name, "target-susyid", $default_target_susyid);
  1276. my $target_serial = AttrVal($name, "target-serial", $default_target_serial);
  1277. # Define own ID and target ID and packet ID
  1278. $myID = ByteOrderShort(substr(sprintf("%04X",$mysusyid),0,4)) . ByteOrderLong(sprintf("%08X",$myserialnumber));
  1279. $target_ID = ByteOrderShort(substr(sprintf("%04X",$target_susyid),0,4)) . ByteOrderLong(sprintf("%08X",$target_serial));
  1280. # Increasing Packet ID
  1281. $hash->{HELPER}{PKT_ID} = $hash->{HELPER}{PKT_ID} + 1;
  1282. $spkt_ID = ByteOrderShort(sprintf("%04X",$hash->{HELPER}{PKT_ID}));
  1283. #Logout command
  1284. $cmd_ID = "0E01FDFF" . "FFFFFFFF"; # Logout command
  1285. #build final command to send
  1286. $cmd = $cmdheader . $pktlength . $esignature . $target_ID . "0003" . $myID . "0003" . "00000000" . $spkt_ID . $cmd_ID . "00000000";
  1287. # flush after every write
  1288. $| = 1;
  1289. # Create Socket and check if successful
  1290. $socket = new IO::Socket::INET (PeerHost => $host, PeerPort => 9522, Proto => 'udp',); # open Socket
  1291. if (!$socket) {
  1292. # in case of error
  1293. Log3 $name, 1, "$name - ERROR - Can't open socket to inverter: $!";
  1294. return 0;
  1295. };
  1296. # Send Data
  1297. $data = pack("H*",$cmd);
  1298. $socket->send($data);
  1299. Log3 $name, 4, "$name - Send logout to $host on Port 9522";
  1300. Log3 $name, 5, "$name - Send: $cmd ";
  1301. $target_serial = ($target_serial eq $default_target_serial)?"any inverter":$target_serial;
  1302. $target_susyid = ($target_susyid eq $default_target_susyid)?"any susyid":$target_susyid;
  1303. Log3 $name, 4, "$name - logged out now from inverter serial: $target_serial, susyid: $target_susyid";
  1304. $socket->close();
  1305. return 1;
  1306. }
  1307. ##########################################################################
  1308. # Hilfsroutinen
  1309. ##########################################################################
  1310. ##########################
  1311. sub ByteOrderShort($) {
  1312. my $input = $_[0];
  1313. my $output = "";
  1314. $output = substr($input, 2, 2) . substr($input, 0, 2);
  1315. return $output;
  1316. }
  1317. ##########################
  1318. sub ByteOrderLong($) {
  1319. my $input = $_[0];
  1320. my $output = "";
  1321. $output = substr($input, 6, 2) . substr($input, 4, 2) . substr($input, 2, 2) . substr($input, 0, 2);
  1322. return $output;
  1323. }
  1324. ##########################
  1325. sub StatusText($)
  1326. {
  1327. # Parameter is the code, return value is the Text or if not known then the code as string
  1328. my $code = $_[0];
  1329. if($code eq 51) { return (AttrVal("global", "language", "EN") eq "DE") ? "geschlossen" : "Closed"; }
  1330. if($code eq 311) { return (AttrVal("global", "language", "EN") eq "DE") ? "offen" : "Open"; }
  1331. if($code eq 16777213) { return (AttrVal("global", "language", "EN") eq "DE") ? "Information liegt nicht vor" : "No Information"; }
  1332. if($code eq 35) { return (AttrVal("global", "language", "EN") eq "DE") ? "Fehler" : "Fault"; }
  1333. if($code eq 303) { return "Off"; }
  1334. if($code eq 307) { return "Ok"; }
  1335. if($code eq 455) { return (AttrVal("global", "language", "EN") eq "DE") ? "Warnung" : "Warning"; }
  1336. return sprintf("%d", $code);
  1337. }
  1338. ##########################
  1339. # identify device type
  1340. sub devtype ($) {
  1341. my ($code) = @_;
  1342. unless (exists($SMAInverter_devtypes{$code})) { return $code;}
  1343. my $dev = $SMAInverter_devtypes{$code};
  1344. return ($dev);
  1345. }
  1346. ##########################
  1347. # identify device class
  1348. sub classtype ($) {
  1349. my ($code) = @_;
  1350. my $class;
  1351. if(AttrVal("global", "language", "EN") eq "DE") {
  1352. unless (exists($SMAInverter_classesDE{$code})) { return $code;}
  1353. $class = $SMAInverter_classesDE{$code};
  1354. } else {
  1355. unless (exists($SMAInverter_classesEN{$code})) { return $code;}
  1356. $class = $SMAInverter_classesEN{$code};
  1357. }
  1358. return ($class);
  1359. }
  1360. 1;
  1361. =pod
  1362. =item summary Integration of SMA Inverters over it's Speedwire (=Ethernet) Interface
  1363. =item summary_DE Integration von SMA Wechselrichtern über Speedwire (=Ethernet) Interface
  1364. =begin html
  1365. <a name="SMAInverter"></a>
  1366. <h3>SMAInverter</h3>
  1367. Module for the integration of a SMA Inverter over it's Speedwire (=Ethernet) Interface.<br>
  1368. Tested on Sunny Tripower 6000TL-20 and Sunny Island 4.4 with Speedwire/Webconnect Piggyback.
  1369. <br><br>
  1370. Questions and discussions about this module you can find in the FHEM-Forum link:<br>
  1371. <a href="https://forum.fhem.de/index.php/topic,56080.msg476525.html#msg476525">76_SMAInverter.pm - Abfrage von SMA Wechselrichter</a>.
  1372. <br><br>
  1373. <b>Requirements</b>
  1374. <br><br>
  1375. This module requires:
  1376. <ul>
  1377. <li>Perl Module: IO::Socket::INET (apt-get install libio-socket-multicast-perl) </li>
  1378. <li>Perl Module: Date::Time (apt-get install libdatetime-perl) </li>
  1379. <li>Perl Module: Time::HiRes</li>
  1380. <li>FHEM Module: 99_SUNRISE_EL.pm</li>
  1381. <li>FHEM Module: Blocking.pm</li>
  1382. </ul>
  1383. <br>
  1384. <br>
  1385. <b>Define</b>
  1386. <ul>
  1387. <code>define &lt;name&gt; SMAInverter &lt;pin&gt; &lt;hostname/ip&gt; </code><br>
  1388. <br>
  1389. <li>pin: User-Password of the SMA Inverter. Default is 0000. Can be changed by "Sunny Explorer" Windows Software</li>
  1390. <li>hostname/ip: Hostname or IP-Adress of the inverter (or it's speedwire piggyback module).</li>
  1391. <li>Port of the inverter is 9522 by default. Firewall has to allow connection on this port !</li>
  1392. </ul>
  1393. <b>Operation method</b>
  1394. <ul>
  1395. The module sends commands to the inverter and checks if they are supported by the inverter.<br>
  1396. In case of a positive answer the data is collected and displayed in the readings according to the detail-level. <br>
  1397. If more than one inverter is installed, set attributes "target-susyid" and "target-serial" with an appropriate value. <br><br>
  1398. The normal operation time of the inverter is supposed from sunrise to sunset. In that time period the inverter will be polled.
  1399. The time of sunrise and sunset will be calculated by functions of FHEM module 99_SUNRISE_EL.pm which is loaded automatically by default.
  1400. Therefore the global attribute "longitude" and "latitude" should be set to determine the position of the solar system
  1401. (see <a href="#SUNRISE_EL">Commandref SUNRISE_EL</a>). <br><br>
  1402. By the attribute "suppressSleep" the sleep mode between sunset and sunrise can be suppressed. Using attribute "offset" you may prefer the sunrise and
  1403. defer the sunset virtually. So the working period of the inverter will be extended. <br><br>
  1404. In operating mode "automatic" the inverter will be requested periodically corresponding the preset attribute "interval". The operating mode can be
  1405. switched to "manual" to realize the retrieval manually (e.g. to synchronize the requst with a SMA energy meter by notify). <br><br>
  1406. During inverter operating time the average energy production of the last 5, 10 and 15 minutes will be calculated and displayed in the readings
  1407. "avg_power_lastminutes_05", "avg_power_lastminutes_10" and "avg_power_lastminutes_15". <b>Note:</b> To permit a precise calculation, you should
  1408. also set the real request interval into the attribute "interval" although you would use the "manual" operation mode ! <br><br>
  1409. The retrieval of the inverter will be executed non-blocking. You can adjust the timeout value for this background process by attribute "timeout". <br>
  1410. </ul>
  1411. <b>Get</b>
  1412. <br>
  1413. <ul>
  1414. <code>get &lt;name&gt; data</code>
  1415. <br><br>
  1416. The request of the inverter will be executed. Those possibility is especifically created for the "manual" operation mode (see attribute "mode").
  1417. </ul>
  1418. <b>Attributes</b>
  1419. <ul>
  1420. <li><b>interval</b> : Queryintreval in seconds </li>
  1421. <li><b>detail-level</b> : "0" - Only Power and Energy / "1" - Including Voltage and Current / "2" - All values </li>
  1422. <li><b>disable</b> : 1 = the module is disabled </li>
  1423. <li><b>mode</b> : automatic = the inverter will be polled by preset interval, manual = query only by command "get &lt;name&gt; data" </li>
  1424. <li><b>offset</b> : time in seconds to prefer the sunrise respectively defer the sunset virtualy (0 ... 7200). You will be able to extend the working
  1425. period of the module. </li>
  1426. <li><b>SBFSpotComp</b> : 1 = the readings are created like SBFSpot-style </li>
  1427. <li><b>suppressSleep</b> : the sleep mode (after sunset, before sunrise) is deactivated and the inverter will be polled continuously. </li>
  1428. <li><b>showproctime</b> : shows processing time in background and wasted time to retrieve inverter data </li>
  1429. <li><b>target-susyid</b> : In case of a Multigate the target SUSyID can be defined. If more than one inverter is installed you have to
  1430. set the inverter-SUSyID to assign the inverter to the device definition.
  1431. Default is 0xFFFF, means any SUSyID</li>
  1432. <li><b>target-serial</b> : In case of a Multigate the target Serialnumber can be defined. If more than one inverter is installed you have to
  1433. set the inverter-Serialnumber to assign the inverter to the device definition.
  1434. Default is 0xFFFFFFFF, means any Serialnumber</li>
  1435. <li><b>timeout</b> : setup timeout of inverter data request (default 60s) </li>
  1436. </ul>
  1437. <b>Readings</b>
  1438. <ul>
  1439. <li><b>BAT_CYCLES / bat_cycles</b> : Battery recharge cycles </li>
  1440. <li><b>BAT_IDC / bat_idc</b> : Battery Current </li>
  1441. <li><b>BAT_TEMP / bat_temp</b> : Battery temperature </li>
  1442. <li><b>BAT_UDC / bat_udc</b> : Battery Voltage </li>
  1443. <li><b>ChargeStatus / chargestatus</b> : Battery Charge status </li>
  1444. <li><b>CLASS / device_class</b> : Inverter Class </li>
  1445. <li><b>PACMAX1 / pac_max_phase_1</b> : Nominal power in Ok Mode </li>
  1446. <li><b>PACMAX1_2 / pac_max_phase_1_2</b> : Maximum active power device (Some inverters like SB3300/SB1200) </li>
  1447. <li><b>PACMAX2 / pac_max_phase_2</b> : Nominal power in Warning Mode </li>
  1448. <li><b>PACMAX3 / pac_max_phase_3</b> : Nominal power in Fault Mode </li>
  1449. <li><b>Serialnumber / serial_number</b> : Inverter Serialnumber </li>
  1450. <li><b>SPOT_ETODAY / etoday</b> : Today yield </li>
  1451. <li><b>SPOT_ETOTAL / etotal</b> : Total yield </li>
  1452. <li><b>SPOT_FEEDTM / feed-in_time</b> : Feed-in time </li>
  1453. <li><b>SPOT_FREQ / grid_freq.</b> : Grid Frequency </li>
  1454. <li><b>SPOT_IAC1 / phase_1_iac</b> : Grid current phase L1 </li>
  1455. <li><b>SPOT_IAC2 / phase_2_iac</b> : Grid current phase L2 </li>
  1456. <li><b>SPOT_IAC3 / phase_3_iac</b> : Grid current phase L3 </li>
  1457. <li><b>SPOT_IDC1 / string_1_idc</b> : DC current input </li>
  1458. <li><b>SPOT_IDC2 / string_2_idc</b> : DC current input </li>
  1459. <li><b>SPOT_OPERTM / operation_time</b> : Operation Time </li>
  1460. <li><b>SPOT_PAC1 / phase_1_pac</b> : Power L1 </li>
  1461. <li><b>SPOT_PAC2 / phase_2_pac</b> : Power L2 </li>
  1462. <li><b>SPOT_PAC3 / phase_3_pac</b> : Power L3 </li>
  1463. <li><b>SPOT_PACTOT / total_pac</b> : Total Power </li>
  1464. <li><b>SPOT_PDC1 / string_1_pdc</b> : DC power input 1 </li>
  1465. <li><b>SPOT_PDC2 / string_2_pdc</b> : DC power input 2 </li>
  1466. <li><b>SPOT_UAC1 / phase_1_uac</b> : Grid voltage phase L1 </li>
  1467. <li><b>SPOT_UAC2 / phase_2_uac</b> : Grid voltage phase L2 </li>
  1468. <li><b>SPOT_UAC3 / phase_3_uac</b> : Grid voltage phase L3 </li>
  1469. <li><b>SPOT_UDC1 / string_1_udc</b> : DC voltage input </li>
  1470. <li><b>SPOT_UDC2 / string_2_udc</b> : DC voltage input </li>
  1471. <li><b>SUSyID / susyid</b> : Inverter SUSyID </li>
  1472. <li><b>INV_TEMP / device_temperature</b> : Inverter temperature </li>
  1473. <li><b>INV_TYPE / device_type</b> : Inverter Type </li>
  1474. <li><b>POWER_IN / power_in</b> : Battery Charging power </li>
  1475. <li><b>POWER_OUT / power_out</b> : Battery Discharging power </li>
  1476. <li><b>INV_GRIDRELAY / gridrelay_status</b> : Grid Relay/Contactor Status </li>
  1477. <li><b>INV_STATUS / device_status</b> : Inverter Status </li>
  1478. <li><b>opertime_start</b> : Begin of iverter operating time corresponding the calculated time of sunrise with consideration of the
  1479. attribute "offset" (if set) </li>
  1480. <li><b>opertime_stop</b> : End of iverter operating time corresponding the calculated time of sunrise with consideration of the
  1481. attribute "offset" (if set) </li>
  1482. <li><b>modulstate</b> : shows the current module state "normal" or "sleep" if the inverter won't be requested at the time. </li>
  1483. <li><b>avg_power_lastminutes_05</b> : average power of the last 5 minutes. </li>
  1484. <li><b>avg_power_lastminutes_10</b> : average power of the last 10 minutes. </li>
  1485. <li><b>avg_power_lastminutes_15</b> : average power of the last 15 minutes. </li>
  1486. <li><b>inverter_processing_time</b> : wasted time to retrieve the inverter data </li>
  1487. <li><b>background_processing_time</b> : total wasted time by background process (BlockingCall) </li>
  1488. </ul>
  1489. <br><br>
  1490. =end html
  1491. =begin html_DE
  1492. <a name="SMAInverter"></a>
  1493. <h3>SMAInverter</h3>
  1494. Modul zur Einbindung eines SMA Wechselrichters über Speedwire (Ethernet).<br>
  1495. Getestet mit Sunny Tripower 6000TL-20 und Sunny Island 4.4 mit Speedwire/Webconnect Piggyback.
  1496. <br><br>
  1497. Fragen und Diskussionen rund um dieses Modul finden sie im FHEM-Forum unter:<br>
  1498. <a href="https://forum.fhem.de/index.php/topic,56080.msg476525.html#msg476525">76_SMAInverter.pm - Abfrage von SMA Wechselrichter</a>.
  1499. <br><br>
  1500. <b>Voraussetzungen</b>
  1501. <br><br>
  1502. Dieses Modul benötigt:
  1503. <ul>
  1504. <li>Perl Modul: IO::Socket::INET (apt-get install libio-socket-multicast-perl) </li>
  1505. <li>Perl Modul: Datetime (apt-get install libdatetime-perl) </li>
  1506. <li>Perl Modul: Time::HiRes</li>
  1507. <li>FHEM Modul: 99_SUNRISE_EL.pm</li>
  1508. <li>FHEM Modul: Blocking.pm</li>
  1509. </ul>
  1510. <br>
  1511. <br>
  1512. <b>Define</b>
  1513. <ul>
  1514. <code>define &lt;name&gt; SMAInverter &lt;pin&gt; &lt;hostname/ip&gt;</code><br>
  1515. <br>
  1516. <li>pin: Benutzer-Passwort des SMA STP Wechselrichters. Default ist 0000. Kann über die Windows-Software "Sunny Explorer" geändert werden </li>
  1517. <li>hostname/ip: Hostname oder IP-Adresse des Wechselrichters (bzw. dessen Speedwire Moduls mit Ethernetanschluss) </li>
  1518. <li>Der Port des Wechselrichters ist 9522. Dieser Port muss in der Firewall freigeschaltet sein !</li>
  1519. </ul>
  1520. <b>Arbeitsweise</b>
  1521. <ul>
  1522. Das Modul schickt Befehle an den Wechselrichter und überprüft, ob diese unterstützt werden.<br>
  1523. Bei einer positiven Antwort werden die Daten gesammelt und je nach Detail-Level in den Readings dargestellt. <br>
  1524. Sind mehr als ein Wechselrichter installiert, sind die Attribute "target-susyid" und "target-serial" entsprechend zu setzen um die korrekte
  1525. Funktion zu gewährleisten. <br><br>
  1526. Die normale Betriebszeit des Wechselrichters wird in der Zeit vom Sonnenaufgang bis Sonnenuntergang angenommen. In dieser Periode werden die Wechselrichterdaten
  1527. abgefragt. Die Ermittlung von Sonnenaufgang / Sonnenuntergang wird über die Funktionen des FHEM-Moduls 99_SUNRISE_EL.pm vorgenommen. Zu diesem Zweck sollten die globalen
  1528. Attribute longitude und latitude gesetzt sein um den Standort der Anlage genau zu ermitteln. (siehe <a href="#SUNRISE_EL">Commandref SUNRISE_EL</a>) <br><br>
  1529. Mit dem Attribut "suppressSleep" kann der Schlafmodus unterdrückt werden. Das Attribut "offset" dient dazu den effektiven Zeitpunkt des Sonnenaufgangs / Sonnenuntergangs
  1530. um den Betrag "offset" vorzuziehen (Sonnenaufgang) bzw. zu verzögern (Sonnenuntergang) und somit die Abfrageperiode des Wechselrichters zu verlängern. <br><br>
  1531. Im Betriebsmodus "automatic" wird der Wechselrichter entsprechend des eingestellten Attributs "interval" abgefragt. Der Betriebsmodus kann in "manual"
  1532. umgestellt werden um eine manuelle Abfrage zu realisieren (z.B. Synchronisierung mit einem SMA Energymeter über ein Notify). <br><br>
  1533. Während der Betriebszeit des Wechselrichters wird die durchschnittliche Energieerzeugung der letzten 5, 10, 15 Minuten berechnet und in den Readings
  1534. "avg_power_lastminutes_05", "avg_power_lastminutes_10" und "avg_power_lastminutes_15" ausgegeben. <b>Hinweis:</b> Um eine korrekte Berechnung zu
  1535. ermöglichen, sollte auch im Betriebsmodus "manual" das tatsächliche Abfrageinterval im Attribute "interval" hinterlegt werden ! <br><br>
  1536. Die Abfrage des Wechselrichters wird non-blocking ausgeführt. Der Timeoutwert für diesen Hintergrundprozess kann mit dem Attribut "timeout" eingestellt werden. <br>
  1537. </ul>
  1538. <b>Get</b>
  1539. <br>
  1540. <ul>
  1541. <code>get &lt;name&gt; data</code>
  1542. <br><br>
  1543. Die Datenabfrage des Wechselrichters wird ausgeführt. Diese Möglichkeit ist speziell für den Betriebsmodus "manual" vorgesehen (siehe Attribut "mode").
  1544. </ul>
  1545. <b>Attribute</b>
  1546. <ul>
  1547. <li><b>interval</b> : Abfrageinterval in Sekunden </li>
  1548. <li><b>detail-level</b> : "0" - Nur Leistung und Energie / "1" - zusätzlich Strom und Spannung / "2" - Alle Werte </li>
  1549. <li><b>disable</b> : 1 = das Modul ist disabled </li>
  1550. <li><b>mode</b> : automatic = die Wechselrichterwerte werden im eingestellten Interval abgefragt, manual = Abfrage nur mit "get &lt;name&gt; data" </li>
  1551. <li><b>offset</b> : Zeit in Sekunden um die der Sonnenaufgang vorgezogen bzw. Sonnenuntergang verzögert wird (0 ... 7200). Dadurch wird die
  1552. effektive Aktivzeit des Moduls erweitert. </li>
  1553. <li><b>suppressSleep</b> : der Schlafmodus (nach Sonnenuntergang, vor Sonnenaufgang) wird ausgeschaltet und der WR abgefragt. </li>
  1554. <li><b>showproctime</b> : zeigt die für den Hintergrundprozess und die Abfrage des Wechselrichter verbrauchte Zeit. </li>
  1555. <li><b>SBFSpotComp</b> : 1 = die Readings werden kompatibel zu SBFSpot-Ausgaben erzeugt </li>
  1556. <li><b>target-susyid</b> : Im Falle eines Multigate kann die Ziel-SUSyID definiert werden. Ist mehr als ein Wechselrichter installiert,
  1557. muß die Wechselreichter-SUSyID gesetzt werden um den Wechselrichter der Device-Definition eindeutig zuzuweisen.
  1558. Default ist 0xFFFF (=keine Einschränkung)</li>
  1559. <li><b>target-serial</b> : Im Falle eines Multigate kann die Ziel-Seriennummer definiert werden. Ist mehr als ein Wechselrichter installiert,
  1560. muß die Wechselreichter-Seriennummer gesetzt werden um den Wechselrichter der Device-Definition eindeutig zuzuweisen.
  1561. Default ist 0xFFFFFFFF (=keine Einschränkung)</li>
  1562. <li><b>timeout</b> : Einstellung des timeout für die Wechselrichterabfrage (default 60s) </li>
  1563. </ul>
  1564. <b>Readings</b>
  1565. <ul>
  1566. <li><b>BAT_CYCLES / bat_cycles</b> : Akku Ladezyklen </li>
  1567. <li><b>BAT_IDC / bat_idc</b> : Akku Strom </li>
  1568. <li><b>BAT_TEMP / bat_temp</b> : Akku Temperatur </li>
  1569. <li><b>BAT_UDC / bat_udc</b> : Akku Spannung </li>
  1570. <li><b>ChargeStatus / chargestatus</b> : Akku Ladestand </li>
  1571. <li><b>CLASS / device_class</b> : Wechselrichter Klasse </li>
  1572. <li><b>PACMAX1 / pac_max_phase_1</b> : Nominelle Leistung in Ok Mode </li>
  1573. <li><b>PACMAX1_2 / pac_max_phase_1_2</b> : Maximale Leistung (für einige Wechselrichtertypen) </li>
  1574. <li><b>PACMAX2 / pac_max_phase_2</b> : Nominelle Leistung in Warning Mode </li>
  1575. <li><b>PACMAX3 / pac_max_phase_3</b> : Nominelle Leistung in Fault Mode </li>
  1576. <li><b>Serialnumber / serial_number</b> : Wechselrichter Seriennummer </li>
  1577. <li><b>SPOT_ETODAY / etoday</b> : Energie heute</li>
  1578. <li><b>SPOT_ETOTAL / etotal</b> : Energie Insgesamt </li>
  1579. <li><b>SPOT_FEEDTM / feed-in_time</b> : Einspeise-Stunden </li>
  1580. <li><b>SPOT_FREQ / grid_freq.</b> : Netz Frequenz </li>
  1581. <li><b>SPOT_IAC1 / phase_1_iac</b> : Netz Strom phase L1 </li>
  1582. <li><b>SPOT_IAC2 / phase_2_iac</b> : Netz Strom phase L2 </li>
  1583. <li><b>SPOT_IAC3 / phase_3_iac</b> : Netz Strom phase L3 </li>
  1584. <li><b>SPOT_IDC1 / string_1_idc</b> : DC Strom Eingang 1 </li>
  1585. <li><b>SPOT_IDC2 / string_2_idc</b> : DC Strom Eingang 2 </li>
  1586. <li><b>SPOT_OPERTM / operation_time</b> : Betriebsstunden </li>
  1587. <li><b>SPOT_PAC1 / phase_1_pac</b> : Leistung L1 </li>
  1588. <li><b>SPOT_PAC2 / phase_2_pac</b> : Leistung L2 </li>
  1589. <li><b>SPOT_PAC3 / phase_3_pac</b> : Leistung L3 </li>
  1590. <li><b>SPOT_PACTOT / total_pac</b> : Gesamtleistung </li>
  1591. <li><b>SPOT_PDC1 / string_1_pdc</b> : DC Leistung Eingang 1 </li>
  1592. <li><b>SPOT_PDC2 / string_2_pdc</b> : DC Leistung Eingang 2 </li>
  1593. <li><b>SPOT_UAC1 / phase_1_uac</b> : Netz Spannung phase L1 </li>
  1594. <li><b>SPOT_UAC2 / phase_2_uac</b> : Netz Spannung phase L2 </li>
  1595. <li><b>SPOT_UAC3 / phase_3_uac</b> : Netz Spannung phase L3 </li>
  1596. <li><b>SPOT_UDC1 / string_1_udc</b> : DC Spannung Eingang 1 </li>
  1597. <li><b>SPOT_UDC2 / string_2_udc</b> : DC Spannung Eingang 2 </li>
  1598. <li><b>SUSyID / susyid</b> : Wechselrichter SUSyID </li>
  1599. <li><b>INV_TEMP / device_temperature</b> : Wechselrichter Temperatur </li>
  1600. <li><b>INV_TYPE / device_type</b> : Wechselrichter Typ </li>
  1601. <li><b>POWER_IN / power_in</b> : Akku Ladeleistung </li>
  1602. <li><b>POWER_OUT / power_out</b> : Akku Entladeleistung </li>
  1603. <li><b>INV_GRIDRELAY / gridrelay_status</b> : Netz Relais Status </li>
  1604. <li><b>INV_STATUS / device_status</b> : Wechselrichter Status </li>
  1605. <li><b>opertime_start</b> : Beginn Aktivzeit des Wechselrichters entsprechend des ermittelten Sonnenaufgangs mit Berücksichtigung des
  1606. Attributs "offset" (wenn gesetzt) </li>
  1607. <li><b>opertime_stop</b> : Ende Aktivzeit des Wechselrichters entsprechend des ermittelten Sonnenuntergangs mit Berücksichtigung des
  1608. Attributs "offset" (wenn gesetzt) </li>
  1609. <li><b>modulstate</b> : zeigt den aktuellen Modulstatus "normal" oder "sleep" falls der Wechselrichter nicht abgefragt wird. </li>
  1610. <li><b>avg_power_lastminutes_05</b> : durchschnittlich erzeugte Leistung der letzten 5 Minuten. </li>
  1611. <li><b>avg_power_lastminutes_10</b> : durchschnittlich erzeugte Leistung der letzten 10 Minuten. </li>
  1612. <li><b>avg_power_lastminutes_15</b> : durchschnittlich erzeugte Leistung der letzten 15 Minuten. </li>
  1613. <li><b>inverter_processing_time</b> : verbrauchte Zeit um den Wechelrichter abzufragen. </li>
  1614. <li><b>background_processing_time</b> : gesamte durch den Hintergrundprozess (BlockingCall) verbrauchte Zeit. </li>
  1615. </ul>
  1616. <br><br>
  1617. =end html_DE
  1618. =cut