14_SD_WS.pm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. ##############################################
  2. # $Id: 14_SD_WS.pm 13215 2017-01-23 20:09:44Z Sidey $
  3. #
  4. # The purpose of this module is to support serval
  5. # weather sensors which use various protocol
  6. # Sidey79 & Ralf9 2016
  7. #
  8. package main;
  9. use strict;
  10. use warnings;
  11. #use Data::Dumper;
  12. sub SD_WS_Initialize($)
  13. {
  14. my ($hash) = @_;
  15. $hash->{Match} = '^W\d+x{0,1}#.*';
  16. $hash->{DefFn} = "SD_WS_Define";
  17. $hash->{UndefFn} = "SD_WS_Undef";
  18. $hash->{ParseFn} = "SD_WS_Parse";
  19. $hash->{AttrFn} = "SD_WS_Attr";
  20. $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 showtime:1,0 " .
  21. "$readingFnAttributes ";
  22. $hash->{AutoCreate} =
  23. {
  24. "SD_WS37_TH.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"},
  25. "SD_WS50_SM.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"},
  26. "BresserTemeo.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"}
  27. };
  28. }
  29. #############################
  30. sub SD_WS_Define($$)
  31. {
  32. my ($hash, $def) = @_;
  33. my @a = split("[ \t][ \t]*", $def);
  34. return "wrong syntax: define <name> SD_WS <code> ".int(@a) if(int(@a) < 3 );
  35. $hash->{CODE} = $a[2];
  36. $hash->{lastMSG} = "";
  37. $hash->{bitMSG} = "";
  38. $modules{SD_WS}{defptr}{$a[2]} = $hash;
  39. $hash->{STATE} = "Defined";
  40. my $name= $hash->{NAME};
  41. return undef;
  42. }
  43. #####################################
  44. sub SD_WS_Undef($$)
  45. {
  46. my ($hash, $name) = @_;
  47. delete($modules{SD_WS}{defptr}{$hash->{CODE}})
  48. if(defined($hash->{CODE}) && defined($modules{SD_WS}{defptr}{$hash->{CODE}}));
  49. return undef;
  50. }
  51. ###################################
  52. sub SD_WS_Parse($$)
  53. {
  54. my ($iohash, $msg) = @_;
  55. #my $rawData = substr($msg, 2);
  56. my $name = $iohash->{NAME};
  57. my ($protocol,$rawData) = split("#",$msg);
  58. $protocol=~ s/^[WP](\d+)/$1/; # extract protocol
  59. my $dummyreturnvalue= "Unknown, please report";
  60. my $hlen = length($rawData);
  61. my $blen = $hlen * 4;
  62. my $bitData = unpack("B$blen", pack("H$hlen", $rawData));
  63. my $bitData2;
  64. my $model; # wenn im elsif Abschnitt definiert, dann wird der Sensor per AutoCreate angelegt
  65. my $SensorTyp;
  66. my $id;
  67. my $bat;
  68. my $channel;
  69. my $rawTemp;
  70. my $temp;
  71. my $hum;
  72. my $trend;
  73. my %decodingSubs = (
  74. 50 => # Protocol 50
  75. # FF550545FF9E
  76. # FF550541FF9A
  77. # AABCDDEEFFGG
  78. # A = Preamble, always FF
  79. # B = TX type, always 5
  80. # C = Address (5/6/7) > low 2 bits = 1/2/3
  81. # D = Soil moisture 05%
  82. # E = temperature
  83. # F = security code, always F
  84. # G = Checksum 55+05+45+FF=19E CRC value = 9E
  85. { # subs to decode this
  86. sensortype => 'XT300',
  87. model => 'SD_WS_50_SM',
  88. prematch => sub {my $msg = shift; return 1 if ($msg =~ /^FF5[0-9A-F]{5}FF[0-9A-F]{2}/); }, # prematch
  89. crcok => sub {my $msg = shift; return 1 if ((hex(substr($msg,2,2))+hex(substr($msg,4,2))+hex(substr($msg,6,2))+hex(substr($msg,8,2))&0xFF) == (hex(substr($msg,10,2))) ); }, # crc
  90. id => sub {my $msg = shift; return (hex(substr($msg,2,2)) &0x03 ); }, #id
  91. #temp => sub {my $msg = shift; return (sprintf('%x',((hex(substr($msg,6,2)) <<4)/2/10))); }, #temp
  92. #temphex => sub {my $msg = shift; return sprintf("%04X",((hex(substr($msg,6,2)))<<4)/2); }, #temp
  93. temp => sub {my $msg = shift; return ((hex(substr($msg,6,2)))-40) }, #temp
  94. #hum => sub {my $msg = shift; return (printf('%02x',hex(substr($msg,4,2)))); }, #hum
  95. hum => sub {my $msg = shift; return hex(substr($msg,4,2)); }, #hum
  96. channel => sub {my (undef,$bitData) = @_; return ( SD_WS_binaryToNumber($bitData,12,15)&0x03 ); }, #channel
  97. },
  98. 33 =>
  99. {
  100. sensortype => 's014/TFA 30.3200/TCM/Conrad',
  101. model => 'SD_WS_33_TH',
  102. prematch => sub {my $msg = shift; return 1 if ($msg =~ /^[0-9A-F]{10,11}/); }, # prematch
  103. crcok => sub {return SD_WS_binaryToNumber($bitData,36,39); }, # crc
  104. id => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,0,9); }, # id
  105. # sendmode => sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,10,11) eq "1" ? "manual" : "auto"; }
  106. temp => sub {my (undef,$bitData) = @_; return (((SD_WS_binaryToNumber($bitData,22,25)*256 + SD_WS_binaryToNumber($bitData,18,21)*16 + SD_WS_binaryToNumber($bitData,14,17)) *10 -12200) /18)/10; }, #temp
  107. hum => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,30,33)*16 + SD_WS_binaryToNumber($bitData,26,29)); }, #hum
  108. channel => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,12,13)+1 ); }, #channel
  109. bat => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,34) eq "1" ? "ok" : "critical");},
  110. # sync => sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,35,35) eq "1" ? "true" : "false");},
  111. }
  112. );
  113. Log3 $name, 4, "SD_WS_Parse: Protocol: $protocol, rawData: $rawData";
  114. if ($protocol eq "37") # Bresser 7009994
  115. {
  116. # 0 7 8 9 10 12 22 25 31
  117. # 01011010 0 0 01 01100001110 10 0111101 11001010
  118. # ID B? T Kan Temp ?? Hum Pruefsumme?
  119. # MU;P0=729;P1=-736;P2=483;P3=-251;P4=238;P5=-491;D=010101012323452323454523454545234523234545234523232345454545232345454545452323232345232340;CP=4;
  120. $model = "SD_WS37_TH";
  121. $SensorTyp = "Bresser 7009994";
  122. $id = SD_WS_binaryToNumber($bitData,0,7);
  123. #$bat = int(substr($bitData,8,1)) eq "1" ? "ok" : "low";
  124. $channel = SD_WS_binaryToNumber($bitData,10,11);
  125. $rawTemp = SD_WS_binaryToNumber($bitData,12,22);
  126. $hum = SD_WS_binaryToNumber($bitData,25,31);
  127. $id = sprintf('%02X', $id); # wandeln nach hex
  128. $temp = ($rawTemp - 609.93) / 9.014;
  129. $temp = sprintf("%.1f", $temp);
  130. if ($hum < 10 || $hum > 99 || $temp < -30 || $temp > 70) {
  131. return "";
  132. }
  133. $bitData2 = substr($bitData,0,8) . ' ' . substr($bitData,8,4) . ' ' . substr($bitData,12,11);
  134. $bitData2 = $bitData2 . ' ' . substr($bitData,23,2) . ' ' . substr($bitData,25,7) . ' ' . substr($bitData,32,8);
  135. Log3 $iohash, 4, "$name converted to bits: " . $bitData2;
  136. Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) sensor id=$id, channel=$channel, rawTemp=$rawTemp, temp=$temp, hum=$hum";
  137. }
  138. elsif ($protocol eq "44" || $protocol eq "44x") # BresserTemeo
  139. {
  140. # 0 4 8 12 20 24 28 32 36 40 44 52 56 60
  141. # 0101 0111 1001 00010101 0010 0100 0001 1010 1000 0110 11101010 1101 1011 1110 110110010
  142. # hhhh hhhh ?bcc iiiiiiii sttt tttt tttt xxxx xxxx ?BCC IIIIIIII Syyy yyyy yyyy
  143. # - h humidity / -x checksum
  144. # - t temp / -y checksum
  145. # - c Channel / C checksum
  146. # - i 8 bit random id (aendert sich beim Batterie- und Kanalwechsel) / - I checksum
  147. # - b battery indicator (0=>OK, 1=>LOW) / - B checksum
  148. # - s Test/Sync (0=>Normal, 1=>Test-Button pressed) / - S checksum
  149. $model= "BresserTemeo";
  150. $SensorTyp = "BresserTemeo";
  151. #my $binvalue = unpack("B*" ,pack("H*", $rawData));
  152. my $binvalue = $bitData;
  153. if (length($binvalue) != 72) {
  154. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: length error (72 bits expected)!!!";
  155. return "";
  156. }
  157. # Check what Humidity Prefix (*sigh* Bresser!!!)
  158. if ($protocol eq "44")
  159. {
  160. $binvalue = "0".$binvalue;
  161. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Humidity <= 79 Flag";
  162. }
  163. else
  164. {
  165. $binvalue = "1".$binvalue;
  166. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Humidity > 79 Flag";
  167. }
  168. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: new bin $binvalue";
  169. my $checksumOkay = 1;
  170. my $hum1Dec = SD_WS_binaryToNumber($binvalue, 0, 3);
  171. my $hum2Dec = SD_WS_binaryToNumber($binvalue, 4, 7);
  172. my $checkHum1 = SD_WS_binaryToNumber($binvalue, 32, 35) ^ 0b1111;
  173. my $checkHum2 = SD_WS_binaryToNumber($binvalue, 36, 39) ^ 0b1111;
  174. if ($checkHum1 != $hum1Dec || $checkHum2 != $hum2Dec)
  175. {
  176. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Humidity";
  177. }
  178. else
  179. {
  180. $hum = $hum1Dec.$hum2Dec;
  181. if ($hum < 1 || $hum > 100)
  182. {
  183. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Humidity Error. Humidity=$hum";
  184. return "";
  185. }
  186. }
  187. my $temp1Dec = SD_WS_binaryToNumber($binvalue, 21, 23);
  188. my $temp2Dec = SD_WS_binaryToNumber($binvalue, 24, 27);
  189. my $temp3Dec = SD_WS_binaryToNumber($binvalue, 28, 31);
  190. my $checkTemp1 = SD_WS_binaryToNumber($binvalue, 53, 55) ^ 0b111;
  191. my $checkTemp2 = SD_WS_binaryToNumber($binvalue, 56, 59) ^ 0b1111;
  192. my $checkTemp3 = SD_WS_binaryToNumber($binvalue, 60, 63) ^ 0b1111;
  193. $temp = $temp1Dec.$temp2Dec.".".$temp3Dec;
  194. if ($checkTemp1 != $temp1Dec || $checkTemp2 != $temp2Dec || $checkTemp3 != $temp3Dec)
  195. {
  196. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Temperature";
  197. $checksumOkay = 0;
  198. }
  199. if ($temp > 60)
  200. {
  201. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Temperature Error. temp=$temp";
  202. return "";
  203. }
  204. $bat = substr($binvalue,9,1);
  205. my $checkBat = substr($binvalue,41,1) ^ 0b1;
  206. if ($bat != $checkBat)
  207. {
  208. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Bat";
  209. $bat = undef;
  210. }
  211. else
  212. {
  213. $bat = ($bat == 0) ? "ok" : "low";
  214. }
  215. $channel = SD_WS_binaryToNumber($binvalue, 10, 11);
  216. my $checkChannel = SD_WS_binaryToNumber($binvalue, 42, 43) ^ 0b11;
  217. $id = SD_WS_binaryToNumber($binvalue, 12, 19);
  218. my $checkId = SD_WS_binaryToNumber($binvalue, 44, 51) ^ 0b11111111;
  219. if ($channel != $checkChannel || $id != $checkId)
  220. {
  221. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Channel or Id";
  222. $checksumOkay = 0;
  223. }
  224. if ($checksumOkay == 0)
  225. {
  226. Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error!!! These Values seem incorrect: temp=$temp, channel=$channel, id=$id";
  227. return "";
  228. }
  229. $id = sprintf('%02X', $id); # wandeln nach hex
  230. Log3 $iohash, 4, "$name SD_WS_Parse: model=$model, temp=$temp, hum=$hum, channel=$channel, id=$id, bat=$bat";
  231. }
  232. elsif (defined($decodingSubs{$protocol})) # durch den hash decodieren
  233. {
  234. $SensorTyp=$decodingSubs{$protocol}{sensortype};
  235. return "" && Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) prematch error" if (!$decodingSubs{$protocol}{prematch}->( $rawData ));
  236. return "" && Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) crc error" if (!$decodingSubs{$protocol}{crcok}->( $rawData ));
  237. $id=$decodingSubs{$protocol}{id}->( $rawData,$bitData );
  238. #my $temphex=$decodingSubs{$protocol}{temphex}->( $rawData,$bitData );
  239. $temp=$decodingSubs{$protocol}{temp}->( $rawData,$bitData );
  240. $hum=$decodingSubs{$protocol}{hum}->( $rawData,$bitData );
  241. $channel=$decodingSubs{$protocol}{channel}->( $rawData,$bitData );
  242. $model = $decodingSubs{$protocol}{model};
  243. $bat = $decodingSubs{$protocol}{bat};
  244. Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) sensor id=$id, channel=$channel, temp=$temp, hum=$hum";
  245. }
  246. else {
  247. Log3 $iohash, 4, "SD_WS_Parse: unknown message, please report. converted to bits: $bitData";
  248. return undef;
  249. }
  250. if (!defined($model)) {
  251. return undef;
  252. }
  253. my $deviceCode;
  254. my $longids = AttrVal($iohash->{NAME},'longids',0);
  255. if (($longids ne "0") && ($longids eq "1" || $longids eq "ALL" || (",$longids," =~ m/,$model,/)))
  256. {
  257. $deviceCode = $model . '_' . $id . $channel;
  258. Log3 $iohash,4, "$name using longid: $longids model: $model";
  259. } else {
  260. $deviceCode = $model . "_" . $channel;
  261. }
  262. #print Dumper($modules{SD_WS}{defptr});
  263. my $def = $modules{SD_WS}{defptr}{$iohash->{NAME} . "." . $deviceCode};
  264. $def = $modules{SD_WS}{defptr}{$deviceCode} if(!$def);
  265. if(!$def) {
  266. Log3 $iohash, 1, 'SD_WS: UNDEFINED sensor ' . $model . ' detected, code ' . $deviceCode;
  267. return "UNDEFINED $deviceCode SD_WS $deviceCode";
  268. }
  269. #Log3 $iohash, 3, 'SD_WS: ' . $def->{NAME} . ' ' . $id;
  270. my $hash = $def;
  271. $name = $hash->{NAME};
  272. return "" if(IsIgnored($name));
  273. Log3 $name, 4, "SD_WS: $name ($rawData)";
  274. if (!defined(AttrVal($hash->{NAME},"event-min-interval",undef)))
  275. {
  276. my $minsecs = AttrVal($iohash->{NAME},'minsecs',0);
  277. if($hash->{lastReceive} && (time() - $hash->{lastReceive} < $minsecs)) {
  278. Log3 $hash, 4, "$deviceCode Dropped due to short time. minsecs=$minsecs";
  279. return "";
  280. }
  281. }
  282. $hash->{lastReceive} = time();
  283. $hash->{lastMSG} = $rawData;
  284. if (defined($bitData2)) {
  285. $hash->{bitMSG} = $bitData2;
  286. } else {
  287. $hash->{bitMSG} = $bitData;
  288. }
  289. my $state = "T: $temp" . ($hum > 0 ? " H: $hum":"");
  290. readingsBeginUpdate($hash);
  291. readingsBulkUpdate($hash, "state", $state);
  292. readingsBulkUpdate($hash, "temperature", $temp) if (defined($temp));
  293. readingsBulkUpdate($hash, "humidity", $hum) if (defined($hum) && $hum > 0);
  294. readingsBulkUpdate($hash, "battery", $bat) if (defined($bat));
  295. readingsBulkUpdate($hash, "channel", $channel) if (defined($channel));
  296. readingsBulkUpdate($hash, "trend", $trend) if (defined($trend));
  297. readingsEndUpdate($hash, 1); # Notify is done by Dispatch
  298. return $name;
  299. }
  300. sub SD_WS_Attr(@)
  301. {
  302. my @a = @_;
  303. # Make possible to use the same code for different logical devices when they
  304. # are received through different physical devices.
  305. return if($a[0] ne "set" || $a[2] ne "IODev");
  306. my $hash = $defs{$a[1]};
  307. my $iohash = $defs{$a[3]};
  308. my $cde = $hash->{CODE};
  309. delete($modules{SD_WS}{defptr}{$cde});
  310. $modules{SD_WS}{defptr}{$iohash->{NAME} . "." . $cde} = $hash;
  311. return undef;
  312. }
  313. sub SD_WS_binaryToNumber
  314. {
  315. my $binstr=shift;
  316. my $fbit=shift;
  317. my $lbit=$fbit;
  318. $lbit=shift if @_;
  319. return oct("0b".substr($binstr,$fbit,($lbit-$fbit)+1));
  320. }
  321. 1;
  322. =pod
  323. =item summary Supports various weather stations
  324. =item summary_DE Unterst&uumltzt verschiedene Funk Wetterstationen
  325. =begin html
  326. <a name="SD_WS"></a>
  327. <h3>Weather Sensors various protocols</h3>
  328. <ul>
  329. The SD_WS module interprets temperature sensor messages received by a Device like CUL, CUN, SIGNALduino etc.<br>
  330. <br>
  331. <b>Known models:</b>
  332. <ul>
  333. <li>Bresser 7009994</li>
  334. <li>Opus XT300</li>
  335. </ul>
  336. <br>
  337. New received device are add in fhem with autocreate.
  338. <br><br>
  339. <a name="SD_WS_Define"></a>
  340. <b>Define</b>
  341. <ul>The received devices created automatically.<br>
  342. The ID of the defice is the cannel or, if the longid attribute is specified, it is a combination of channel and some random generated bits at powering the sensor and the channel.<br>
  343. If you want to use more sensors, than channels available, you can use the longid option to differentiate them.
  344. </ul>
  345. <br>
  346. <a name="SD_WS Events"></a>
  347. <b>Generated readings:</b>
  348. <br>Some devices may not support all readings, so they will not be presented<br>
  349. <ul>
  350. <li>State (T: H:)</li>
  351. <li>temperature (&deg;C)</li>
  352. <li>humidity: (The humidity (1-100 if available)</li>
  353. <li>battery: (low or ok)</li>
  354. <li>channel: (The Channelnumber (number if)</li>
  355. </ul>
  356. <br>
  357. <b>Attributes</b>
  358. <ul>
  359. <li><a href="#do_not_notify">do_not_notify</a></li>
  360. <li><a href="#ignore">ignore</a></li>
  361. <li><a href="#showtime">showtime</a></li>
  362. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  363. </ul>
  364. <a name="SD_WS_Set"></a>
  365. <b>Set</b> <ul>N/A</ul><br>
  366. <a name="SD_WS_Parse"></a>
  367. <b>Set</b> <ul>N/A</ul><br>
  368. </ul>
  369. =end html
  370. =begin html_DE
  371. <a name="SD_WS"></a>
  372. <h3>SD_WS</h3>
  373. <ul>
  374. Das SD_WS Modul verarbeitet von einem IO Ger&aumlt (CUL, CUN, SIGNALDuino, etc.) empfangene Nachrichten von Temperatur-Sensoren.<br>
  375. <br>
  376. <b>Unterst&uumltzte Modelle:</b>
  377. <ul>
  378. <li>Bresser 7009994</li>
  379. <li>Opus XT300</li>
  380. <li>BresserTemeo</li>
  381. </ul>
  382. <br>
  383. Neu empfangene Sensoren werden in FHEM per autocreate angelegt.
  384. <br><br>
  385. <a name="SD_WS_Define"></a>
  386. <b>Define</b>
  387. <ul>Die empfangenen Sensoren werden automatisch angelegt.<br>
  388. Die ID der angelgten Sensoren ist entweder der Kanal des Sensors, oder wenn das Attribut longid gesetzt ist, dann wird die ID aus dem Kanal und einer Reihe von Bits erzeugt, welche der Sensor beim Einschalten zuf&aumlllig vergibt.<br>
  389. </ul>
  390. <br>
  391. <a name="SD_WS Events"></a>
  392. <b>Generierte Readings:</b>
  393. <ul>
  394. <li>State (T: H:)</li>
  395. <li>temperature (&deg;C)</li>
  396. <li>humidity: (Luftfeuchte (1-100)</li>
  397. <li>battery: (low oder ok)</li>
  398. <li>channel: (Der Sensor Kanal)</li>
  399. </ul>
  400. <br>
  401. <b>Attribute</b>
  402. <ul>
  403. <li><a href="#do_not_notify">do_not_notify</a></li>
  404. <li><a href="#ignore">ignore</a></li>
  405. <li><a href="#showtime">showtime</a></li>
  406. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  407. </ul>
  408. <a name="SD_WS_Set"></a>
  409. <b>Set</b> <ul>N/A</ul><br>
  410. <a name="SD_WS_Parse"></a>
  411. <b>Set</b> <ul>N/A</ul><br>
  412. </ul>
  413. =end html_DE
  414. =cut