| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725 |
- # $Id: 34_ESPEasy.pm 14936 2017-08-21 04:12:53Z dev0 $
- ################################################################################
- #
- # 34_ESPEasy.pm is a FHEM Perl module to control ESP8266 /w ESPEasy
- #
- # Copyright 2017 by dev0
- # FHEM forum: https://forum.fhem.de/index.php?action=profile;u=7465
- #
- # This file is part of FHEM.
- #
- # Fhem is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 2 of the License, or
- # (at your option) any later version.
- #
- # Fhem is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with fhem. If not, see <http://www.gnu.org/licenses/>.
- #
- ################################################################################
- package main;
- use strict;
- use warnings;
- use Data::Dumper;
- use MIME::Base64;
- use TcpServerUtils;
- use HttpUtils;
- use Color;
- # ------------------------------------------------------------------------------
- # global/default values
- # ------------------------------------------------------------------------------
- my $module_version = "1.33"; # Version of this module
- my $minEEBuild = 128; # informational
- my $minJsonVersion = 1.02; # checked in received data
- my $d_Interval = 300; # interval
- my $d_httpReqTimeout = 10; # timeout http req
- my $d_colorpickerCTww = 2000; # color temp for ww (kelvin)
- my $d_colorpickerCTcw = 6000; # color temp for cw (kelvin)
- my $d_maxHttpSessions = 3; # concurrent connects to a single esp
- my $d_maxQueueSize = 250; # max queue size,
- my $d_resendFailedCmd = 0; # resend failed http requests by default?
- my $d_displayTextEncode = 1; # urlEncode Text for Displays
- my $d_displayTextWidth = 0; # display width, 0 => disable formating
- my $d_bridgePort = 8383;
- # IP ranges that are allowed to connect to ESPEasy without attr allowedIPs set.
- # defined as regexp beause it's quicker than check against IP ranges...
- my $d_allowedIPs = "192.168.0.0/16,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,"
- . "fe80::/10,fc00::/7,::1";
- my $d_localIPs = "^(127|192.168|172.(1[6-9]|2[0-9]|3[01])|10|169.254)\\.|"
- . "^(f(e[89ab]|[cd])|::1)";
- # ------------------------------------------------------------------------------
- # "setCmds" => "min. number of parameters"
- # ------------------------------------------------------------------------------
- my %ESPEasy_setCmds = (
- "gpio" => "2",
- "pwm" => "2",
- "pwmfade" => "3",
- "pulse" => "3",
- "longpulse" => "3",
- "servo" => "3",
- "lcd" => "3",
- "lcdcmd" => "1",
- "mcpgpio" => "2",
- "oled" => "3",
- "oledcmd" => "1",
- "pcapwm" => "2",
- "pcfgpio" => "2",
- "pcfpulse" => "3",
- "pcflongpulse" => "3",
- "irsend" => "3", #_P035_IRTX.ino
- "status" => "2",
- "raw" => "1",
- "reboot" => "0",
- "erase" => "0",
- "reset" => "0",
- "statusrequest" => "0",
- "clearreadings" => "0",
- "help" => "1",
- "lights" => "1",
- "dots" => "1",
- "tone" => "3",
- "rtttl" => "1",
- "dmx" => "1",
- "motorshieldcmd" => "5",
- "candle" => "0", # params are splited by ":" not " "
- "neopixel" => "4",
- "neopixelall" => "3",
- "neopixelline" => "5",
- "oledframedcmd" => "1",
- "serialsend" => "1", #_P020_Ser2Net.ino
- "buzzer" => "0",
- "inputswitchstate" => "0", # _0P001_Switch.ini
- "event" => "1",
- "nfx" => "1"
- );
- my @ESPEasy_maplightCmds =
- qw( on off toggle pct ct rgb dim line one all fade colorfade rainbow kitt
- comet theatre scan dualscan twinkle twinklefade sparkle wipe fire stop
- fadetime fadedelay count speed bgcolor
- );
- # ------------------------------------------------------------------------------
- # "setCmds" => "syntax", ESPEasy_paramPos() will parse for some <.*> positions
- # ------------------------------------------------------------------------------
- my %ESPEasy_setCmdsUsage = (
- "gpio" => "gpio <pin> <0|1|off|on>",
- "pwm" => "pwm <pin> <level>",
- "pulse" => "pulse <pin> <0|1|off|on> <duration>",
- "longpulse" => "longpulse <pin> <0|1|off|on> <duration>",
- "servo" => "servo <servoNo> <pin> <position>",
- "lcd" => "lcd <row> <col> <text>",
- "lcdcmd" => "lcdcmd <on|off|clear>",
- "mcpgpio" => "mcpgpio <pin> <0|1|off|on>",
- "oled" => "oled <row> <col> <text>",
- "oledcmd" => "oledcmd <on|off|clear>",
- "pcapwm" => "pcapwm <pin> <Level>",
- "pcfgpio" => "pcfgpio <pin> <0|1|off|on>",
- "pcfpulse" => "pcfpulse <pin> <0|1|off|on> <duration>", #missing docu
- "pcflongpulse" => "pcflongPulse <pin> <0|1|off|on> <duration>",#missing docu
- "status" => "status <device> <pin>",
- "pwmfade" => "pwmfade <pin> <target> <duration>", #Forum #55728.msg530220
- "irsend" => "irsend <RAW> <B32 raw code> <frequenz> <pulse length> <blank length> "
- . "| irsend <NEC|JVC|RC5|RC6|SAMSUNG|SONY|PANASONIC> <code> <bits>",
- "raw" => "raw <esp_comannd> <...>",
- "reboot" => "reboot",
- "erase" => "erase",
- "reset" => "reset",
- "statusrequest" => "statusRequest",
- "clearreadings" => "clearReadings",
- "help" => "help <".join("|", sort keys %ESPEasy_setCmds).">",
- "lights" => "lights <rgb|ct|pct|on|off|toggle> [color] [fading time] [pct]",
- "dots" => "dots <params>",
- "tone" => "tone <pin> <freq> <duration>",
- "rtttl" => "rtttl <RTTTL>",
- "dmx" => "dmx <ON|OFF|LOG|value|channel=value[,value][...]>",
- "motorshieldcmd" => "motorshieldcmd <DCMotor|Stepper> <Motornumber> "
- . "<Forward|Backward|Release> <Speed|Steps> "
- . "<SINGLE|DOUBLE|INTERLEAVE|MICROSTEP>",
- "candle" => "CANDLE:<FlameType>:<Color>:<Brightness>",
- "neopixel" => "NeoPixel <led_nr> <red 0-255> <green 0-255> <blue 0-255>",
- "neopixelall" => "NeoPixelAll <red 0-255> <green 0-255> <blue 0-255>",
- "neopixelline" => "NeoPixelLine <start_led_nr> <end_led_nr> <red 0-255> ".
- "<green 0-255> <blue 0-255>",
- "oledframedcmd" => "oledframedcmd <on|off>",
- "serialsend" => "serialsend <string>",
- "buzzer" => "buzzer",
- "inputswitchstate" => "inputswitchstate",
- "event" => "event <string>", #Forum #73291
- "nfx" => "nfx <off|on|dim|line|one|all|rgb|fade|colorfade|rainbow|kitt|comet|theatre|scan|dualscan|twinkle|twinklefade|sparkle|wipe|fire|stop> <parameter>", #Forum #73949
- #Lights
- "rgb" => "rgb <rrggbb> [fadetime] [delay +/-ms]",
- "pct" => "pct <pct> [fadetime]",
- "ct" => "ct <ct> [fadetime] [pct bri]",
- "on" => "on [fadetime] [delay +/-ms]",
- "off" => "off [fadetime] [delay +/-ms]",
- "toggle" => "toggle [fadetime]",
- # more light related commands - Forum #73949
- "dim" => "dim <value 0-255>",
- "line" => "line <startpixel> <endpixel> <rrggbb>",
- "one" => "one <pixel> <rrggbb>",
- "all" => "all <rrggbb> [fadetime] [delay +/-ms]",
- "fade" => "fade <rrggbb> [fadetime ms] [delay +/-ms]",
- "colorfade" => "colorfade <rrggbb_start> <rrggbb_end> [startpixel] [endpixel]",
- "rainbow" => "rainbow [speed +/- 0-50]",
- "kitt" => "kitt <rrggbb> [speed 0-50]",
- "comet" => "comet <rrggbb> [speed +/- 0-50]",
- "theatre" => "theatre <rrggbb> [rrggbb background] [speed +/- 0-50]",
- "scan" => "scan <rrggbb> [rrggbb background] [speed 0-50]",
- "dualscan" => "dualscan <rrggbb> [rrggbb background] [speed 0-50]",
- "twinkle" => "twinkle <rrggbb> [rrggbb background] [speed 0-50]",
- "twinklefade" => "twinklefade <rrggbb> [number of pixels] [speed 0-50]",
- "sparkle" => "sparkle <rrggbb> [rrggbb background] [speed 0-50]",
- "wipe" => "wipe <rrggbb> [rrggbb dot] [speed +/- 0-50]",
- "fire" => "fire [fps] [brightness 0-255] [cooling 20-100] [sparking 50-200]",
- "stop" => "stop",
- "fadetime" => "fadetime <value in ms>",
- "fadedelay" => "fadedelay <value in +/-ms>",
- "count" => "count <value>",
- "speed" => "speed <value 0-50>",
- "bgcolor" => "bgcolor <rrggbb>"
- );
- # ------------------------------------------------------------------------------
- # Bridge "setCmds" => "min. number of parameters"
- # ------------------------------------------------------------------------------
- my %ESPEasy_setBridgeCmds = (
- "user" => "0",
- "pass" => "0",
- "clearqueue" => "0",
- "help" => "1"
- );
- # ------------------------------------------------------------------------------
- # "setBridgeCmds" => "syntax", ESPEasy_paramPos() parse for some <.*> positions
- # ------------------------------------------------------------------------------
- my %ESPEasy_setBridgeCmdsUsage = (
- "user" => "user <username>",
- "pass" => "pass <password>",
- "clearqueue" => "clearqueue",
- "help" => "help <".join("|", sort keys %ESPEasy_setBridgeCmds).">"
- );
- # ------------------------------------------------------------------------------
- # pin names can be used instead of gpio numbers.
- # ------------------------------------------------------------------------------
- my %ESPEasy_pinMap = (
- "D0" => 16,
- "D1" => 5,
- "D2" => 4,
- "D3" => 0,
- "D4" => 2,
- "D5" => 14,
- "D6" => 12,
- "D7" => 13,
- "D8" => 15,
- "D9" => 3,
- "D10" => 1,
- "RX" => 3,
- "TX" => 1,
- "SD2" => 9,
- "SD3" => 10
- );
- # ------------------------------------------------------------------------------
- # build id
- # ------------------------------------------------------------------------------
- my %ESPEasy_build_id = (
- "1" => { "type" => "ESP Easy", "ver" => "STD" },
- "17" => { "type" => "ESP Easy Mega", "ver" => "STD" },
- "33" => { "type" => "ESP Easy 32", "ver" => "STD" },
- "65" => { "type" => "ARDUINO Easy", "ver" => "STD" },
- "81" => { "type" => "NANO Easy", "ver" => "STD" }
- );
- # ------------------------------------------------------------------------------
- #grep ^sub 34_ESPEasy.pm | awk '{print $1" "$2";"}'
- # ------------------------------------------------------------------------------
- sub ESPEasy_Initialize($)
- {
- my ($hash) = @_;
- #common
- $hash->{DefFn} = "ESPEasy_Define";
- $hash->{GetFn} = "ESPEasy_Get";
- $hash->{SetFn} = "ESPEasy_Set";
- $hash->{AttrFn} = "ESPEasy_Attr";
- $hash->{UndefFn} = "ESPEasy_Undef";
- $hash->{ShutdownFn} = "ESPEasy_Shutdown";
- $hash->{DeleteFn} = "ESPEasy_Delete";
- $hash->{RenameFn} = "ESPEasy_Rename";
- $hash->{NotifyFn} = "ESPEasy_Notify";
- #provider
- $hash->{ReadFn} = "ESPEasy_Read"; #ESP http request will be parsed here
- $hash->{WriteFn} = "ESPEasy_Write"; #called from logical module's IOWrite
- $hash->{Clients} = ":ESPEasy:"; #used by dispatch,$hash->{TYPE} of receiver
- my %matchList = ( "1:ESPEasy" => ".*" );
- $hash->{MatchList} = \%matchList;
- #consumer
- $hash->{ParseFn} = "ESPEasy_dispatchParse";
- $hash->{Match} = ".+";
- $hash->{AttrList} = "allowedIPs "
- ."authentication:1,0 "
- ."autocreate:1,0 "
- ."autosave:1,0 "
- ."colorpicker:RGB,HSV,HSVp "
- ."deniedIPs "
- ."disable:1,0 "
- ."displayTextEncode:1,0 "
- ."displayTextWidth "
- ."do_not_notify:0,1 "
- ."httpReqTimeout "
- ."IODev "
- ."Interval "
- ."adjustValue "
- ."parseCmdResponse "
- ."pollGPIOs "
- ."presenceCheck:1,0 "
- ."readingPrefixGPIO "
- ."readingSuffixGPIOState "
- ."readingSwitchText:1,0 "
- ."setState:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,100 "
- ."combineDevices "
- ."rgbGPIOs "
- ."maxQueueSize:10,25,50,100,250,500,1000,2500,5000,10000,25000,50000,100000 "
- ."maxHttpSessions:0,1,2,3,4,5,6,7,8,9 "
- ."resendFailedCmd:0,1 "
- ."mapLightCmds "
- ."colorpickerCTww "
- ."colorpickerCTcw "
- # ."wwcwGPIOs "
- # ."wwcwMaxBri:0,1 "
- # ."ctWW_reducedRange "
- # ."ctCW_reducedRange "
- .$readingFnAttributes;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_Define($$) # only called when defined, not on reload.
- {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- my $usg = "\nUse 'define <name> ESPEasy <bridge> <PORT>".
- "\nUse 'define <name> ESPEasy <ip|fqdn> <PORT> <IODev> <IDENT>";
- return "Wrong syntax: $usg" if(int(@a) < 3);
- my $name = $a[0];
- my $type = $a[1];
- my $host = $a[2];
- my $port;
- $port = $a[3] if defined $a[3];
- $port = 8383 if !defined $port && $host eq "bridge";
- my $iodev = $a[4] if defined $a[4];
- my $ident = $a[5] if defined $a[5];
- my $ipv = $port =~ m/^IPV6:/ ? 6 : 4;
-
- return "ERROR: only 1 ESPEasy bridge can be defined!"
- if($host eq "bridge" && $modules{ESPEasy}{defptr}{BRIDGE}{$ipv});
- return "ERROR: missing arguments for subtype device: $usg"
- if ($host ne "bridge" && !(defined $a[4]) && !(defined $a[5]));
- return "ERROR: too much arguments for a bridge: $usg"
- if ($host eq "bridge" && defined $a[4]);
- (ESPEasy_isIPv4($host) || ESPEasy_isFqdn($host) || $host eq "bridge")
- ? $hash->{HOST} = $host
- : return "ERROR: invalid IPv4 address, fqdn or keyword bridge: '$host'";
- # check fhem.pl version (internalTimer modifications are required)
- # https://forum.fhem.de/index.php/topic,55728.msg497094.html#msg497094
- AttrVal('global','version','') =~ m/^fhem.pl:(\d+)\/.*$/;
- return "ERROR: fhem.pl is too old to use $type module."
- ." Version 11000/2016-03-05 is required at least."
- if (not(defined $1) || $1 < 11000);
- $hash->{PORT} = $port if defined $port;
- $hash->{IDENT} = $ident if defined $ident;
- $hash->{VERSION} = $module_version;
- $hash->{NOTIFYDEV} = "global";
- #--- BRIDGE -------------------------------------------------
- if ($hash->{HOST} eq "bridge") {
- $hash->{SUBTYPE} = "bridge";
- $hash->{IPV} = $ipv;
- $modules{ESPEasy}{defptr}{BRIDGE}{$ipv} = $hash;
- Log3 $hash->{NAME}, 2, "$type $name: Opening bridge port tcp/$port (v$module_version)";
- ESPEasy_tcpServerOpen($hash);
- if ($init_done && !defined($hash->{OLDDEF})) {
- #if (not defined getKeyValue($type."_".$name."-firstrun")) {
- CommandAttr(undef,"$name room $type");
- CommandAttr(undef,"$name group $type Bridge");
- CommandAttr(undef,"$name authentication 0");
- CommandAttr(undef,"$name combineDevices 0");
- setKeyValue($type."_".$name."-firstrun","done");
- }
- $hash->{".bau"} = getKeyValue($type."_".$name."-user");
- $hash->{".bap"} = getKeyValue($type."_".$name."-pass");
- # only informational
- $hash->{MAX_HTTP_SESSIONS} = $d_maxHttpSessions;
- $hash->{MAX_QUEUE_SIZE} = $d_maxQueueSize;
- # Check OS IPv6 support
- if ($ipv == 6) {
- use constant HAS_AF_INET6 => defined eval { Socket::AF_INET6() };
- Log3 $name, 2, "$type $name: WARNING: Your system seems to have no IPv6 support."
- if !HAS_AF_INET6;
- }
- # Check that GIT repository is not activated
- ESPEasy_removeGit($hash);
- }
- #--- DEVICE -------------------------------------------------
- else {
- $hash->{INTERVAL} = $d_Interval;
- $hash->{SUBTYPE} = "device";
- AssignIoPort($hash,$iodev) if !defined $hash->{IODev};
- InternalTimer(gettimeofday()+5+rand(5), "ESPEasy_statusRequest", $hash);
- readingsSingleUpdate($hash, 'state', 'opened',1);
- my $io = (defined($hash->{IODev}{NAME})) ? $hash->{IODev}{NAME} : "none";
- Log3 $hash->{NAME}, 4, "$type $name: Opened for $ident $host:$port using bridge $io";
- }
- ESPEasy_loadRequiredModules($hash);
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_Get($@)
- {
- my ($hash, @a) = @_;
- return "argument is missing" if(int(@a) != 2);
- my $reading = $a[1];
- my $ret;
- if ($reading =~ m/^pinmap$/i && $hash->{SUBTYPE} eq "device") {
- $ret .= "\nName => GPIO\n";
- $ret .= "------------\n";
- foreach (sort keys %ESPEasy_pinMap) {
- $ret .= $_." " x (5-length $_ ) ."=> $ESPEasy_pinMap{$_}\n";
- }
- return $ret;
- } elsif ($reading =~ m/^user$/i && $hash->{SUBTYPE} eq "bridge") {
- return $hash->{".bau"} ? $hash->{".bau"} : "username is not defined, yet.";
- } elsif ($reading =~ m/^pass$/i && $hash->{SUBTYPE} eq "bridge") {
- return $hash->{".bap"} ? $hash->{".bap"} : "password is not defined, yet.";
- } elsif (lc $reading =~ m/^queueSize$/i && $hash->{SUBTYPE} eq "bridge") {
- foreach (keys %{ $hash->{helper}{queue} }) {
- $ret .= "$_:".scalar @{$hash->{helper}{queue}{"$_"}}." ";
- }
- return $ret;
- } elsif (exists($hash->{READINGS}{$reading})) {
- return defined($hash->{READINGS}{$reading})
- ? $hash->{READINGS}{$reading}{VAL}
- : "reading $reading exists but has no value defined";
- } else {
- $ret = "unknown argument $reading, choose one of";
- foreach my $reading (sort keys %{$hash->{READINGS}}) {
- $ret .= " $reading:noArg";
- }
- return ($hash->{SUBTYPE} eq "bridge")
- ? $ret . " user:noArg pass:noArg queueSize:noArg"
- : $ret . " pinMap:noArg";
- }
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_Set($$@)
- {
- my ($hash, $name, $cmd, @params) = @_;
- my ($type,$self) = ($hash->{TYPE},ESPEasy_whoami());
- $cmd = lc($cmd) if $cmd;
- return if (IsDisabled $name);
- Log3 $name, 3, "$type: set $name $cmd ".join(" ",@params)
- if $cmd !~ m/^(\?|user|pass)$/;
- # ----- BRDIGE ----------------------------------------------
- if ($hash->{SUBTYPE} eq "bridge") {
- # are there all required argumets?
- if($ESPEasy_setBridgeCmds{$cmd}
- && scalar @params < $ESPEasy_setBridgeCmds{$cmd}) {
- Log3 $name, 2, "$type $name: Missing argument: 'set $name $cmd "
- .join(" ",@params)."'";
- return "Missing argument: $cmd needs at least "
- ."$ESPEasy_setBridgeCmds{$cmd} parameter(s)\n"
- ."Usage: 'set $name $ESPEasy_setBridgeCmdsUsage{$cmd}'";
- }
- # handle unknown cmds
- if(!exists $ESPEasy_setBridgeCmds{$cmd}) {
- my @cList = sort keys %ESPEasy_setBridgeCmds;
- my $clist = join(" ", @cList);
- my $hlist = join(",", @cList);
- $clist =~ s/help/help:$hlist/; # add all cmds as params to help cmd
- return "Unknown argument $cmd, choose one of ". $clist;
- }
- if ($cmd eq "help") {
- my $usage = $ESPEasy_setBridgeCmdsUsage{$params[0]};
- $usage =~ s/Note:/\nNote:/g;
- return "Usage: set $name $usage";
- }
- elsif ($cmd =~ m/^clearqueue$/i) {
- delete $hash->{helper}{queue};
- Log3 $name, 3, "$type $name: Queues erased.";
- return undef;
- }
- elsif ($cmd =~ m/^user|pass$/ ) {
- setKeyValue($hash->{TYPE}."_".$hash->{NAME}."-".$cmd,$params[0]);
- $cmd eq "user" ? $hash->{".bau"} = $params[0] : $hash->{".bap"} = $params[0];
- }
- }
- # ----- DEVICE ----------------------------------------------
- else {
- # cmds are included in hash
- ESPEasy_adjustSetCmds($hash);
- # are there all required argumets?
- if($ESPEasy_setCmds{$cmd} && scalar @params < $ESPEasy_setCmds{$cmd}) {
- Log3 $name, 2, "$type $name: Missing argument: "
- ."'set $name $cmd ".join(" ",@params)."'";
- return "Missing argument: $cmd needs at least $ESPEasy_setCmds{$cmd} ".
- "parameter(s)\n"."Usage: 'set $name $ESPEasy_setCmdsUsage{$cmd}'";
- }
- #Lights Plugin
- my $lightCmds = join("|",@ESPEasy_maplightCmds);
- if (defined AttrVal($name,"mapLightCmds",undef) && $cmd =~ m/^$lightCmds$/i) {
- unshift @params, $cmd;
- $cmd = lc AttrVal($name,"mapLightCmds","");
- }
- else {
- # enable ct|pct commands if attr wwcwGPIOs is set
- if (AttrVal($name,"wwcwGPIOs",0) && $cmd =~ m/^(ct|pct)$/i) {
- my $ret = ESPEasy_setCT($hash,$cmd,@params);
- return $ret if ($ret);
- }
- # enable rgb commands if attr rgbGPIOs is set
- if (AttrVal($name,"rgbGPIOs",0) && $cmd =~ m/^(rgb|on|off|toggle)$/i) {
- my $ret = ESPEasy_setRGB($hash,$cmd,@params);
- return $ret if ($ret);
- }
- } #else
- # handle unknown cmds
- if (!exists $ESPEasy_setCmds{$cmd}) {
- my @cList = sort keys %ESPEasy_setCmds;
- my $clist = join(" ", @cList);
- my $hlist = join(",", @cList);
- foreach (@cList) {$clist =~ s/ $_/ $_:noArg/ if $ESPEasy_setCmds{$_} == 0}
- # expand ct
- my $ct = "ct:colorpicker,CT,"
- .AttrVal($name,"ctWW_reducedRange",AttrVal($name,"colorpickerCTww",$d_colorpickerCTww))
- .",10,"
- .AttrVal($name,"ctCW_reducedRange",AttrVal($name,"colorpickerCTcw",$d_colorpickerCTcw));
- $clist =~ s/ct /$ct /;
- # expand colorpicker
- my $cp = AttrVal($name,"colorpicker","HSVp");
- my $cpRepl = "rgb|bgcolor|fade|all|comet|twinkle|twinklefade|sparkle|theatre|scan|dualscan|wipe";
- $clist =~ s/(($cpRepl)\s)/$2:colorpicker,$cp /g; # add colorPicker if rgb cmd is available
- # expand pct colorpicker widget (-100)
- my $pct = "pct:colorpicker,BRI,0,1,100";
- $clist =~ s/pct /$pct /;
- # expand dim colorpicker widget (-255)
- my $dim = "dim:colorpicker,BRI,0,1,255";
- $clist =~ s/dim /$dim /;
- # slider for some values...
- my $fadetime = "fadetime:slider,0,100,10000";
- $clist =~ s/fadetime /$fadetime /;
- my $fadedelay = "fadedelay:slider,-5000,10,5000";
- $clist =~ s/fadedelay /$fadedelay /;
- my $speed = "speed:slider,-50,1,50";
- $clist =~ s/speed /$speed /;
- my $count = "count:slider,1,1,50";
- $clist =~ s/count /$count /;
- my $rainbow = "rainbow:slider,-10,1,10";
- $clist =~ s/rainbow/$rainbow/;
- # expand help
- $clist =~ s/help/help:$hlist/;
- Log3 $name, 2, "$type $name: Unknown set command $cmd" if $cmd ne "?";
- return "Unknown argument $cmd, choose one of ". $clist;
- }
- # urlEncode <text> parameter
- @params = ESPEasy_urlEncodeDisplayText($hash,$cmd,@params);
- # pin mapping (eg. D8 -> 15)
- my $pp = ESPEasy_paramPos($cmd,'<pin>');
- if ($pp && $params[$pp-1] =~ m/^[a-zA-Z]/) {
- Log3 $name, 5, "$type $name: Pin mapping ". uc $params[$pp-1] .
- " => $ESPEasy_pinMap{uc $params[$pp-1]}";
- $params[$pp-1] = $ESPEasy_pinMap{uc $params[$pp-1]};
- }
- # onOff mapping (on/off -> 1/0)
- $pp = ESPEasy_paramPos($cmd,'<0|1|off|on>');
- if ($pp && not($params[$pp-1] =~ m/^(0|1)$/)) {
- my $state;
- if ($params[$pp-1] =~ m/^off$/i) {
- $state = 0;
- }
- elsif ($params[$pp-1] =~ m/^on$/i) {
- $state = 1;
- }
- else {
- Log3 $name, 2, "$type $name: $cmd ".join(" ",@params)." => unknown argument: '$params[$pp-1]'";
- return undef;
- }
- Log3 $name, 5, "$type $name: onOff mapping ". $params[$pp-1]." => $state";
- $params[$pp-1] = $state;
- }
- if ($cmd eq "help") {
- my $usage = $ESPEasy_setCmdsUsage{$params[0]};
- $usage =~ s/Note:/\nNote:/g;
- return "Usage: set $name $usage";
- }
- if ($cmd eq "statusrequest") {
- ESPEasy_statusRequest($hash);
- return undef;
- }
- if ($cmd eq "clearreadings") {
- ESPEasy_clearReadings($hash);
- return undef;
- }
- Log3 $name, 5, "$type $name: IOWrite(\$defs{$hash->{NAME}}, $hash->{HOST}, $hash->{PORT}, ".
- "$hash->{IDENT}, $cmd, ".join(",",@params).")";
- Log3 $name, 2, "$type $name: Device seems to be in sleep mode, sending command nevertheless."
- if (defined $hash->{SLEEP} && $hash->{SLEEP} ne "0");
- my $parseCmd = ESPEasy_isParseCmd($hash,$cmd); # should response be parsed and dispatched
- IOWrite($hash, $hash->{HOST}, $hash->{PORT}, $hash->{IDENT}, $parseCmd, $cmd, @params);
- } # DEVICE
- return undef
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_Read($) {
- my ($hash) = @_; #hash of temporary child instance
- my $name = $hash->{NAME};
- my $ipv = $hash->{IPV} ? $hash->{IPV} : ($hash->{PEER} =~ m/:/ ? 6 : 4);
- my $bhash = $modules{ESPEasy}{defptr}{BRIDGE}{$ipv}; #hash of original instance
- my $bname = $bhash->{NAME};
- my $btype = $bhash->{TYPE};
- # Levering new TcpServerUtils security feature.
- #$attr{$name}{allowfrom} = ".*" if !$attr{$name}{allowfrom};
- # Accept and create a child
- if( $hash->{SERVERSOCKET} ) {
- my $aRet = ESPEasy_TcpServer_Accept($hash,"ESPEasy");
- return;
- }
- # use received IP instead of configured one (NAT/PAT could have modified)
- my $peer = $hash->{PEER};
- # Read 1024 byte of data
- my $buf;
- my $ret = sysread($hash->{CD}, $buf, 1024);
- # If there is an error in connection return
- if( !defined($ret ) || $ret <= 0 ) {
- CommandDelete( undef, $hash->{NAME} );
- return;
- }
- #$bhash->{SESSIONS} = scalar devspec2array("TYPE=$btype:FILTER=TEMPORARY=1")-1;
- # Check attr disabled
- return if (IsDisabled $bname);
- # Check allowed IPs
- if ( !( ESPEasy_isPeerAllowed($peer,AttrVal($bname,"allowedIPs", $d_allowedIPs)) &&
- !ESPEasy_isPeerAllowed($peer,AttrVal($bname,"deniedIPs",0)) ) ) {
- Log3 $bname, 2, "$btype $name: Peer address rejected";
- return;
- }
- Log3 $bname, 4, "$btype $name: Peer address accepted";
- my @data = split( '\R\R', $buf );
- my $header = ESPEasy_header2Hash($data[0]);
- # mask password in authorization header with ****
- my $logHeader = { %$header };
- # public IPs
- if (!defined $logHeader->{Authorization} && $peer !~ m/$d_localIPs/) {
- Log3 $bname, 2, "$btype $name: No basic auth set while using public IP "
- . "address. $peer rejected.";
- return;
- }
- $logHeader->{Authorization} =~ s/Basic\s.*\s/Basic ***** / if defined $logHeader->{Authorization};
- # Dump logHeader
- Log3 $bname, 5, "$btype $name: Received header: ".ESPEasy_dumpSingleLine($logHeader)
- if (defined $logHeader);
- # Dump content
- Log3 $bname, 5, "$btype $name: Received content: $data[1]" if defined $data[1];
- # Check content length if defined
- if (defined $header->{'Content-Length'}
- && $header->{'Content-Length'} != length($data[1])) {
- Log3 $bname, 2, "$btype $name: Invalid content length ".
- "($header->{'Content-Length'} != ".length($data[1]).")";
- Log3 $bname, 2, "$btype $name: Received content: $data[1]"
- if defined $data[1];
- ESPEasy_sendHttpClose($hash,"400 Bad Request","");
- return;
- }
- # check authorization
- if (!defined ESPEasy_isAuthenticated($hash,$header->{Authorization})) {
- ESPEasy_sendHttpClose($hash,"401 Unauthorized","");
- return;
- }
- # No error occurred, send http respose OK to ESP
- ESPEasy_sendHttpClose($hash,"200 OK",""); #if !grep(/"sleep":1/, $data[1]);
- # JSON received...
- my $json;
- if (defined $data[1] && $data[1] =~ m/"module":"ESPEasy"/) {
- # perl module JSON not installed
- if ( !$bhash->{helper}{pm}{JSON} ) {
- Log3 $bname, 2, "$btype $bname: Perl module 'JSON' is not installed. Can't process received data from $peer.";
- return;
- }
- # use encode_utf8 if available else replace any disturbing chars
- $bhash->{helper}{pm}{Encode}
- ? ( eval { $json = decode_json( encode_utf8($data[1]) ); 1; } )
- : ( eval { $json = decode_json( $data[1] =~ s/[^\x20-\x7E]/_/gr ); 1; } );
- if ($@) {
- Log3 $bname, 2, "$btype $name: WARNING: Invalid JSON received. "
- . "Check your ESP configuration ($peer).\n$@";
- return;
- }
- # check that ESPEasy software is new enough
- return if ESPEasy_checkVersion($bhash,$peer,$json->{data}{ESP}{build},$json->{version});
- # should never happen, but who knows what some JSON module versions do...
- $json->{data}{ESP}{name} = "" if !defined $json->{data}{ESP}{name};
- $json->{data}{SENSOR}{0}{deviceName} = "" if !defined $json->{data}{SENSOR}{0}{deviceName};
- # remove illegal chars from ESP name for further processing and assign to new var
- (my $espName = $json->{data}{ESP}{name}) =~ s/[^A-Za-z\d_\.]/_/g;
- (my $espDevName = $json->{data}{SENSOR}{0}{deviceName}) =~ s/[^A-Za-z\d_\.]/_/g;
- # check that 'ESP name' or 'device name' is set
- if ($espName eq "" && $espDevName eq "") {
- Log3 $bname, 2, "$btype $name: WARNIING 'ESP name' and 'device name' "
- ."missing ($peer). Check your ESP config. Skip processing data.";
- Log3 $bname, 2, "$btype $name: Data: $data[1]";
- return;
- }
- my $ident = ESPEasy_isCombineDevices($peer,$espName,AttrVal($bname,"combineDevices",0))
- ? $espName ne "" ? $espName : $peer
- : $espName.($espName ne "" && $espDevName ne "" ? "_" : "").$espDevName;
- # push internals in @values
- my @values;
- my @intVals = qw(unit sleep build build_git build_notes version node_type_id);
- foreach my $intVal (@intVals) {
- next if !defined $json->{data}{ESP}{$intVal} || $json->{data}{ESP}{$intVal} eq "";
- push(@values,"i||".$intVal."||".$json->{data}{ESP}{$intVal}."||0");
- }
- # push sensor value in @values
- foreach my $vKey (keys %{$json->{data}{SENSOR}}) {
- if(ref $json->{data}{SENSOR}{$vKey} eq ref {}
- && exists $json->{data}{SENSOR}{$vKey}{value}) {
- # remove illegal chars
- $json->{data}{SENSOR}{$vKey}{valueName} =~ s/[^A-Za-z\d_\.\-\/]/_/g;
- my $dmsg = "r||".$json->{data}{SENSOR}{$vKey}{valueName}
- ."||".$json->{data}{SENSOR}{$vKey}{value}
- ."||".$json->{data}{SENSOR}{$vKey}{type};
- if ($dmsg =~ m/(\|\|\|\|)|(\|\|$)/) { #detect an empty value
- Log3 $bname, 2, "$btype $name: WARNING: value name or value is "
- ."missing ($peer). Skip processing this value.";
- Log3 $bname, 2, "$btype $name: Data: $data[1]";
- next; #skip further processing for this value only
- }
- push(@values,$dmsg);
- }
- }
- ESPEasy_dispatch($hash,$ident,$peer,@values);
- } #$data[1] =~ m/"module":"ESPEasy"/
- else {
- Log3 $bname, 2, "$btype $name: WARNING: Wrong controller configured or "
- ."ESPEasy Version is too old.";
- Log3 $bname, 2, "$btype $name: WARNING: ESPEasy version R"
- .$minEEBuild." or later required.";
- }
- # session will not be close immediately if ESP goes to sleep after http send
- # needs further investigation?
- if ($hash->{TEMPORARY} && $json->{data}{ESP}{sleep}) {
- CommandDelete(undef, $name);
- }
- return;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_Write($$$$$@) #called from logical's IOWrite (end of SetFn)
- {
- my ($hash,$ip,$port,$ident,$parseCmd,$cmd,@params) = @_;
- my ($name,$type,$self) = ($hash->{NAME},$hash->{TYPE},ESPEasy_whoami()."()");
- if ($cmd eq "cleanup") {
- delete $hash->{helper}{received};
- return undef;
- }
- elsif ($cmd eq "statusrequest") {
- ESPEasy_statusRequest($hash);
- return undef;
- }
- ESPEasy_httpReq($hash, $ip, $port, $ident, $parseCmd, $cmd, @params);
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_Notify($$)
- {
- my ($hash,$dev) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- # $hash->{NOTIFYDEV} = "global" set in DefineFn
- return if(!grep(m/^(DELETE)?ATTR $name /, @{$dev->{CHANGED}}));
- foreach (@{$dev->{CHANGED}}) {
- if (m/^(DELETE)?ATTR ($name) (\w+)\s?(\d+)?$/) {
- Log3 $name, 5, "$type $name: received event: $_";
- if ($3 eq "disable") {
- if (defined $1 || (defined $4 && $4 eq "0")) {
- Log3 $name, 4,"$type $name: Device enabled";
- ESPEasy_resetTimer($hash) if ($hash->{SUBTYPE} eq "device");
- readingsSingleUpdate($hash, 'state', 'opened',1);
- }
- else {
- Log3 $name, 3,"$type $name: Device disabled";
- ESPEasy_clearReadings($hash) if $hash->{SUBTYPE} eq "device";
- ESPEasy_resetTimer($hash,"stop");
- readingsSingleUpdate($hash, "state", "disabled",1)
- }
- }
- elsif ($3 eq "Interval") {
- if (defined $1) {
- $hash->{INTERVAL} = $d_Interval;
- }
- elsif (defined $4 && $4 eq "0") {
- $hash->{INTERVAL} = "disabled";
- ESPEasy_resetTimer($hash,"stop");
- CommandDeleteReading(undef, "$name presence")
- if defined $hash->{READINGS}{presence};
- }
- else { # Interval > 0
- $hash->{INTERVAL} = $4;
- ESPEasy_resetTimer($hash);
- }
- }
- elsif ($3 eq "setState") {
- if (defined $1 || (defined $4 && $4 > 0)) {
- ESPEasy_setState($hash);
- }
- else { #setState == 0
- CommandSetReading(undef,"$name state opened");
- }
- }
- else {
- #Log 5, "$type $name: Attribute $3 not handeled by NotifyFn ";
- }
- } #main if
- else { #should never be reached
- #Log 5, "$type $name: WARNING: unexpected event received by NotifyFn: $_";
- }
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_Rename() {
- my ($new,$old) = @_;
- my $i = 0;
- my $type = $defs{"$new"}->{TYPE};
- my $name = $defs{"$new"}->{NAME};
- my $subtype = $defs{"$new"}->{SUBTYPE};
- my @am;
- # copy values from old to new device
- setKeyValue($type."_".$new."-user",getKeyValue($type."_".$old."-user"));
- setKeyValue($type."_".$new."-pass",getKeyValue($type."_".$old."-pass"));
- setKeyValue($type."_".$new."-firstrun",getKeyValue($type."_".$old."-firstrun"));
- # delete old entries
- setKeyValue($type."_".$old."-user",undef);
- setKeyValue($type."_".$old."-pass",undef);
- setKeyValue($type."_".$old."-firstrun",undef);
- # replace IDENT in devices if bridge name changed
- if ($subtype eq "bridge") {
- foreach my $ldev (devspec2array("TYPE=$type")) {
- my $dhash = $defs{$ldev};
- my $dsubtype = $dhash->{SUBTYPE};
- next if ($dsubtype eq "bridge");
- my $dname = $dhash->{NAME};
- my $ddef = $dhash->{DEF};
- my $oddef = $dhash->{DEF};
- $ddef =~ s/ $old / $new /;
- if ($oddef ne $ddef){
- $i = $i+2;
- CommandModify(undef, "$dname $ddef");
- CommandAttr(undef,"$dname IODev $new");
- push (@am,$dname);
- }
- }
- }
- Log3 $name, 2, "$type $name: Device $old renamed to $new";
- Log3 $name, 2, "$type $name: Attribute IODev set to '$name' in these "
- ."devices: ".join(", ",@am) if $subtype eq "bridge";
- if (AttrVal($name,"autosave",AttrVal("global","autosave",1)) && $i>0) {
- CommandSave(undef,undef);
- Log3 $type, 2, "$type $name: $i structural changes saved "
- ."(autosave is enabled)";
- }
- elsif ($i>0) {
- Log3 $type, 2, "$type $name: There are $i structural changes. "
- ."Don't forget to save chages.";
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_Attr(@)
- {
- my ($cmd,$name,$aName,$aVal) = @_;
- my $hash = $defs{$name};
- my $type = $hash->{TYPE};
- my $ret;
- if ($cmd eq "set" && !defined $aVal) {
- Log3 $name, 2, "$type $name: attr $name $aName '': value must not be empty";
- return "$name: attr $aName: value must not be empty";
- }
- # device attributes
- if (defined $hash->{SUBTYPE} && $hash->{SUBTYPE} eq "bridge"
- && ($aName =~ m/^(Interval|pollGPIOs|IODev|setState|readingSwitchText)$/
- || $aName =~ m/^(readingPrefixGPIO|readingSuffixGPIOState|adjustValue)$/
- || $aName =~ m/^(displayTextEncode|displayTextWidth)$/
- || $aName =~ m/^(presenceCheck|parseCmdResponse|rgbGPIOs|colorpicker)$/
- || $aName =~ m/^(wwcwGPIOs|colorpickerCTww|colorpickerCTcw|mapLightCmds)$/)) {
- Log3 $name, 2, "$type $name: Attribut '$aName' can not be used by bridge";
- return "$type: attribut '$aName' cannot be used by bridge device";
- }
- # bridge attributes
- elsif (defined $hash->{SUBTYPE} && $hash->{SUBTYPE} eq "device"
- && ($aName =~ m/^(autocreate|autosave|authentication|httpReqTimeout)$/
- || $aName =~ m/^(maxHttpSessions|maxQueueSize|resendFailedCmd)$/
- || $aName =~ m/^(allowedIPs|deniedIPs|combineDevices)$/ )) {
- Log3 $name, 2, "$type $name: Attribut '$aName' can be used with "
- ."bridge device, only";
- return "$type: attribut '$aName' can be used with the bridge device, only";
- }
- elsif ($aName =~ m/^(autosave|autocreate|authentication|disable)$/
- || $aName =~ m/^(presenceCheck|readingSwitchText|resendFailedCmd)$/
- || $aName =~ m/^(displayTextEncode)$/) {
- $ret = "0,1" if ($cmd eq "set" && not $aVal =~ m/^(0|1)$/)}
- elsif ($aName eq "combineDevices") {
- $ret = "0 | 1 | ESPname | ip[/netmask][,ip[/netmask]][,...]"
- if $cmd eq "set" && !(ESPEasy_isAttrCombineDevices($aVal) || $aVal =~ m/^[01]$/ )}
- elsif ($aName =~ m/^(allowedIPs|deniedIPs)$/) {
- $ret = "[comma separated list of] ip[/netmask] or a regexp"
- if $cmd eq "set" && !ESPEasy_isIPv64Range($aVal,"regexp")}
- elsif ($aName =~ m/^(pollGPIOs|rgbGPIOs|wwcwGPIOs)$/) {
- $ret = "GPIO_No[,GPIO_No][...]"
- if $cmd eq "set" && $aVal !~ m/^[a-zA-Z]{0,2}[0-9]+(,[a-zA-Z]{0,2}[0-9]+)*$/}
- elsif ($aName eq "colorpicker") {
- $ret = "RGB | HSV | HSVp"
- if ($cmd eq "set" && not $aVal =~ m/^(RGB|HSV|HSVp)$/)}
- elsif ($aName =~ m/^(colorpickerCTww|colorpickerCTcw)$/) {
- $ret = "1000..10000"
- if $cmd eq "set" && ($aVal < 1000 || $aVal > 10000)}
- elsif ($aName eq "parseCmdResponse") {
- my $cmds = lc join("|",keys %ESPEasy_setCmdsUsage);
- $ret = "cmd[,cmd][...]"
- if $cmd eq "set" && lc($aVal) !~ m/^($cmds){1}(,($cmds))*$/}
- elsif ($aName eq "mapLightCmds") {
- my $cmds = lc join("|",keys %ESPEasy_setCmdsUsage);
- $ret = "ESPEasy cmd"
- if $cmd eq "set" && lc($aVal) !~ m/^($cmds){1}(,($cmds))*$/}
- elsif ($aName eq "setState") {
- $ret = "integer"
- if ($cmd eq "set" && not $aVal =~ m/^(\d+)$/)}
- elsif ($aName eq "displayTextWidth") {
- $ret = "number of charaters per line"
- if ($cmd eq "set" && not $aVal =~ m/^(\d+)$/)}
- elsif ($aName eq "readingPrefixGPIO") {
- $ret = "[a-zA-Z0-9._-/]+"
- if ($cmd eq "set" && $aVal !~ m/^[A-Za-z\d_\.\-\/]+$/)}
- elsif ($aName eq "readingSuffixGPIOState") {
- $ret = "[a-zA-Z0-9._-/]+"
- if ($cmd eq "set" && $aVal !~ m/^[A-Za-z\d_\.\-\/]+$/)}
- elsif ($aName eq "httpReqTimeout") {
- $ret = "3..60 (default: $d_httpReqTimeout)"
- if $cmd eq "set" && ($aVal < 3 || $aVal > 60)}
- elsif ($aName eq "maxHttpSessions") {
- ($cmd eq "set" && ($aVal !~ m/^[0-9]+$/))
- ? ($ret = ">= 0 (default: $d_maxHttpSessions, 0: disable queuing)")
- : ($hash->{MAX_HTTP_SESSIONS} = $aVal);
- if ($cmd eq "del") {$hash->{MAX_HTTP_SESSIONS} = $d_maxHttpSessions}
- }
- elsif ($aName eq "maxQueueSize") {
- ($cmd eq "set" && ($aVal !~ m/^[1-9][0-9]+$/))
- ? ($ret = ">=10 (default: $d_maxQueueSize)")
- : ($hash->{MAX_QUEUE_SIZE} = $aVal);
- if ($cmd eq "del") {$hash->{MAX_QUEUE_SIZE} = $d_maxQueueSize}
- }
- elsif ($aName eq "Interval") {
- ($cmd eq "set" && ($aVal !~ m/^(\d)+$/ || $aVal <10 && $aVal !=0))
- ? ($ret = "0 or >=10")
- : ($hash->{INTERVAL} = $aVal)
- }
- if (!$init_done) {
- if ($aName =~ /^disable$/ && $aVal == 1) {
- readingsSingleUpdate($hash, "state", "disabled",1);
- }
- }
- if (defined $ret) {
- Log3 $name, 2, "$type $name: attr $name $aName '$aVal' != '$ret'";
- return "$name: $aName must be: $ret";
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- #UndefFn: called while deleting device (delete-command) or while rereadcfg
- sub ESPEasy_Undef($$)
- {
- my ($hash, $arg) = @_;
- my ($name,$type,$port) = ($hash->{NAME},$hash->{TYPE},$hash->{PORT});
- # close server and return if it is a child process for incoming http requests
- if (defined $hash->{TEMPORARY} && $hash->{TEMPORARY} == 1) {
- my $ipv = $hash->{PEER} =~ m/:/ ? 6 : 4;
- my $bhash = $modules{ESPEasy}{defptr}{BRIDGE}{$ipv};
- Log3 $bhash->{NAME}, 4, "$type $name: Closing tcp session.";
- TcpServer_Close($hash);
- return undef
- };
- HttpUtils_Close($hash);
- RemoveInternalTimer($hash);
- if($hash->{SUBTYPE} && $hash->{SUBTYPE} eq "bridge") {
- my $ipv = $hash->{IPV};
- delete $modules{ESPEasy}{defptr}{BRIDGE}{$ipv}
- if(defined($modules{ESPEasy}{defptr}{BRIDGE}{$ipv}));
- TcpServer_Close( $hash );
- Log3 $name, 2, "$type $name: Socket on port tcp/$port closed";
- }
- else {
- IOWrite($hash, $hash->{HOST}, undef, undef, undef, "cleanup", undef );
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- #ShutdownFn: called before fhem's shutdown command
- sub ESPEasy_Shutdown($)
- {
- my ($hash) = @_;
- HttpUtils_Close($hash);
- Log3 $hash->{NAME}, 4, "$hash->{TYPE} $hash->{NAME}: Shutdown requested";
- return undef;
- }
- # ------------------------------------------------------------------------------
- #DeleteFn: called while deleting device (delete-command) but after UndefFn
- sub ESPEasy_Delete($$)
- {
- my ($hash, $arg) = @_;
- #return if it is a child process for incoming http requests
- if (not defined $hash->{TEMPORARY}) {
- setKeyValue($hash->{TYPE}."_".$hash->{NAME}."-user",undef);
- setKeyValue($hash->{TYPE}."_".$hash->{NAME}."-pass",undef);
- setKeyValue($hash->{TYPE}."_".$hash->{NAME}."-firstrun",undef);
- Log3 $hash->{NAME}, 4, "$hash->{TYPE} $hash->{NAME}: $hash->{NAME} deleted";
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_dispatch($$$@) #called by bridge -> send to logical devices
- {
- my($hash,$ident,$host,@values) = @_;
- my $name = $hash->{NAME};
- return if (IsDisabled $name);
- my $type = $hash->{TYPE};
- my $ipv = $host =~ m/:/ ? 6 : 4;
- my $bhash = $modules{ESPEasy}{defptr}{BRIDGE}{$ipv};
- my $bname = $bhash->{NAME};
- my $ui = 1; #can be removed later
- my $as = (AttrVal($bname,"autosave",AttrVal("global","autosave",1))) ? 1 : 0;
- my $ac = (AttrVal($bname,"autocreate",AttrVal("global","autoload_undefined_devices",1))) ? 1 : 0;
- my $msg = $ident."::".$host."::".$ac."::".$as."::".$ui."::".join("|||",@values);
- Log3 $bname, 5, "$type $name: Dispatch: $msg";
- Dispatch($bhash, $msg, undef);
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_dispatchParse($$$) # called by logical device (defined by
- { # $hash->{ParseFn})
- # we are called from dispatch() from the ESPEasy bridge device
- # we never come here if $msg does not match $hash->{MATCH} in the first place
- my ($IOhash, $msg) = @_; # IOhash points to the ESPEasy bridge, not device
- my $IOname = $IOhash->{NAME};
- my $type = $IOhash->{TYPE};
- # 1:ident 2:ip 3:autocreate 4:autosave 5:uniqIDs 6:value(s)
- my ($ident,$ip,$ac,$as,$ui,$v) = split("::",$msg);
- # return undef if !$ident || $ident eq "";
- return "" if !$ident || $ident eq "";
- my $name;
- my @v = split("\\|\\|\\|",$v);
- # look in each $defs{$d}{IDENT} for $ident to get device name.
- foreach my $d (keys %defs) {
- next if($defs{$d}{TYPE} ne "ESPEasy");
- if (InternalVal($defs{$d}{NAME},"IDENT","") eq "$ident") {
- $name = $defs{$d}{NAME} ;
- last;
- }
- }
- # autocreate device if no device has $ident asigned.
- if (!($name) && $ac eq "1") {
- $name = ESPEasy_autocreate($IOhash,$ident,$ip,$as);
- # cleanup helper
- delete $IOhash->{helper}{autocreate}{$ident}
- if defined $IOhash->{helper}{autocreate}{$ident};
- delete $IOhash->{helper}{autocreate}
- if scalar keys %{$IOhash->{helper}{autocreate}} == 0;
- }
- # autocreate is disabled
- elsif (!($name) && $ac eq "0") {
- Log3 $IOname, 2, "$type $IOname: autocreate is disabled (ident: $ident)"
- if not defined $IOhash->{helper}{autocreate}{$ident};
- $IOhash->{helper}{autocreate}{$ident} = "disabled";
- return $ident;
- }
- return $name if (IsDisabled $name);
- my $hash = $defs{$name};
- Log3 $name, 5, "$type $name: Received: $msg";
- if (defined $hash && $hash->{TYPE} eq "ESPEasy" && $hash->{SUBTYPE} eq "device") {
- my @logInternals;
- foreach (@v) {
- my ($cmd,$reading,$value,$vType) = split("\\|\\|",$_);
- # reading prefix replacement (useful if we poll values)
- my $replace = '"'.AttrVal($name,"readingPrefixGPIO","GPIO").'"';
- $reading =~ s/^GPIO/$replace/ee;
- # --- setReading ----------------------------------------------
- if ($cmd eq "r") {
- # reading suffix replacement only for setreading
- $replace = '"'.AttrVal($name,"readingSuffixGPIOState","").'"';
- $reading =~ s/_state$/$replace/ee;
- # map value to on/off if device is a switch
- $value = ($value eq "1") ? "on" : "off"
- if ($vType == 10 && AttrVal($name,"readingSwitchText",1) && !AttrVal($name,"rgbGPIOs",0)
- && $value =~ /^(0|1)$/);
- # delete ignored reading and helper
- if (defined ReadingsVal($name,".ignored_$reading",undef)) {
- delete $hash->{READINGS}{".ignored_$reading"};
- delete $hash->{helper}{received}{".ignored_$reading"};
- }
- # delete warning if there is any (send from httpRequestParse before)
- if (exists ($hash->{"WARNING"})) {
- if (defined $hash->{"WARNING"}) {
- Log3 $name, 2, "$type $name: RESOLVED: ".$hash->{"WARNING"};
- }
- delete $hash->{"WARNING"};
- }
- # attr adjustValue
- my $orgVal = $value;
- $value = ESPEasy_adjustValue($hash,$reading,$value);
- if (!defined $value) {
- Log3 $name, 4, "$type $name: $reading: $orgVal [ignored]";
- $reading = ".ignored_$reading";
- $value = $orgVal;
- }
- readingsSingleUpdate($hash, $reading, $value, 1);
- my $adj = ($orgVal ne $value) ? " [adjusted]" : "";
- Log3 $name, 4, "$type $name: $reading: $value".$adj
- if defined $value && $reading !~ m/^\./; #no leading dot
- # used for presence detection
- $hash->{helper}{received}{$reading} = time();
- # recalc RGB reading if a PWM channel has changed
- if (AttrVal($name,"rgbGPIOs",0) && $reading =~ m/\d$/i) {
- my ($r,$g,$b) = ESPEasy_gpio2RGB($hash);
- # if (($r ne "" && uc ReadingsVal($name,"rgb","") ne uc $r.$g.$b) || ReadingsAge($name,"rgb",0) > 5 ) {
- if (($r ne "" && uc ReadingsVal($name,"rgb","") ne uc $r.$g.$b) ) {
- readingsSingleUpdate($hash, "rgb", $r.$g.$b, 1);
- }
- }
- }
- # --- setInternal ---------------------------------------------
- elsif ($cmd eq "i") {
- # add human readable text to node_type_id
- $value .= defined $ESPEasy_build_id{$value}{type}
- ? ": " . $ESPEasy_build_id{$value}{type}
- : ": unknown node type id"
- if $reading eq "node_type_id";
- # no value given
- $value = "not defined" if !defined $value || $value eq "";
- # set internal
- $hash->{"ESP_".uc($reading)} = $value;
- # add to log
- push(@logInternals,"$reading:$value");
- }
- # --- Error ---------------------------------------------------
- elsif ($cmd eq "e") {
- if (!defined $hash->{"WARNING"} || $hash->{"WARNING"} ne $value) {
- Log3 $name, 2, "$type $name: WARNING: $value";
- $hash->{"WARNING"} = $value;
- # CommandTrigger(undef, "$name ....");
- }
- #readingsSingleUpdate($hash, $reading, $value, 1);
- }
- # --- DeleteReading -------------------------------------------
- elsif ($cmd eq "dr") {
- CommandDeleteReading(undef, "$name $reading");
- Log3 $name, 4, "$type $name: Reading $reading deleted";
- }
- else {
- Log3 $name, 2, "$type $name: Unknown command received via dispatch";
- }
- } # foreach @v
- Log3 $name, 5, "$type $name: Internals: ".join(" ",@logInternals)
- if scalar @logInternals > 0;
- ESPEasy_checkPresence($hash) if ReadingsVal($name,"presence","") ne "present";
- ESPEasy_setState($hash);
- }
- else { #autocreate failed
- Log3 undef, 2, "ESPEasy: Device $name not defined";
- }
- return $name; # must be != undef. else msg will processed further -> help me!
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_autocreate($$$$)
- {
- my ($IOhash,$ident,$ip,$autosave) = @_;
- my $IOname = $IOhash->{NAME};
- my $IOtype = $IOhash->{TYPE};
- my $devname = "ESPEasy_".$ident;
- my $define = "$devname ESPEasy $ip 80 $IOhash->{NAME} $ident";
- Log3 undef, 2, "$IOtype $IOname: Autocreate $define";
- my $cmdret= CommandDefine(undef,$define);
- if(!$cmdret) {
- $cmdret= CommandAttr(undef, "$devname room $IOhash->{TYPE}");
- $cmdret= CommandAttr(undef, "$devname group $IOhash->{TYPE} Device");
- $cmdret= CommandAttr(undef, "$devname setState 3");
- $cmdret= CommandAttr(undef, "$devname Interval $d_Interval");
- $cmdret= CommandAttr(undef, "$devname presenceCheck 1");
- $cmdret= CommandAttr(undef, "$devname readingSwitchText 1");
- if (AttrVal($IOname,"autosave",AttrVal("global","autosave",1))) {
- CommandSave(undef,undef);
- Log3 undef, 2, "$IOtype $IOname: Structural changes saved.";
- }
- else {
- Log3 undef, 2, "$IOtype $IOname: Autosave is disabled: "
- ."Do not forget to save changes.";
- }
- }
- else {
- Log3 undef, 1, "$IOtype $IOname: WARNING: an error occurred "
- ."while creating device for $ident: $cmdret";
- }
- return $devname;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_httpReq($$$$$$@)
- {
- my ($hash, $host, $port, $ident, $parseCmd, $cmd, @params) = @_;
- my ($name,$type,$self) = ($hash->{NAME},$hash->{TYPE},ESPEasy_whoami()."()");
- # command queue (in)
- return undef if ESPEasy_httpReqQueue(@_);
- # increment http session counter
- $hash->{helper}{sessions}{$host}++;
- my $orgParams = join(",",@params);
- my $orgCmd = $cmd;
- # raw is used for command not implemented right now
- if ($cmd eq "raw") {
- $cmd = $params[0];
- splice(@params,0,1);
- }
- $params[0] = ",".$params[0] if defined $params[0];
- my $plist = join(",",@params);
- my $path = ($cmd =~ m/(reboot|reset|erase)/i) ? "/?cmd=" : "/control?cmd=";
- my $url = "http://".$host.":".$port.$path.$cmd.$plist;
- # there is already a log entry with verbose 3 from device
- Log3 $name, 4, "$type $name: Send $cmd$plist to $host for ident $ident" if ($cmd !~ m/^(status)/);
- my $timeout = AttrVal($name,"httpReqTimeout",$d_httpReqTimeout);
- my $httpParams = {
- url => $url,
- timeout => $timeout,
- keepalive => 0,
- httpversion => "1.0",
- hideurl => 0,
- method => "GET",
- hash => $hash,
- cmd => $orgCmd, # passthrought to parseFn
- plist => $orgParams, # passthrought to parseFn
- host => $host, # passthrought to parseFn
- port => $port, # passthrought to parseFn
- ident => $ident, # passthrought to parseFn
- parseCmd => $parseCmd, # passthrought to parseFn (attr parseCmdResponse => (0)|1)
- callback => \&ESPEasy_httpReqParse
- };
- Log3 $name, 5, "$type $name: NonblockingGet for ident:$ident => $url";
- HttpUtils_NonblockingGet($httpParams);
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_httpReqParse($$$)
- {
- my ($param, $err, $data) = @_;
- my $hash = $param->{hash};
- my ($name,$type) = ($hash->{NAME},$hash->{TYPE});
- my @values;
- # command queue
- $hash->{helper}{sessions}{$param->{host}}--;
- if ($err ne "") {
- #dispatch $err to logical device
- @values = ("e||_lastError||$err||0");
- # keep in helper for support reason
- $hash->{"WARNING_$param->{host}"} = $err;
- # logqueue
- Log3 $name, 2, "$type $name: httpReq failed: $param->{host} $param->{ident} "
- ."'$param->{cmd} $param->{plist}' ";
- # unshift command back to queue (resend)
- if (AttrVal($name,"resendFailedCmd",$d_resendFailedCmd)
- && $hash->{MAX_HTTP_SESSIONS}) {
- my @p = ($param->{hash}, $param->{host}, $param->{port}, $param->{ident},
- $param->{parseCmd}, $param->{cmd}, $param->{plist});
- unshift @{$hash->{helper}{queue}{$param->{host}}}, \@p;
- # logqueue
- Log3 $name, 5, "$type $name: Requeuing: $param->{host} $param->{ident} "
- ."'$param->{cmd} $param->{plist}' "
- ."(".scalar @{$hash->{helper}{queue}{$param->{host}}}.")";
- }
- }
- # check that response from cmd should be parsed (client attr parseCmdResponse)
- elsif ($data ne "" && !$param->{parseCmd}) {
- ESPEasy_httpReqDequeue($hash, $param->{host});
- return undef;
- }
- elsif ($data ne "") { # no error occurred
- # command queue
- delete $hash->{"WARNING_$param->{host}"};
- (my $logData = $data) =~ s/\n//sg;
- Log3 $name, 5, "$type $name: http response for ident:$param->{ident} "
- ."cmd:'$param->{cmd},$param->{plist}' => '$logData'";
- if ($data =~ m/^{/) { #it could be json...
- my $res;
- if ( !$hash->{helper}{pm}{JSON} ) {
- Log3 $name, 2, "$type $name: Perl module JSON missing, can't process data.";
- return undef;
- }
- # use encode_utf8 if available else replace any disturbing chars
- $hash->{helper}{pm}{Encode}
- ? ( eval { $res = decode_json( encode_utf8($data) ); 1; } )
- : ( eval { $res = decode_json( $data =~ s/[^\x20-\x7E]/_/gr ); 1; } );
- if ($@) {
- Log3 $name, 2, "$type $name: WARNING: deformed JSON data received "
- ."from $param->{host} requested by $param->{ident}.";
- Log3 $name, 2, "$type $name: $@";
- @values = ("e||_lastError||$@||0");
- return undef;
- }
- # maps plugin type (answer for set state/gpio) to SENSOR_TYPE_SWITCH
- # 10 = SENSOR_TYPE_SWITCH
- my $vType = (defined $res->{plugin} && $res->{plugin} eq "1") ? "10" : "0";
- # Plugin lights:123 nfx:124
- if (defined $res->{plugin} && $res->{plugin} =~ m/^(123|124)$/) {
- foreach (keys %{ $res }) {
- push @values, "r||$_||".$res->{$_}."||".$vType
- if $res->{$_} ne "" && $_ ne "plugin";
- }
- }
- else {
- # push values/cmds in @values
- push @values, "r||GPIO".$res->{pin}."_mode||".$res->{mode}."||".$vType;
- push @values, "r||GPIO".$res->{pin}."_state||".$res->{state}."||".$vType;
- push @values, "r||_lastAction"."||".$res->{log}."||".$vType if $res->{log} ne "";
- }
- } #it is json...
- else { # no json returned => unknown state
- Log3 $name, 5, "$type $name: No json fmt: ident:$param->{ident} ".
- "$param->{cmd} $param->{plist} => $data";
- if ($param->{cmd} eq "status" && $param->{plist} =~ m/^gpio,(\d+)$/i) {
- # push values/cmds in @values
- if (defined $1) {
- push @values, "r||GPIO".$1."_mode||"."?"."||0";
- push @values, "r||GPIO".$1."_state||".$data."||0";
- }
- }
- }
- } # ($data ne "")
- ESPEasy_dispatch($hash,$param->{ident},$param->{host},@values);
- ESPEasy_httpReqDequeue($hash, $param->{host});
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_httpReqQueue(@)
- {
- my ($hash, $host, $port, $ident, $parseCmd, $cmd, @params) = @_;
- my ($name,$type) = ($hash->{NAME},$hash->{TYPE});
- $hash->{helper}{sessions}{$host} = 0 if !defined $hash->{helper}{sessions}{$host};
- # is queueing enabled?
- if ($hash->{MAX_HTTP_SESSIONS}) {
- # do queueing if max sessions are already in use
- if ($hash->{helper}{sessions}{$host} >= $hash->{MAX_HTTP_SESSIONS} ) {
- # max queue size reached
- if (!defined $hash->{helper}{queue}{"$host"}
- || scalar @{$hash->{helper}{queue}{"$host"}} < $hash->{MAX_QUEUE_SIZE}) {
- push(@{$hash->{helper}{queue}{"$host"}}, \@_);
- # logqueue
- Log3 $name, 5, "$type $name: Queuing: $host $ident '$cmd ".join(",",@params)."' (". scalar @{$hash->{helper}{queue}{"$host"}} .")";
- return 1;
- }
- else {
- # logqueue
- Log3 $name, 2, "$type $name: set $cmd ".join(",",@params)." (skipped "
- ."due to queue size exceeded: $hash->{MAX_QUEUE_SIZE})";
- # if ($cmd ne "status");
- # ESPEasy_httpReqDequeue($hash,$host);
- return 1;
- }
- }
- }
- return 0;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_httpReqDequeue($$)
- {
- my ($hash,$host) = @_;
- my ($name,$type) = ($hash->{NAME},$hash->{TYPE});
- if (defined $hash->{helper}{queue}{"$host"} && scalar @{$hash->{helper}{queue}{"$host"}}) {
- my $p = shift @{$hash->{helper}{queue}{"$host"}};
- my ($dhash, $dhost, $port, $ident, $parseCmd, $cmd, @params) = @{ $p };
- # logqueue
- Log3 $name, 5, "$type $name: Dequeuing: $host $ident '$cmd ".join(",",@params)."' (". scalar @{$hash->{helper}{queue}{"$host"}} .")";
- ESPEasy_httpReq($dhash, $dhost, $port, $ident, $parseCmd, $cmd, @params);
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_statusRequest($) #called by device
- {
- my ($hash) = @_;
- my ($name, $type) = ($hash->{NAME},$hash->{TYPE});
- unless (IsDisabled $name) {
- Log3 $name, 4, "$type $name: set statusRequest";
- ESPEasy_pollGPIOs($hash);
- ESPEasy_checkPresence($hash);
- ESPEasy_setState($hash);
- }
- ESPEasy_resetTimer($hash);
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_pollGPIOs($) #called by device
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- my $sleep = $hash->{SLEEP};
- my $a = AttrVal($name,'pollGPIOs',undef);
- if (!defined $a) {
- # do nothing, just return
- }
- elsif (defined $sleep && $sleep eq "1") {
- Log3 $name, 2, "$type $name: Polling of GPIOs is not possible as long as deep sleep mode is active.";
- }
- else {
- my @gpios = split(",",$a);
- foreach my $gpio (@gpios) {
- if ($gpio =~ m/^[a-zA-Z]/) { # pin mapping (eg. D8 -> 15)
- Log3 $name, 5, "$type $name: Pin mapping ".uc $gpio." => $ESPEasy_pinMap{uc $gpio}";
- $gpio = $ESPEasy_pinMap{uc $gpio};
- }
- Log3 $name, 5, "$type $name: IOWrite(\$defs{$name}, $hash->{HOST}, $hash->{PORT}, $hash->{IDENT}, 1, status, gpio,".$gpio.")";
- IOWrite($hash, $hash->{HOST}, $hash->{PORT}, $hash->{IDENT}, 1, "status", "gpio,".$gpio);
- } #foreach
- } #else
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_resetTimer($;$)
- {
- my ($hash,$sig) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- $sig = "" if !$sig;
- if ($init_done == 1) {
- RemoveInternalTimer($hash, "ESPEasy_statusRequest");
- }
- if ($sig eq "stop") {
- Log3 $name, 5, "$type $name: internalTimer stopped";
- return undef;
- }
- return undef if AttrVal($name,"Interval",$d_Interval) == 0;
- unless(IsDisabled($name)) {
- my $s = AttrVal($name,"Interval",$d_Interval) + rand(5);
- my $ts = $s + gettimeofday();
- Log3 $name, 5, "$type $name: Start internalTimer +".int($s)." => ".FmtDateTime($ts);
- InternalTimer($ts, "ESPEasy_statusRequest", $hash);
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_tcpServerOpen($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- my $port = ($hash->{PORT}) ? $hash->{PORT} : 8383;
- my $ret = TcpServer_Open( $hash, $port, "global" );
- exit(1) if ($ret && !$init_done);
- readingsSingleUpdate ( $hash, "state", "initialized", 1 );
- return $ret;
- }
- # ------------------------------------------------------------------------------
- # Duplicated sub from TcpServerUtils as a workaround for new security feature:
- # https://forum.fhem.de/index.php/topic,72717.0.html
- sub ESPEasy_TcpServer_Accept($$)
- {
- my ($hash, $type) = @_;
- my $name = $hash->{NAME};
- my @clientinfo = $hash->{SERVERSOCKET}->accept();
- if(!@clientinfo) {
- Log3 $name, 1, "Accept failed ($name: $!)" if($! != EAGAIN);
- return undef;
- }
- $hash->{CONNECTS}++;
- my ($port, $iaddr) = $hash->{IPV6} ?
- sockaddr_in6($clientinfo[1]) :
- sockaddr_in($clientinfo[1]);
- my $caddr = $hash->{IPV6} ?
- inet_ntop(AF_INET6(), $iaddr) :
- inet_ntoa($iaddr);
- # ------------------------------------------------------------------------------
- # Removed from sub because we have our own access control system that works in
- # a more readable and flexible way (network ranges with allow/deny and regexps).
- # Our new allowed ranges default are also now:
- # 127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7,fe80::/10,::1
- # ------------------------------------------------------------------------------
- #
- # my $af = $attr{$name}{allowfrom};
- # if(!$af) {
- # my $re = "^(127|192.168|172.(1[6-9]|2[0-9]|3[01])|10|169.254)\\.|".
- # "^(fe[89ab]|::1)";
- # if($caddr !~ m/$re/) {
- # my %empty;
- # $hash->{SNAME} = $hash->{NAME};
- # my $auth = Authenticate($hash, \%empty);
- # delete $hash->{SNAME};
- # if($auth == 0) {
- # Log3 $name, 1,
- # "Connection refused from the non-local address $caddr:$port, ".
- # "as there is no working allowed instance defined for it";
- # close($clientinfo[0]);
- # return undef;
- # }
- # }
- # }
- #
- # if($af) {
- # if($caddr !~ m/$af/) {
- # my $hostname = gethostbyaddr($iaddr, AF_INET);
- # if(!$hostname || $hostname !~ m/$af/) {
- # Log3 $name, 1, "Connection refused from $caddr:$port";
- # close($clientinfo[0]);
- # return undef;
- # }
- # }
- # }
- #$clientinfo[0]->blocking(0); # Forum #24799
- if($hash->{SSL}) {
- # Forum #27565: SSLv23:!SSLv3:!SSLv2', #35004: TLSv12:!SSLv3
- my $sslVersion = AttrVal($hash->{NAME}, "sslVersion",
- AttrVal("global", "sslVersion", "TLSv12:!SSLv3"));
- # Certs directory must be in the modpath, i.e. at the same level as the
- # FHEM directory
- my $mp = AttrVal("global", "modpath", ".");
- my $ret = IO::Socket::SSL->start_SSL($clientinfo[0], {
- SSL_server => 1,
- SSL_key_file => "$mp/certs/server-key.pem",
- SSL_cert_file => "$mp/certs/server-cert.pem",
- SSL_version => $sslVersion,
- SSL_cipher_list => 'HIGH:!RC4:!eNULL:!aNULL',
- Timeout => 4,
- });
- my $err = $!;
- if( !$ret
- && $err != EWOULDBLOCK
- && $err ne "Socket is not connected") {
- $err = "" if(!$err);
- $err .= " ".($SSL_ERROR ? $SSL_ERROR : IO::Socket::SSL::errstr());
- Log3 $name, 1, "$type SSL/HTTPS error: $err"
- if($err !~ m/error:00000000:lib.0.:func.0.:reason.0./); #Forum 56364
- close($clientinfo[0]);
- return undef;
- }
- }
- my $cname = "${name}_${caddr}_${port}";
- my %nhash;
- $nhash{NR} = $devcount++;
- $nhash{NAME} = $cname;
- $nhash{PEER} = $caddr;
- $nhash{PORT} = $port;
- $nhash{FD} = $clientinfo[0]->fileno();
- $nhash{CD} = $clientinfo[0]; # sysread / close won't work on fileno
- $nhash{TYPE} = $type;
- $nhash{SSL} = $hash->{SSL};
- $nhash{STATE} = "Connected";
- $nhash{SNAME} = $name;
- $nhash{TEMPORARY} = 1; # Don't want to save it
- $nhash{BUF} = "";
- $attr{$cname}{room} = "hidden";
- $defs{$cname} = \%nhash;
- $selectlist{$nhash{NAME}} = \%nhash;
- my $ret = $clientinfo[0]->setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1);
- Log3 $name, 4, "Connection accepted from $nhash{NAME}";
- return \%nhash;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_header2Hash($) {
- my ($string) = @_;
- my %header = ();
- foreach my $line (split("\r\n", $string)) {
- my ($key,$value) = split(": ", $line,2);
- next if !$value;
- $value =~ s/^ //;
- $header{$key} = $value;
- }
- return \%header;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isAuthenticated($$)
- {
- my ($hash,$ah) = @_;
- my ($name,$type) = ($hash->{NAME},$hash->{TYPE});
- my $ipv = $hash->{PEER} =~ m/:/ ? 6 : 4;
- my $bhash = $modules{ESPEasy}{defptr}{BRIDGE}{$ipv};
- my ($bname,$btype) = ($bhash->{NAME},$bhash->{TYPE});
- my $u = $bhash->{".bau"};
- my $p = $bhash->{".bap"};
- my $attr = AttrVal($bname,"authentication",0);
- if (!defined $u || !defined $p || $attr == 0) {
- if (defined $ah){
- Log3 $bname, 2, "$type $name: No basic authentication active but ".
- "credentials received";
- }
- else {
- Log3 $bname, 4, "$type $name: No basic authentication required";
- }
- return "not required";
- }
- elsif (defined $ah) {
- my ($a,$v) = split(" ",$ah);
- if ($a eq "Basic" && decode_base64($v) eq $u.":".$p) {
- Log3 $bname, 4, "$type $name: Basic authentication accepted";
- return "accepted";
- }
- else {
- Log3 $bname, 2, "$type $name: Basic authentication rejected";
- }
- }
- else {
- Log3 $bname, 2, "$type $name: Basic authentication active but ".
- "no credentials received";
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isParseCmd($$) #called by device
- {
- my ($hash,$cmd) = @_;
- my $name = $hash->{NAME};
- my $doParse = 0;
- my @cmds = split(",",AttrVal($name,"parseCmdResponse","status"));
- foreach (@cmds) {
- if (lc($_) eq lc($cmd)) {
- $doParse = 1;
- last;
- }
- }
- return $doParse;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_sendHttpClose($$$) {
- my ($hash,$code,$response) = @_;
- my ($name,$type,$con) = ($hash->{NAME},$hash->{TYPE},$hash->{CD});
- my $ipv = $hash->{PEER} =~ m/:/ ? 6 : 4;
- my $bhash = $modules{ESPEasy}{defptr}{BRIDGE}{$ipv};
- my $bname = $bhash->{NAME};
- print $con "HTTP/1.1 ".$code."\r\n",
- "Content-Type: text/plain\r\n",
- "Connection: close\r\n",
- "Content-Length: ".length($response)."\r\n\r\n",
- $response;
- Log3 $bname, 4, "$type $name: Send http close '$code'";
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_paramPos($$)
- {
- my ($cmd,$search) = @_;
- my @usage = split(" ",$ESPEasy_setCmdsUsage{$cmd});
- my $pos = 0;
- my $i = 0;
- foreach (@usage) {
- if ($_ eq $search) {
- $pos = $i;
- last;
- }
- $i++;
- }
- return $pos; # return 0 if no match, else position
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_paramCount($)
- {
- return () = $_[0] =~ m/\s/g # count \s in a string
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_clearReadings($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- my @dr;
- foreach (keys %{$hash->{READINGS}}) {
- # next if $_ =~ m/^(presence)$/;
- CommandDeleteReading(undef, "$name $_");
- # fhem("deletereading $name $_");
- push(@dr,$_);
- }
- if (scalar @dr >= 1) {
- delete $hash->{helper}{received};
- delete $hash->{helper}{fpc}; # used in checkPresence
- Log3 $name, 3, "$type $name: Readings [".join(",",@dr)."] wiped out";
- }
- ESPEasy_setState($hash);
- return undef
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_checkVersion($$$$)
- {
- my ($hash,$dev,$ve,$vj) = @_;
- my ($type,$name) = ($hash->{TYPE},$hash->{NAME});
- my $ov = "_OUTDATED_ESP_VER_$dev";
- if ($vj < $minJsonVersion) {
- $hash->{$ov} = "R".$ve."/J".$vj;
- Log3 $name, 2, "$type $name: WARNING: no data processed. ESPEasy plugin "
- ."'FHEM HTTP' is too old [$dev: R".$ve." J".$vj."]. ".
- "Use ESPEasy R$minEEBuild at least.";
- return 1;
- }
- else{
- delete $hash->{$ov} if exists $hash->{$ov};
- return 0;
- }
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_checkPresence($)
- {
- my ($hash,$isPresent) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- my $interval = AttrVal($name,'Interval',$d_Interval);
- my $addTime = 10; # if there is extreme heavy system load
- return undef if AttrVal($name,'presenceCheck',1) == 0;
- return undef if $interval == 0;
- my $presence = "absent";
- # check each received reading
- foreach my $reading (keys %{$hash->{helper}{received}}) {
- if (ReadingsAge($name,$reading,0) < $interval+$addTime) {
- #dev is present if any reading is newer than INTERVAL+$addTime
- $presence = "present";
- last;
- }
- }
- # update presence only if FirstPrecenceCheck is $interval seconds ago.
- $hash->{helper}{fpc} = time() if (!defined $hash->{helper}{fpc});
- if ($presence eq "present" || (time() - $hash->{helper}{fpc}) > $interval) {
- readingsSingleUpdate($hash,"presence",$presence,1);
- Log3 $name, 4, "$type $name: presence: $presence";
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_setState($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- return undef if not AttrVal($name,"setState",1);
- if (AttrVal($name,"rgbGPIOs",0)) {
- my ($r,$g,$b) = ESPEasy_gpio2RGB($hash);
- if ($r ne "") {
- readingsSingleUpdate($hash,"state", "R: $r G: $g B: $b", 1)
- }
- }
- else {
- my $interval = AttrVal($name,"Interval",$d_Interval);
- my $addTime = 3;
- my @ret;
- foreach my $reading (sort keys %{$hash->{helper}{received}}) {
- next if $reading =~ m/^(\.ignored_.*|state|presence|_lastAction|_lastError|\w+_mode)$/;
- next if $interval && ReadingsAge($name,$reading,1) > $interval+$addTime;
- push(@ret, substr($reading,0,AttrVal($name,"setState",3))
- .": ".ReadingsVal($name,$reading,""));
- }
- my $oState = ReadingsVal($name, "state", "");
- my $presence = ReadingsVal($name, "presence", "opened");
- if ($presence eq "absent" && $oState ne "absent") {
- readingsSingleUpdate($hash,"state","absent", 1 );
- delete $hash->{helper}{received};
- }
- else {
- my $nState = (scalar @ret >= 1) ? join(" ",@ret) : $presence;
- readingsSingleUpdate($hash,"state",$nState, 1 ); # if ($oState ne $nState);
- }
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_setRGB($$@)
- {
- my ($hash,$cmd,@p) = @_;
- my ($type,$name) = ($hash->{TYPE},$hash->{NAME});
- my ($rg,$gg,$bg) = split(",",AttrVal($name,"rgbGPIOs",""));
- my ($r,$g,$b);
- my $rgb = $p[0] if $cmd =~ m/^rgb$/i;
- # return undef if !defined $rgb;
- $rg = $ESPEasy_pinMap{uc $rg} if defined $ESPEasy_pinMap{uc $rg};
- $gg = $ESPEasy_pinMap{uc $gg} if defined $ESPEasy_pinMap{uc $gg};
- $bg = $ESPEasy_pinMap{uc $bg} if defined $ESPEasy_pinMap{uc $bg};
- if ($cmd =~ m/^(1|on)$/ || ($cmd =~ m/^rgb$/i && $rgb =~ m/^(1|on)$/)) {
- $rgb = "FFFFFF" }
- elsif ($cmd =~ m/^(0|off)$/ || ($cmd =~ m/^rgb$/i && $rgb =~ m/^(0|off)$/)) {
- $rgb = "000000" }
- elsif ($cmd =~ m/^toggle$/i || ($cmd =~ m/^rgb$/i && $rgb =~ m/^toggle$/i)) {
- $rgb = ReadingsVal($name,"rgb","000000") ne "000000" ? "000000" : "FFFFFF"
- }
- if ($rgb =~ m/^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/) {
- ($r,$g,$b) = (hex($1), hex($2), hex($3));
- }
- else {
- Log3 $name, 2, "$type $name: set $name $cmd $rgb: "
- ."'$rgb' is not a valid RGB value.";
- return "'$rgb' is not a valid RGB value.";
- }
- ESPEasy_Set($hash, $name, "pwm", ("$rg", $r*4));
- ESPEasy_Set($hash, $name, "pwm", ("$gg", $g*4));
- ESPEasy_Set($hash, $name, "pwm", ("$bg", $b*4));
- readingsSingleUpdate($hash, "rgb", uc $rgb, 1);
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_setCT($$@)
- {
- my ($hash,$cmd,@p) = @_;
- my ($type,$name) = ($hash->{TYPE},$hash->{NAME});
- my ($gww,$gcw) = split(",",AttrVal($name,"wwcwGPIOs",""));
- my ($ww,$cw);
- my ($pct,$ct);
- my $ctWW = AttrVal($name,"colorpickerCTww",$d_colorpickerCTww);
- my $ctCW = AttrVal($name,"colorpickerCTcw",$d_colorpickerCTcw);
- my $ctWW_lim = AttrVal($name,"ctWW_reducedRange",undef);
- my $ctCW_lim = AttrVal($name,"ctCW_reducedRange",undef);
- $gww = $ESPEasy_pinMap{uc $gww} if defined $ESPEasy_pinMap{uc $gww};
- $gcw = $ESPEasy_pinMap{uc $gcw} if defined $ESPEasy_pinMap{uc $gcw};
- readingsSingleUpdate($hash, $cmd, $p[0], 1);
- if ($cmd eq "ct") {
- $ct = $p[0];
- $pct = ReadingsVal($name,"pct",50);
- }
- elsif ($cmd eq "pct") {
- $pct = $p[0];
- $ct = ReadingsVal($name,"ct",3000);
- }
- # are we out of range?
- $pct = 100 if $pct > 100;
- $pct = 0 if $pct < 0;
- $ct = $ctWW if $ct < $ctWW;
- $ct = $ctCW if $ct > $ctCW;
- #Log 1, "pct:$pct ct:$ct ctWW:$ctWW ctCW:$ctCW ctWW_lim:$ctWW_lim ctCW_lim:$ctCW_lim";
- my $wwcwMaxBri = AttrVal($name,"wwcwMaxBri",0);
- my ($fww,$fcw) = ESPEasy_ct2wwcw($ct, $ctWW, $ctCW, $wwcwMaxBri, $ctWW_lim, $ctCW_lim);
- ESPEasy_Set($hash, $name, "pwm", ($gww, int $pct*10.23*$fww));
- ESPEasy_Set($hash, $name, "pwm", ($gcw, int $pct*10.23*$fcw));
- return undef;
- }
- # ------------------------------------------------------------------------------
- # ct2wwcw with constant brightness over temp range (or max bri if $maxBri == 1).
- # "used range" can be set to reduce temp range to get a lighter leds with constant
- # bri over reduced temp range.
- # 1: temp to set 2:led-ww-temp 3:led-cw-temp 4:maxBri 5:used range ww 6:used range cw
- sub ESPEasy_ct2wwcw($$$;$$$)
- {
- my ($t,$tww,$tcw,$maxBri,$tww_ur,$tcw_ur) = @_;
- my $maxBriFactor;
- $tcw -= $tww;
- $t -= $tww;
- my $fcw = $t / $tcw;
- my $fww = 1 - $fcw;
- if ($maxBri // $maxBri) {
- $maxBriFactor = ($fcw > $fww) ? 1/$fcw : 1/$fww;
- #Log 1, "maxBriFactor: $maxBriFactor (maxBri)";
- }
- else {
- $tww_ur = $tww if !(defined $tww_ur) || $tww_ur < $tww || $tww_ur >= $tcw;
- $tcw_ur = $tcw if !(defined $tcw_ur) || $tcw_ur > $tcw || $tcw_ur <= $tww;
- $tww_ur -= $tww;
- $tcw_ur -= $tww;
- my $t = ($tww_ur < $tcw - $tcw_ur) ? $tww_ur : $tcw - $tcw_ur;
- my $fcw = $t / $tcw;
- my $fww = 1 - $fcw;
- $maxBriFactor = ($fcw > $fww) ? 1/$fcw : 1/$fww;
- #Log 1, "maxBriFactor: $maxBriFactor (constBri)";
- }
- return ( $fww * $maxBriFactor, $fcw * $maxBriFactor );
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_gpio2RGB($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my ($r,$g,$b,$rgb);
- my $a = AttrVal($name,"rgbGPIOs",undef);
- return undef if !defined $a;
- my ($gr,$gg,$gb) = split(",",AttrVal($name,"rgbGPIOs",""));
- $gr = $ESPEasy_pinMap{uc $gr} if defined $ESPEasy_pinMap{uc $gr};
- $gg = $ESPEasy_pinMap{uc $gg} if defined $ESPEasy_pinMap{uc $gg};
- $gb = $ESPEasy_pinMap{uc $gb} if defined $ESPEasy_pinMap{uc $gb};
- my $rr = AttrVal($name,"readingPrefixGPIO","GPIO").$gr;
- my $rg = AttrVal($name,"readingPrefixGPIO","GPIO").$gg;
- my $rb = AttrVal($name,"readingPrefixGPIO","GPIO").$gb;
- $r = ReadingsVal($name,$rr,undef);
- $g = ReadingsVal($name,$rg,undef);
- $b = ReadingsVal($name,$rb,undef);
- return ("","","") if !defined $r || !defined $g || !defined $b
- || $r !~ m/^\d+$/ || $g !~ m/^\d+$/i || $b !~ m/^\d+$/i;
- return (sprintf("%2.2X",$r/4), sprintf("%2.2X",$g/4), sprintf("%2.2X",$b/4));
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_adjustSetCmds($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- delete $ESPEasy_setCmds{rgb};
- delete $ESPEasy_setCmds{ct};
- delete $ESPEasy_setCmds{pct};
- delete $ESPEasy_setCmds{on};
- delete $ESPEasy_setCmds{off};
- delete $ESPEasy_setCmds{toggle};
-
- delete $ESPEasy_setCmds{dim};
- delete $ESPEasy_setCmds{line};
- delete $ESPEasy_setCmds{one};
- delete $ESPEasy_setCmds{all};
- delete $ESPEasy_setCmds{fade};
- delete $ESPEasy_setCmds{colorfade};
- delete $ESPEasy_setCmds{rainbow};
- delete $ESPEasy_setCmds{kitt};
- delete $ESPEasy_setCmds{comet};
- delete $ESPEasy_setCmds{theatre};
- delete $ESPEasy_setCmds{scan};
- delete $ESPEasy_setCmds{dualscan};
- delete $ESPEasy_setCmds{twinkle};
- delete $ESPEasy_setCmds{twinklefade};
- delete $ESPEasy_setCmds{sparkle};
- delete $ESPEasy_setCmds{wipe};
- delete $ESPEasy_setCmds{fire};
- delete $ESPEasy_setCmds{stop};
- delete $ESPEasy_setCmds{fadetime};
- delete $ESPEasy_setCmds{fadedelay};
- delete $ESPEasy_setCmds{count};
- delete $ESPEasy_setCmds{speed};
- delete $ESPEasy_setCmds{bgcolor};
- if (defined AttrVal($name,"mapLightCmds",undef)) {
- $ESPEasy_setCmds{rgb} = 1;
- $ESPEasy_setCmds{ct} = 1;
- $ESPEasy_setCmds{pct} = 1;
- $ESPEasy_setCmds{on} = 0;
- $ESPEasy_setCmds{off} = 0;
- $ESPEasy_setCmds{toggle} = 0;
- # used by nfx plugin
- $ESPEasy_setCmds{dim} = 1;
- $ESPEasy_setCmds{line} = 3;
- $ESPEasy_setCmds{one} = 2;
- $ESPEasy_setCmds{all} = 1;
- $ESPEasy_setCmds{fade} = 1;
- $ESPEasy_setCmds{colorfade}= 2;
- $ESPEasy_setCmds{rainbow} = 0;
- $ESPEasy_setCmds{kitt} = 1;
- $ESPEasy_setCmds{comet} = 1;
- $ESPEasy_setCmds{theatre} = 1;
- $ESPEasy_setCmds{scan} = 1;
- $ESPEasy_setCmds{dualscan} = 1;
- $ESPEasy_setCmds{twinkle} = 1;
- $ESPEasy_setCmds{twinklefade} = 1;
- $ESPEasy_setCmds{sparkle} = 1;
- $ESPEasy_setCmds{wipe} = 1;
- $ESPEasy_setCmds{fire} = 0;
- $ESPEasy_setCmds{stop} = 0;
- $ESPEasy_setCmds{fadetime} = 1;
- $ESPEasy_setCmds{fadedelay}= 1;
- $ESPEasy_setCmds{speed} = 1;
- $ESPEasy_setCmds{count} = 1;
- $ESPEasy_setCmds{bgcolor} = 1;
- }
- if (defined AttrVal($name,"rgbGPIOs",undef)) {
- $ESPEasy_setCmds{rgb} = 1;
- $ESPEasy_setCmds{on} = 0;
- $ESPEasy_setCmds{off} = 0;
- $ESPEasy_setCmds{toggle} = 0;
- }
- if (defined AttrVal($name,"wwcwGPIOs",undef)) {
- $ESPEasy_setCmds{ct} = 1;
- $ESPEasy_setCmds{pct} = 1;
- $ESPEasy_setCmds{on} = 0;
- $ESPEasy_setCmds{off} = 0;
- $ESPEasy_setCmds{toggle} = 0;
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- # attr <dev> devStateIcon { ESPEasy_devStateIcon($name) }
- sub ESPEasy_devStateIcon($)
- {
- my $ret = Color::devStateIcon($_[0],"rgb","rgb");
- $ret =~ m/^.*:on@#(..)(..)(..):toggle$/;
- return undef if !defined $1;
- my $symP = int((hex($1)+hex($2)+hex($3))/76.5)*10;
- $symP = "00" if $symP == 0;
- my $icon = "light_light_dim_".$symP;
- $ret =~ s/:on@#/:$icon@#/;
- return $ret;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_adjustValue($$$)
- {
- my ($hash,$r,$v) = @_;
- my $name = $hash->{NAME};
- my $type = $hash->{TYPE};
- my $a = AttrVal($name,"adjustValue",undef);
- return $v if !defined $a;
- my ($VALUE,$READING,$NAME) = ($v,$r,$name); #capital vars for use in attribute
- my @a = split(" ",$a);
- foreach (@a) {
- my ($regex,$formula) = split(":",$_);
- if ($r =~ m/^$regex$/) {
- no warnings;
- my $adjVal = $formula =~ m/\$VALUE/ ? eval($formula) : eval($v.$formula);
- use warnings;
- if ($@) {
- Log3 $name, 2, "$type $name: WARNING: attribute 'adjustValue': "
- ."mad expression '$formula'";
- Log3 $name, 2, "$type $name: $@";
- }
- else {
- my $rText = (defined $adjVal) ? $adjVal : "'undef'";
- Log3 $name, 5, "$type $name: Adjusted reading $r: $v => $formula = $rText";
- return $adjVal;
- }
- #last; #disabled to be able to match multiple readings
- }
- }
- return $v;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_urlEncodeDisplayText($$@)
- {
- my ($hash, $cmd, @params) = @_;
- my $name = $hash->{NAME};
- my $enc = AttrVal($name, "displayTextEncode", $d_displayTextEncode);
- my $pp = ESPEasy_paramPos($cmd,'<text>');
- if ($enc && $pp) {
- my (@p, @t);
- my $c = scalar @params;
- # leading parameters
- for (my $i=0; $i<$pp-1; $i++) {
- push( @p, $params[$i] )
- }
- # collect all texts parameters
- for (my $i=$pp-1; $i<$c; $i++) {
- $params[$i] =~ s/,/./g; # comma is ESPEasy parameter splitter, can't be used
- push @t, $params[$i];
- }
- my $text = join(" ", @t);
- # fill line with leading/trailing spaces
- my $width = AttrVal($name,"displayTextWidth", $d_displayTextWidth);
- if ($width) {
- $text = " " x ($p[1]-1) .$text. " " x ($width - length($text) - $p[1]+1);
- $text = substr($text, 0, $width);
- $p[1] = 1;
- }
- push(@p, urlEncode($text));
- return @p;
- }
- return @params;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_loadRequiredModules($)
- {
- my ($hash) = @_;
- foreach ("JSON", "Encode") {
- eval "use $_; 1;";
- if (!$@) {
- $hash->{helper}{pm}{$_} = 1;
- }
- else {
- $hash->{helper}{pm}{$_} = 0;
- if ($init_done || $hash->{SUBTYPE} eq "bridge") {
- my ($name,$type) = ($hash->{NAME},$hash->{TYPE});
- Log3 $name, 1, "$type $name: WARNING: Perl module $_ is not installed. "
- . "Reduced functionality!";
- Log3 $name, 2, "$type $name: $@" if $init_done;
- }
- }
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isAttrCombineDevices($)
- {
- return 0 if !defined $_[0];
- my @ranges = split(/,| /,$_[0]);
- foreach (@ranges) {
- if (!($_ =~ m/^([A-Za-z0-9_\.]|[A-Za-z0-9_\.][A-Za-z0-9_\.]*[A-Za-z0-9\._])$/
- || ESPEasy_isIPv64Range($_)))
- {
- return 0
- }
- }
- return 1;
- }
- # ------------------------------------------------------------------------------
- # check if $peer is covered by $allowed (eg. 10.1.2.3 is included in 10.0.0.0/8)
- # 1:peer address 2:allowed range
- # ------------------------------------------------------------------------------
- sub ESPEasy_isCombineDevices($$$)
- {
- my ($peer,$espName,$allowed) = @_;
- return $allowed if $allowed =~ m/^[01]$/;
- my @allowed = split(/,| /,$allowed);
- foreach (@allowed) { return 1 if $espName eq $_ }
- return 1 if ESPEasy_isPeerAllowed($peer,$allowed);
- return 0;
- }
- # ------------------------------------------------------------------------------
- # check param to be a valid ip64 address or fqdn or hostname
- # ------------------------------------------------------------------------------
- sub ESPEasy_isValidPeer($)
- {
- my ($addr) = @_;
- return 0 if !defined $addr;
- my @ranges = split(/,| /,$addr);
- foreach (@ranges) {
- return 0 if !( ESPEasy_isIPv64Range($_)
- || ESPEasy_isFqdn($_) || ESPEasy_isHostname($_) );
- }
- return 1;
- }
- # ------------------------------------------------------------------------------
- # check if given ip or ip range is guilty
- # argument can be:
- # - ipv4, ipv4/CIDR, ipv4/dotted, ipv6, ipv6/CIDR (or a regexp if opt. argument
- # $regexChk is set)
- # - space or comma separated list of above.
- # ------------------------------------------------------------------------------
- sub ESPEasy_isIPv64Range($;$)
- {
- my ($addr,$regexChk) = @_;
- return 0 if !defined $addr;
- my @ranges = split(/,| /,$addr);
- foreach (@ranges) {
- my ($ip,$nm) = split("/",$_);
- if (ESPEasy_isIPv4($ip)) {
- return 0 if defined $nm && !( ESPEasy_isNmDotted($nm)
- || ESPEasy_isNmCIDRv4($nm) );
- }
- elsif (ESPEasy_isIPv6($ip)) {
- return 0 if defined $nm && !ESPEasy_isNmCIDRv6($nm);
- }
- elsif (defined $regexChk && !defined $nm) {
- return 0 if $ip =~ m/^\*/ || $ip =~ m/^\d+\.\d+\.\d+\.\d+$/; # faulty regexp/ip
- eval { "Hallo" =~ m/^$ip$/ };
- return $@ ? 0 : 1;
- }
- else {
- return 0;
- }
- }
- return 1;
- }
- # ------------------------------------------------------------------------------
- # check if $peer is covered by $allowed (eg. 10.1.2.3 is included in 10.0.0.0/8)
- # 1:peer address 2:allowed range
- # ------------------------------------------------------------------------------
- sub ESPEasy_isPeerAllowed($$)
- {
- my ($peer,$allowed) = @_;
- return $allowed if $allowed =~ m/^[01]$/;
- #return 1 if $allowed =~ /^0.0.0.0\/0(.0.0.0)?$/; # not necessary but faster
- my $binPeer = ESPEasy_ip2bin($peer);
- my @a = split(/,| /,$allowed);
- foreach (@a) {
- return 1 if $peer =~ m/^$_$/; # a regexp is been used
- next if !ESPEasy_isIPv64Range($_); # needed for combinedDevices
- my ($addr,$ip,$mask) = ESPEasy_addrToCIDR($_);
- return 0 if !defined $ip || !defined $mask; # return if ip or mask !guilty
- my $binAllowed = ESPEasy_ip2bin($addr);
- my $binPeerCut = substr($binPeer,0,$mask);
- return 1 if ($binAllowed eq $binPeerCut);
- }
- return 0;
- }
- # ------------------------------------------------------------------------------
- # convert IPv64 address to binary format and return network part of binary, only
- # ------------------------------------------------------------------------------
- sub ESPEasy_ip2bin($)
- {
- my ($addr) = @_;
- my ($ip,$mask) = split("/",$addr);
- my @bin;
- if (ESPEasy_isIPv4($ip)) {
- $mask = 32 if !defined $mask;
- @bin = map substr(unpack("B32",pack("N",$_)),-8), split(/\./,$ip);
- }
- elsif (ESPEasy_isIPv6($ip)) {
- $ip = ESPEasy_expandIPv6($ip);
- $mask = 128 if !defined $mask;
- @bin = map {unpack('B*',pack('H*',$_))} split(/:/, $ip);
- }
- else {
- return undef;
- }
- my $bin = join('', @bin);
- my $binMask = substr($bin, 0, $mask);
- return $binMask; # return network part of $bin
- }
- # ------------------------------------------------------------------------------
- # expand IPv6 address to 8 full blocks
- # Advantage of IO::Socket : already installed and it seems to be the fastest way
- # http://stackoverflow.com/questions/4800691/perl-ipv6-address-expansion-parsing
- # ------------------------------------------------------------------------------
- sub ESPEasy_expandIPv6($)
- {
- my ($ipv6) = @_;
- use Socket qw(inet_pton AF_INET6);
- return join(":", unpack("H4H4H4H4H4H4H4H4",inet_pton(AF_INET6, $ipv6)));
- }
- # ------------------------------------------------------------------------------
- # convert IPv64 address or range into CIDR notion
- # return undef if addreess or netmask is not valid
- # ------------------------------------------------------------------------------
- sub ESPEasy_addrToCIDR($)
- {
- my ($addr) = @_;
- my ($ip,$mask) = split("/",$addr);
- # no nm specified
- return (ESPEasy_isIPv4($ip) ? ("$ip/32",$ip,32) : ("$ip/128",$ip,128)) if !defined $mask;
- # netmask is already in CIDR format and all values are valid
- return ("$ip/$mask",$ip,$mask)
- if (ESPEasy_isIPv4($ip) && ESPEasy_isNmCIDRv4($mask))
- || (ESPEasy_isIPv6($ip) && ESPEasy_isNmCIDRv6($mask));
- $mask = ESPEasy_dottedNmToCIDR($mask);
- return (undef,undef,undef) if !defined $mask;
- return ("$ip/$mask",$ip,$mask);
- }
- # ------------------------------------------------------------------------------
- # convert dotted decimal netmask to CIDR format
- # return undef if nm is not in dotted decimal format
- # ------------------------------------------------------------------------------
- sub ESPEasy_dottedNmToCIDR($)
- {
- my ($mask) = @_;
- return undef if !ESPEasy_isNmDotted($mask);
- # dotted decimal to CIDR
- my ($byte1, $byte2, $byte3, $byte4) = split(/\./, $mask);
- my $num = ($byte1 * 16777216) + ($byte2 * 65536) + ($byte3 * 256) + $byte4;
- my $bin = unpack("B*", pack("N", $num));
- my $count = ($bin =~ tr/1/1/);
- return $count; # return number of netmask bits
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isIPv4($)
- {
- return 0 if !defined $_[0];
- return 1 if($_[0]
- =~ m/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/);
- return 0;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isIPv6($)
- {
- return 0 if !defined $_[0];
- return 1 if ($_[0]
- =~ m/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/);
- return 0;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isIPv64($)
- {
- return 0 if !defined $_[0];
- return 1 if ESPEasy_isIPv4($_[0]) || ESPEasy_isIPv6($_[0]);
- return 0;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isNmDotted($)
- {
- return 0 if !defined $_[0];
- return 1 if ($_[0]
- =~ m/^(255|254|252|248|240|224|192|128|0)\.0\.0\.0|255\.(255|254|252|248|240|224|192|128|0)\.0\.0|255\.255\.(255|254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(255|254|252|248|240|224|192|128|0)$/);
- return 0;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isNmCIDRv4($)
- {
- return 0 if !defined $_[0];
- return 1 if ($_[0] =~ m/^([0-2]?[0-9]|3[0-2])$/);
- return 0;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isNmCIDRv6($)
- {
- return 0 if !defined $_[0];
- return 1 if ($_[0] =~ m/^([0-9]?[0-9]|1([0-1][0-9]|2[0-8]))$/);
- return 0;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isFqdn($)
- {
- return 0 if !defined $_[0];
- return 1 if ($_[0]
- =~ m/^(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.)+[a-zA-Z]{2,63}$)$/);
- return 0;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_isHostname($)
- {
- return 0 if !defined $_[0];
- return 1 if ($_[0] =~ m/^([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/)
- && !(ESPEasy_isIPv4($_[0]) || ESPEasy_isIPv6($_[0]));
- return 0;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_whoami() {return (split('::',(caller(1))[3]))[1] || '';}
- # ------------------------------------------------------------------------------
- sub ESPEasy_dumpSingleLine($)
- {
- my $saveIndent = $Data::Dumper::Indent; my $saveTerse = $Data::Dumper::Terse ;
- $Data::Dumper::Indent = 0; $Data::Dumper::Terse = 1;
- my $ret = Dumper($_[0]);
- $Data::Dumper::Indent = $saveIndent; $Data::Dumper::Terse = $saveTerse;
- return $ret;
- }
- # ------------------------------------------------------------------------------
- sub ESPEasy_removeGit($)
- {
- my ($hash) = @_;
- my $file = $attr{global}{modpath}."/ESPEasy.txt";
- if (-f $file) {
- unlink $file;
- my $controls = $attr{global}{modpath}."/FHEM/controls.txt";
- open(FH, $controls) || return "Can't open $controls: $!";
- my $ret = join("", <FH>);
- close(FH);
- if ($ret =~ m/controls_ESPEasy.txt/) {
- my ($name,$type) = ($hash->{NAME},$hash->{TYPE});
- Log3 $name, 1, "";
- Log3 $name, 1, "================================================================================";
- Log3 $name, 1, "";
- Log3 $name, 1, "ESPEasy is part of the official FHEM distribution.";
- Log3 $name, 1, "";
- Log3 $name, 1, "Please remove ESPEasy Github repository from FHEM update by using the following";
- Log3 $name, 1, "FHEM command: ";
- Log3 $name, 1, "";
- Log3 $name, 1, "update delete https://raw.githubusercontent.com/ddtlabs/ESPEasy/master/controls_ESPEasy.txt";
- Log3 $name, 1, "";
- Log3 $name, 1, "================================================================================";
- Log3 $name, 1, "";
- }
- }
- return undef;
- }
- 1;
- =pod
- =item device
- =item summary Control and access to ESPEasy (Espressif ESP8266 WLAN-SoC)
- =item summary_DE Steuerung und Zugriff auf ESPEasy (Espressif ESP8266 WLAN-SoC)
- =begin html
- <a name="ESPEasy"></a>
- <h3>ESPEasy</h3>
- <ul>
- <p>Provides access and control to Espressif ESP8266 WLAN-SoC w/ ESPEasy</p>
- Notes:
- <ul>
- <li>You have to define a bridge device before any logical device can be
- defined.
- </li>
- <li>You have to configure your ESP to use "FHEM HTTP" controller protocol.
- Furthermore ESP controller IP must match FHEM's IP address. ESP controller
- port and the FHEM ESPEasy bridge port must be the same.
- </li>
- <li>
- Max. 2 ESPEasy bridges can be defined at the same time: 1 for IPv4 and 1 for IPv6
- </li>
- <li>Further information about this module is available here:
- <a href="https://forum.fhem.de/index.php/topic,55728.0.html">Forum #55728</a>
- </li>
- <br>
- <li><b>UPDATE:
- </li>
- <li></b>For security reasons: if one or more of your ESPEasy device uses a
- public IP address then you have to enable this explicitly or the device(s)
- will be ignored/rejected:
- </li>
- <ul>
- <li>
- Enable all ESPEasy device IP addresses/subnets/regexs with the help of
- bridge attributes
- <a href="#ESPEasy_bridge_attr_allowedips">allowedIPs</a> /
- <a href="#ESPEasy_bridge_attr_deniedips">deniedIPs</a>.
- </li>
- <li>
- Enable authentication: see attribute
- <a href="#ESPEasy_bridge_attr_authentication">authentication</a> and
- bridge set <a href="#ESPEasy_bridge_set_user">user</a>
- / <a href="#ESPEasy_bridge_set_pass">pass</a> commands.
- </li>
- </ul>
- <br>
- </ul>
- Requirements:
- <ul>
- <li>
- ESPEasy build >= <a href="https://github.com/ESP8266nu/ESPEasy"
- target="_new">R128</a> (self compiled) or an ESPEasy precompiled image
- >= <a href="http://www.letscontrolit.com/wiki/index.php/ESPEasy#Loading_firmware" target="_new">R140_RC3</a><br>
- </li>
- <li>perl module JSON<br>
- Use "cpan install JSON" or operating system's package manager to install
- Perl JSON Modul. Depending on your os the required package is named:
- libjson-perl or perl-JSON.
- </li>
- </ul>
- <h3>ESPEasy Bridge</h3>
- <a name="ESPEasy_bridge_define"></a>
- <b>Define </b>(bridge)<br><br>
- <ul>
- <code>define <name> ESPEasy bridge <[IPV6:]port></code><br><br>
- <li>
- <a name=""><code><name></code></a><br>
- Specifies a device name of your choise.<br>
- example: <code>ESPBridge</code>
- </li><br>
- <li>
- <a name=""><code><port></code></a><br>
- Specifies TCP port for incoming ESPEasy http requests. This port must
- <u>not</u> be used by any other application or daemon on your system and
- must be in the range 1024..65535 unless you run your FHEM installation
- with root permissions (not recommanded).<br>
- If you want to define an IPv4 and an IPv6 bridge on the same TCP port
- (recommanded) then it might be necessary on (some?) Linux
- distributions to activate IPV6_V6ONLY socket option. Use <code>"echo
- 1>/proc/sys/net/ipv6/bindv6only"</code> or systemctl for that purpose.<br>
- eg. <code>8383</code><br>
- eg. <code>IPV6:8383</code><br>
- Example:<br>
- <code>define ESPBridge ESPEasy bridge 8383</code></li><br>
- </ul>
- <br><a name="ESPEasy_bridge_get"></a>
- <b>Get </b>(bridge)<br><br>
- <ul>
- <li><a name="ESPEasy_bridge_get_reading"><reading></a><br>
- returns the value of the specified reading</li>
- <br>
- <li><a name="ESPEasy_bridge_get_queueSize">queueSize</a><br>
- returns number of entries for each queue (<ip>:number
- [<ip>:number] [...]).
- </li><br>
- <li><a name="ESPEasy_bridge_get_user">user</a><br>
- returns username used by basic authentication for incoming requests.
- </li><br>
- <li><a name="ESPEasy_bridge_get_pass">pass</a><br>
- returns password used by basic authentication for incoming requests.
- </li><br>
- </ul>
- <br><a name="ESPEasy_bridge_set"></a>
- <b>Set </b>(bridge)<br><br>
- <ul>
- <li><a name="ESPEasy_bridge_set_help">help</a><br>
- Shows set command usage<br>
- required values: <code>help|pass|user|clearQueue</code></li><br>
- <li><a name="ESPEasy_bridge_set_clearqueue">clearQueue</a><br>
- Used to erase all command queues.<br>
- required value: <code><none></code><br>
- eg. : <code>set ESPBridge clearQueue</code></li><br>
- <li><a name="ESPEasy_bridge_set_pass">pass</a><br>
- Specifies password used by basic authentication for incoming requests.<br>
- Note that attribute <a href="#ESPEasy_bridge_attr_authentication">authentication</a>
- must be set to enable basic authentication, too.<br>
- required value: <code><password></code><br>
- eg. : <code>set ESPBridge pass secretpass</code></li><br>
- <li><a name="ESPEasy_bridge_set_user">user</a><br>
- Specifies username used by basic authentication for incoming requests.<br>
- Note that attribute <a href="#ESPEasy_bridge_attr_authentication">authentication</a>
- must be set to enable basic authentication, too.<br>
- required value: <code><username></code><br>
- eg. : <code>set ESPBridge user itsme</code></li><br>
- </ul>
- <br><a name="ESPEasy_bridge_attr"></a>
- <b>Attributes </b>(bridge)<br><br>
- <ul>
- <li><a name="ESPEasy_bridge_attr_allowedips">allowedIPs</a><br>
- Used to limit IPs or IP ranges of ESPs which are allowed to commit data.
- <br>
- Specify IP, IP/netmask, regexp or a comma separated list of these values.
- Netmask can be written as bitmask or dotted decimal. Domain names for dns
- lookups are not supported.<br>
- Possible values: IPv64 address, IPv64/netmask, regexp<br>
- Default: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16,
- fe80::/10, fc00::/7, ::1
- <br><br>
- Examles:<br>
- <table><tr><td>
- 10.68.30.147
- </td><td>
- => IPv4 single address
- </td></tr><tr><td>
- 10.68.30.0/25
- </td><td>
- => IPv4 CIDR network 192.168.30.0-127
- </td></tr><tr><td>
- 10.68.30.8/255.255.248.0
- </td><td>
- => IPv4 CIDR network 192.168.30.8-15
- </td></tr><tr><td>
- 192.168.30.1([0-4][0-9]|50)
- </td><td>
- => IPv4 range w/ regexp: 192.168.30.100-150
- </td></tr><tr><td>
- 2001:1a59:50a9::aaaa
- </td><td>
- => IPv6 single address
- </td></tr><tr><td>
- 2001:1a59:50a9::/48
- </td><td>
- => IPv6 network 2001:1a59:50a9::/48
- </td></tr><tr><td>
- 2001:1a59:50a9::01[0-4][0-9]
- </td><td>
- => IPv6 range w/ regexp: 2001:1a59:50a9::0100-0149
- </tr></td>
- </table>
- <span style="font-size:small;">Note that short IPv6 notation (::) must be
- used in conjunction with regexps.</span>
- </li><br>
- <li><a name="ESPEasy_bridge_attr_authentication">authentication</a><br>
- Used to enable basic authentication for incoming requests.<br>
- Note that user, pass and authentication attribute must be set to activate
- basic authentication<br>
- Possible values: 0,1<br>
- Default: 0</li><br>
- <li><a name="ESPEasy_bridge_attr_autocreate">autocreate</a><br>
- Used to overwrite global autocreate setting.<br>
- Global autocreate setting will be detected by global attribut
- 'autoload_undefined_devices'<br>
- Possible values: 0,1<br>
- Default: 0 (disabled)</li><br>
- <li><a name="ESPEasy_bridge_attr_autosave">autosave</a><br>
- Used to overwrite global autosave setting.<br>
- Global autosave setting will be detected by global attribut 'autosave'.
- <br>
- Possible values: 0,1<br>
- Default: 0 (disabled)</li><br>
- <li><a name="ESPEasy_bridge_attr_combinedevices">combineDevices</a><br>
- Used to gather all ESP devices of a single ESP into 1 FHEM device even if
- different ESP devices names are used.<br>
- Possible values: 0, 1, IPv64 address, IPv64/netmask, ESPname or a comma
- separated list consisting of these values.<br>
- Netmasks can be written as bitmask or dotted decimal. Domain names for dns
- lookups are not supported.<br>
- Default: 0 (disabled for all ESPs)<br>
- Eg. 1 (globally enabled)<br>
- Eg. ESP01,ESP02<br>
- Eg. 10.68.30.1,10.69.0.0/16,ESP01,2002:1a59:50a9::/48</li><br>
- <li><a name="ESPEasy_bridge_attr_deniedips">deniedIPs</a><br>
- Used to define IPs or IP ranges of ESPs which are denied to commit data.
- <br>
- Syntax see <a href="#ESPEasy_bridge_attr_allowedips">allowedIPs</a>.<br>
- This attribute will overwrite any IP or range defined by
- <a href="#ESPEasy_bridge_attr_allowedips">allowedIPs</a>.<br>
- Default: none (no IPs are denied)</li><br>
- <li><a name="ESPEasy_bridge_attr_disable">disable</a><br>
- Used to disable device.<br>
- Possible values: 0,1<br>
- Default: 0 (eanble)</li><br>
- <li><a name="ESPEasy_bridge_attr_httpreqtimeout">httpReqTimeout</a><br>
- Specifies seconds to wait for a http answer from ESP8266 device.<br>
- Possible values: 4..60<br>
- Default: 10 seconds</li><br>
- <li><a name="ESPEasy_bridge_attr_maxhttpsessions">maxHttpSessions</a><br>
- Limit maximal concurrent outgoing http sessions to a single ESP.<br>
- Set to 0 to disable this feature. At the moment (ESPEasy R147) it seems
- to be possible to send 5 "concurrent" requests if nothing else keeps the
- esp working.<br>
- Possible values: 0..9<br>
- Default: 3</li><br>
- <li><a name="ESPEasy_bridge_attr_maxqueuesize">maxQueueSize</a><br>
- Limit maximal queue size (number of commands in queue) for outgoing http
- requests.<br>
- If command queue size is reached (eg. ESP is offline) any further
- command will be ignored and discard.<br>
- Possible values: >10<br>
- Default: 250</li><br>
- <li><a name="ESPEasy_bridge_attr_resendfailedcmd">resendFailedCmd</a><br>
- Used to resend commands when http request returned an error<br>
- Possible values: 0,1<br>
- Default: 0 (disabled)</li><br>
- <li><a name="ESPEasy_bridge_attr_uniqids">uniqIDs</a><br>
- This attribute has been removed.</li><br>
- <li><a href="#readingFnAttributes">readingFnAttributes</a>
- </li><br>
- </ul>
- <h3>ESPEasy Device</h3>
- <a name="ESPEasy_device_define"></a>
- <b>Define </b>(logical device)<br><br>
- <ul>
- Notes: Logical devices will be created automatically if any values are
- received by the bridge device and autocreate is not disabled. If you
- configured your ESP in a way that no data is send independently then you
- have to define logical devices. At least wifi rssi value could be defined
- to use autocreate and presence detection.<br><br>
- <code>define <name> ESPEasy <ip|fqdn> <port> <IODev> <identifier></code><br><br>
- <li>
- <a name=""><code><name></code></a><br>
- Specifies a device name of your choise.<br>
- example: <code>ESPxx</code>
- </li><br>
- <li>
- <a name=""><code><ip|fqdn></code></a><br>
- Specifies ESP IP address or hostname.<br>
- example: <code>172.16.4.100</code><br>
- example: <code>espxx.your.domain.net</code>
- </li><br>
- <li>
- <a name=""><code><port></code></a><br>
- Specifies http port to be used for outgoing request to your ESP. Should be 80<br>
- example: <code>80</code>
- </li><br>
- <li>
- <a name=""><code><IODev></code></a><br>
- Specifies your ESP bridge device. See above.<br>
- example: <code>ESPBridge</code>
- </li><br>
- <li>
- <a name=""><code><identifier></code></a><br>
- Specifies an identifier that will bind your ESP to this device.<br>
- This identifier must be specified in this form:<br>
- <esp name>_<esp device name>.<br>
- If bridge attribute <a href="#ESPEasy_bridge_attr_combinedevices">combineDevices</a> is used then <esp name> is used, only.<br>
- ESP name and device name can be found here:<br>
- <esp name>: => ESP GUI => Config => Main Settings => Name<br>
- <esp device name>: => ESP GUI => Devices => Edit => Task Settings => Name<br>
- example: <code>ESPxx_DHT22</code><br>
- example: <code>ESPxx</code>
- </li><br>
- <li>Example:<br>
- <code>define ESPxx ESPEasy 172.16.4.100 80 ESPBridge EspXX_SensorXX</code>
- </li><br>
- </ul>
- <br><a name="ESPEasy_device_get"></a>
- <b>Get </b>(logical device)<br><br>
- <ul>
- <li><a name="ESPEasy_device_get_reading"><reading></a><br>
- returns the value of the specified reading
- </li><br>
- <li><a name="ESPEasy_bridge_get_pinmap">pinMap</a><br>
- returns possible alternative pin names that can be used in commands
- </li><br>
- </ul>
- <br><a name="ESPEasy_device_set"></a>
- <b>Set </b>(logical device)
- <br><br>
- <ul>
- Notes:<br>
- - Commands are case insensitive.<br>
- - Users of Wemos D1 mini or NodeMCU can use Arduino pin names instead of
- GPIO numbers.<br>
- D1 => GPIO5, D2 => GPIO4, ...,TX => GPIO1 (see: get
- <a href="#ESPEasy_bridge_get_pinmap">pinMap</a>)<br>
- - low/high state can be written as 0/1 or on/off
- <br>
- <br>
- <li><a name="ESPEasy_device_set_clearreadings">clearReadings</a><br>
- Delete all readings that are auto created by received sensor values
- since last FHEM restart.<br>
- <ul>
- <li>arguments: <code>none</code></li>
- <li>example: set <esp> clearReadings</li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_help">help</a><br>
- Shows set command usage.<br>
- <ul>
- <li>arguments: <code>a valid set command</code></li>
- <li>example: <code>set <esp> help gpio</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_raw">raw</a><br>
- Can be used for own ESP plugins or new ESPEasy commands that are not
- considered by this module at the moment. Any argument will be sent
- directly to the ESP.
- <ul>
- <li>arguments: raw <cmd> [<arg1>] [<arg2>] [<...>]</li>
- <li>example: set <esp> raw myCommand p1 p2 p3</li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_statusrequest">statusRequest</a><br>
- Trigger a statusRequest for configured GPIOs (see attribut pollGPIOs)
- and do a presence check<br>
- <ul>
- <li>arguments: <code>n/a</code></li>
- <li>example: <code>set <esp> statusRequest</code></li>
- </ul><br>
- </li><br>
- </ul>
- <i><b>Note:</b> The following commands are built-in ESPEasy Software commands
- that are send directly to the ESP after passing a syntax check. A detailed
- description can be found here:
- <a href="http://www.letscontrolit.com/wiki/index.php/ESPEasy_Command_Reference"
- target="_NEW">ESPEasy Command Reference</a></i><br><br>
- <ul>
- </ul>
- <u>ESPEasy generic I/O commands:</u><br><br>
- <ul>
- <li><a name="ESPEasy_device_set_gpio">GPIO</a><br>
- Switch output pins to high/low<br>
- <ul>
- <li>arguments: <code><pin> <0|1|off|on></code></li>
- <li>example: <code>set <esp> gpio 14 on</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_pwm">PWM</a><br>
- Direct PWM control of output pins<br>
- <ul>
- <li>arguments: <code><pin> <level></code></li>
- <li>example: <code>set <esp> pwm 14 512</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_pwmfade">PWMFADE</a><br>
- Fade output pins to a pwm value<br>
- <ul>
- <li>arguments: <code><pin> <target pwm> <duration 1-30s></code></li>
- <li>example: <code>set <esp> pwmfade 14 1023 10</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_pulse">Pulse</a><br>
- Direct pulse control of output pins<br>
- <ul>
- <li>arguments: <code><pin> <0|1|off|on> <duration></code></li>
- <li>example: <code>set <esp> pulse 14 on 10</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_logpulse">LongPulse</a><br>
- Direct pulse control of output pins<br>
- <ul>
- <li>arguments: <code><pin> <0|1|off|on> <duration></code></li>
- <li>example: <code>set <esp> longpulse 14 on 10</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_pcfgpio">PCFGpio</a><br>
- Control PCF8574 (8-bit I/O expander for I2C-bus)<br>
- <ul>
- <li>arguments: <code><port> <0|1|off|on></code></li>
- <li>example: <code>set <esp> PCFGpio 128 on</code></li>
- </ul>
- Port numbering see:
- <a href="https://www.letscontrolit.com/wiki/index.php/PCF8574#Input">
- ESPEasy Wiki PCF8574</a>
- </li><br>
- <li><a name="ESPEasy_device_set_pcfpulse">PCFPulse</a><br>
- Control PCF8574 (8-bit I/O expander for I2C-bus)
- <ul>
- <li>arguments: <code><port> <0|1|off|on> <duration></code></li>
- <li>example: <code>set <esp> PCFPulse 128 on 10</code></li>
- </ul>
- Port numbering see:
- <a href="https://www.letscontrolit.com/wiki/index.php/PCF8574#Input">
- ESPEasy Wiki PCF8574</a>
- </li><br>
- <li><a name="ESPEasy_device_set_pcflongpulse">PCFLongPulse</a><br>
- Control on PCF8574 (8-bit I/O expander for I2C-bus)
- <ul>
- <li>arguments: <code><port> <0|1|off|on> <duration></code></li>
- <li>example: <code>set <esp> PCFLongPulse 128 on 10</code></li>
- </ul>
- Port numbering see:
- <a href="https://www.letscontrolit.com/wiki/index.php/PCF8574#Input">
- ESPEasy Wiki PCF8574</a>
- </li><br>
- <li><a name="ESPEasy_device_set_mcpgpio">mcpgpio</a><br>
- Control MCP23017 output pins (16-Bit I/O Expander with Serial Interface)<br>
- <ul>
- <li>arguments: <code><port> <0|1|off|on></code></li>
- <li>example: <code>set <esp> mcpgpio 48 on</code></li>
- </ul>
- Port numbering see:
- <a href="https://www.letscontrolit.com/wiki/index.php/MCP23017#Input">
- ESPEasy Wiki MCP23017</a>
- </li><br>
- <li><a name="ESPEasy_device_set_pcapwm">pcapwm</a><br>
- Control PCA9685 (16-channel / 12-bit PWM I2C-bus controller)<br>
- <ul>
- <li>arguments: <code><pin 0-15> <level 0-4095></code></li>
- <li>example: <code>set <esp> pcapwm 15 4095</code></li>
- </ul>
- </li><br>
- </ul>
- <u>ESPEasy motor control commands:</u><br><br>
- <ul>
- <li><a name="ESPEasy_device_set_servo">Servo</a><br>
- Direct control of servo motors<br>
- <ul>
- <li>arguments: <code><servoNo> <pin> <position></code></li>
- <li>example: <code>set <esp> servo 1 14 100</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_motorshieldcmd">MotorShieldCMD</a><br>
- Control a DC motor or stepper<br>
- <ul>
- <li>
- arguments: <code>DCMotor <motornumber> <forward|backward|release> <speed></code><br>
- arguments: <code>Stepper <motornumber> <forward|backward|release> <steps> <single|double|interleave|microstep></code>
- </li>
- <li>
- example: <code>set <esp> MotorShieldCMD DCMotor 1 forward 10</code><br>
- example: <code>set <esp> MotorShieldCMD Stepper 1 backward 25 single</code>
- </li>
- </ul>
- </li><br>
- </ul>
- <u>ESPEasy display related commands:</u><br>
- <ul><br>
- <li><a name="ESPEasy_device_set_lcd">lcd</a><br>
- Write text messages to LCD screen<br>
- Pay attention to attributes
- <a href="#ESPEasy_device_attr_displaytextencode">displayTextEncode</a> and
- <a href="#ESPEasy_device_attr_displaytextwidth">displayTextWidth</a>.<br>
- <ul>
- <li>arguments: <code><row> <col> <text></code></li>
- <li>example: <code>set <esp> lcd 1 1 Test a b c</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_lcdcmd">lcdcmd</a><br>
- Control LCD screen<br>
- <ul>
- <li>arguments: <code><on|off|clear></code></li>
- <li>example: <code>set <esp> lcdcmd clear</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_oled">oled</a><br>
- Write text messages to OLED screen<br>
- Pay attention to attributes
- <a href="#ESPEasy_device_attr_displaytextencode">displayTextEncode</a> and
- <a href="#ESPEasy_device_attr_displaytextwidth">displayTextWidth</a>.<br>
- <ul>
- <li>arguments: <code><row> <col> <text></code></li>
- <li>example: <code>set <esp> oled 1 1 Test a b c</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_oledcmd">oledcmd</a><br>
- Control OLED screen<br>
- <ul>
- <li>arguments: <code><on|off|clear></code></li>
- <li>example: <code>set <esp> oledcmd clear</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_oledframedcmd">oledframedcmd</a><br>
- Switch oledframed on/off<br>
- <ul>
- <li>arguments: <code><on|off></code></li>
- <li>example: <code>set <esp> oledframedcmd on</code></li>
- </ul>
- </li><br>
- </ul>
- <u>ESPEasy DMX related commands:</u>
- <ul><br>
- <li><a name="ESPEasy_device_set_dmx">dmx</a><br>
- Send DMX commands to a device<br>
- <ul>
- <li>arguments: <code><on|off|log|value|channel=value[,value][...]></code></li>
- <li>example: <code>set <esp> dmx 1=255,2=127</code></li>
- </ul>
- </li><br>
- </ul>
- <u>ESPEasy LED/Lights related commands:</u>
- <ul><br>
- <li><a name="ESPEasy_device_set_lights">Lights</a> (plugin can be found <a
- href="https://github.com/ddtlabs/ESPEasy-Plugin-Lights target="_NEW">here</a>)<br>
- Control a rgb or ct light<br>
- <ul>
- <li>arguments: <code><rgb|ct|pct|on|off|toggle> [<hex-rgb|color-temp|pct-value>] [<fading time>]</code></li>
- <li>examples:<br>
- <code>set <esp> lights rgb aa00aa</code><br>
- <code>set <esp> lights rgb aa00aa 10</code><br>
- <code>set <esp> lights ct 3200</code><br>
- <code>set <esp> lights ct 3200 10</code><br>
- <code>set <esp> lights pct 50</code><br>
- <code>set <esp> lights on</code><br>
- <code>set <esp> lights off</code><br>
- <code>set <esp> lights toggle</code><br>
- </li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_nfx">nfx</a> (plugin can be found
- <a target="_blank" href="https://github.com/djcysmic/NeopixelBusFX">here</a>)<br>
- Control nfx plugin<br>
- Note: To use FHEMWEB's colorpicker and slider widgets you have to set
- Attribut <a href="ESPEasy_device_attr_maplightscmds">mapLightCmds</a>.
- <ul>
- <li>arguments: <code><off|on|dim|line|one|all|rgb|fade|colorfade|rainbow|kitt|comet|theatre|scan|dualscan|twinkle|twinklefade|sparkle|wipe|fire|stop|statusrequest|fadetime|fadedelay|speed|count|bgcolor> [<rgb>] [<fadeing time>] [<delay time>]</code></li>
- <li>examples:<br>
- <code>
- set <esp> nfx all 00ff00 100<br>
- set <esp> nfx rgb aa00ff 1000 10<br>
- set <esp> nfx line 0 100 f0f0f0c<br>
- </code>
- </li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_candle">candle</a><br>
- Control candle rgb plugin<br>
- <ul>
- <li>arguments:
- <code>CANDLE:<FlameType>:<Color>:<Brightness></code></li>
- <li>example: <code>set <esp> CANDLE:4:FF0000:200</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_neopixel">neopixel</a><br>
- Control neopixel plugin (single LED)<br>
- <ul>
- <li>arguments: <code><led nr> <red 0-255> <green 0-255> <blue 0-255></code></li>
- <li>example: <code>set <esp> neopixel 1 255 255 255</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_neopixelall">neopixelall</a><br>
- Control neopixel plugin (all together)<br>
- <ul>
- <li>arguments: <code><red 0-255> <green 0-255> <blue 0-255></code></li>
- <li>example: <code>set <esp> neopixelall 255 255 255</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_neopixelline">neopixelline</a><br>
- Control neopixel plugin (line)<br>
- <ul>
- <li>arguments: <code><start led no> <stop led no> <red 0-255> <green 0-255> <blue 0-255></code></li>
- <li>example: <code>set <esp> neopixelline 1 5 0 127 255</code></li>
- </ul>
- </li><br>
- </ul>
- <u>ESPEasy sound related commands:</u>
- <ul><br>
- <li><a name="ESPEasy_device_set_tone">tone</a><br>
- Play a tone on a pin via a speaker or piezo element (ESPEasy >=
- 2.0.0-dev6)
- <br>
- <ul>
- <li>arguments: <code><pin> <freq Hz> <duration s></code></li>
- <li>example: <code>set <esp> tone 14 4000 1</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_rtttl">rtttl</a><br>
- Play melodies via <a target="_NEW"
- href="https://en.wikipedia.org/wiki/Ring_Tone_Transfer_Language#Technical_specification">RTTTL</a>
- (ESPEasy >= 2.0.0-dev6)
- <br>
- <ul>
- <li>arguments: <rtttl> <pin>:<rtttl codes></li>
- <li>example: <code>set <esp> rtttl 14:d=10,o=6,b=180,c,e,g</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_buzzer">buzzer</a><br>
- Beep a short time<br>
- <ul>
- <li>arguments: <code>none</code></li>
- <li>example: <code>set <esp> buzzer</code></li>
- </ul>
- </li><br>
- </ul>
- <u>ESPEasy miscellaneous commands:</u>
- <ul><br>
- <li><a name="ESPEasy_device_set_event">Event</a><br>
- Trigger an ESP event. Such events can be used in ESP Easy rules.<br>
- <ul>
- <li>arguments: <code><string></code></li>
- <li>example: <code>set <esp> event testevent</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_irsend">irsend</a><br>
- Send ir codes via "Infrared Transmit" Plugin<br>
- Supported protocols are: NEC, JVC, RC5, RC6, SAMSUNG, SONY, PANASONIC at
- the moment. As long as official documentation is missing you can find
- some details here:
- <a href="http://www.letscontrolit.com/forum/viewtopic.php?f=5&t=328" target="_NEW">
- IR Transmitter thread #1</a> and
- <a
- href="https://www.letscontrolit.com/forum/viewtopic.php?t=328&start=61" target="_NEW">
- IR Transmitter thread #61</a>.<br>
- <ul>
- <li>
- arguments: <code><NEC|JVC|RC5|RC6|SAMSUNG|SONY|PANASONIC> <hex code> <bit length></code><br>
- arguments: <code><RAW> <B32 raw> <frequenz> <pulse length> <blank length></code>
- </li>
- <li>
- example: <code>set <esp> irsend NEC 7E81542B 32</code><br>
- example: <code>set <esp> irsend RAW 3U0GGL8AGGK588A22K58ALALALAGL1A22LAK45ALALALALALALALALAL1AK5 38 512 256</code>
- </li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_serialsend">serialsend</a><br>
- Used for ser2net plugin<br>
- <ul>
- <li>arguments: <code><string></code></li>
- <li>example: <code>set <esp> serialsend test</code></li>
- </ul>
- </li><br>
- </ul>
- <u>ESPEasy administrative commands</u> (be careful !!!):
- <ul><br>
- <li><a name="ESPEasy_device_set_erase">erase</a><br>
- Wipe out ESP flash memory<br>
- <ul>
- <li>arguments: <code>none</code></li>
- <li>example: <code>set <esp> erase</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_reboot">reboot</a><br>
- Used to reboot your ESP<br>
- <ul>
- <li>arguments: <code>none</code></li>
- <li>example: <code>set <esp> reboot</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_reset">reset</a><br>
- Do a factory reset on the ESP<br>
- <ul>
- <li>arguments: <code>none</code></li>
- <li>example: <code>set <esp> reset</code></li>
- </ul>
- </li><br>
- </ul>
- <u>ESPEasy experimental commands:</u> (The following commands can be changed or removed at any time)
- <ul><br>
- <li><a name="ESPEasy_device_set_rgb">rgb</a><br>
- Used to control a rgb light wo/ an ESPEasy plugin.<br>
- You have to set attribute <a href="#ESPEasy_device_attr_rgbgpios">rgbGPIOs</a> to
- enable this feature. Default colorpicker mode is HSVp but can be adjusted
- with help of attribute <a href="#ESPEasy_device_attr_colorpicker">colorpicker</a>
- to HSV or RGB. Set attribute <a href="#webCmd">webCmd</a> to rgb to
- display a colorpicker in FHEMWEB room view and on detail page.<br>
- <ul>
- <li>
- arguments: <code><rrggbb>|on|off|toggle</code>
- </li>
- <li>
- examples:<br>
- <code>set <esp> rgb 00FF00</code><br>
- <code>set <esp> rgb on</code><br>
- <code>set <esp> rgb off</code><br>
- <code>set <esp> rgb toggle</code><br>
- </li>
- <li>Full featured example:<br>
- attr <ESP> colorpicker HSVp<br>
- attr <ESP> devStateIcon { ESPEasy_devStateIcon($name) }<br>
- attr <ESP> Interval 30<br>
- attr <ESP> parseCmdResponse status,pwm<br>
- attr <ESP> pollGPIOs D6,D7,D8<br>
- attr <ESP> rgbGPIOs D6,D7,D8<br>
- attr <ESP> webCmd rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:toggle:on:off
- </li>
- </ul>
- </li><br>
- </ul>
- <u>ESPEasy deprecated commands:</u> (will be removed in a later version)
- <ul><br>
- <li><a name="ESPEasy_device_set_status">status</a><br>
- Request esp device status (eg. gpio)<br>
- See attributes: parseCmdResponse, readingPrefixGPIO, readingSuffixGPIOState
- <ul>
- <li>arguments: <code><pin></code></li>
- <li>example: <code>set <esp> status 14</code></li>
- </ul>
- </li><br>
- </ul>
- <br><a name="ESPEasy_device_attr"></a>
- <b>Attributes</b> (logical device)<br><br>
- <ul>
- <li><a name="ESPEasy_device_attr_adjustvalue">adjustValue</a><br>
- Used to adjust sensor values<br>
- Must be a space separated list of <reading>:<formula>.
- Reading can be a regexp. Formula can be an arithmetic expression like
- 'round(($VALUE-32)*5/9,2)'.
- If $VALUE is omitted in formula then it will be added to the beginning of
- the formula. So you can simple write 'temp:+20' or '.*:*4'<br>
- Modified or ignored values are marked in the system log (verbose 4). Use
- verbose 5 logging to see more details.<br>
- If the used sub function returns 'undef' then the value will be ignored.
- <br>
- The following variables can be used if necessary:
- <ul>
- <li>$VALUE contains the original value</li>
- <li>$READING contains the reading name</li>
- <li>$NAME contains the device name</li>
- </ul>
- Default: none<br>
- Eg. <code>attr ESPxx adjustValue humidity:+0.1
- temperature*:($VALUE-32)*5/9</code><br>
- Eg. <code>attr ESPxx adjustValue
- .*:my_OwnFunction($NAME,$READING,$VALUE)</code><br>
- <br>
- Sample function to ignore negative values:<br>
- <code>
- sub my_OwnFunction($$$) {<br>
- my ($name,$reading,$value) = @_;<br>
- return ($value < 0) ? undef : $value;<br>
- }<br>
- </code>
- </li><br>
- <li><a name="ESPEasy_device_attr_colorpicker">colorpicker</a><br>
- Used to select colorpicker mode<br>
- Possible values: RGB,HSV,HSVp<br>
- Default: HSVp
- </li><br>
- <li><a name="ESPEasy_device_attr_colorpickerctcw">colorpickerCTcw</a><br>
- Used to select ct colorpicker's cold white color temperature in Kelvin<br>
- Possible values: > colorpickerCTww<br>
- Default: 6000
- </li><br>
- <li><a name="ESPEasy_device_attr_colorpickerctww">colorpickerCTww</a><br>
- Used to select ct colorpicker's warm white color temperature in Kelvin<br>
- Possible values: < colorpickerCTcw<br>
- Default: 2000
- </li><br>
- <li><a name="ESPEasy_device_attr_disable">disable</a><br>
- Used to disable device<br>
- Possible values: 0,1<br>
- Default: 0
- </li><br>
- <li><a name="ESPEasy_device_attr_displaytextencode">displayTextEncode</a><br>
- Used to disable url encoding for text that is send to oled/lcd displays.
- Useful if you want to encode the text by yourself.<br>
- Possible values: 0,1<br>
- Default: 1 (enabled)
- </li><br>
- <li><a name="ESPEasy_device_attr_displaytextwidth">displayTextWidth</a><br>
- Used to specify number of characters per display line.<br>
- If set then all characters before and after the text on the same line will
- be overwritten with spaces. Attribute
- <a href="#ESPEasy_device_attr_displaytextencode">displayTextEncode</a> must not be
- disabled to use this feature. A 128x64px display has 16 characters per
- line if you are using a 8px font.<br>
- Possible values: integer<br>
- Default: 0 (disabled)
- </li><br>
- <li><a name="ESPEasy_device_attr_interval">Interval</a><br>
- Used to set polling interval for presence check and GPIOs polling in
- seconds. 0 will disable this feature.<br>
- Possible values: secs > 10.<br>
- Default: 300
- </li><br>
- <li><a href="#IODev">IODev</a><br>
- Used to select I/O device (ESPEasy Bridge).
- </li><br>
- <li><a name="ESPEasy_device_attr_maplightscmds">mapLightCmds</a><br>
- Enable the following commands and map them to the specified ESPEasy
- command: rgb, ct, pct, on, off, toggle, dim, line, one, all, fade,
- colorfade, rainbow, kitt, comet, theatre, scan, dualscan, twinkle,
- twinklefade, sparkle, wipe, fire, stop, fadetime, fadedelay, count, speed,
- bgcolor. Ask the ESPEasy maintainer to add more if required.<br>
- Needed to use FHEM's colorpicker or slider widgets to control a
- rgb/ct/effect/... plugin.<br>
- required values: <code>a valid set command</code><br>
- eg. <code>attr <esp> mapLightCmds Lights</code>
- </li><br>
- <li><a name="ESPEasy_device_attr_presencecheck">presenceCheck</a><br>
- Used to enable/disable presence check for ESPs<br>
- Presence check determines the presence of a device by readings age. If any
- reading of a device is newer than <a href="#ESPEasy_device_attr_interval">interval</a>
- seconds then it is marked as being present. This kind of check works for
- ESP devices in deep sleep too but require at least 1 reading that is
- updated regularly. Therefore the ESP must send the corresponding data
- regularly (ESP device option "delay").<br>
- Possible values: 0,1<br>
- Default: 1 (enabled)
- </li><br>
- <li><a name="ESPEasy_device_attr_readingswitchtext">readingSwitchText</a><br>
- Use on,off instead of 1,0 for readings if ESP device is a switch.<br>
- Possible values: 0,1<br>
- Default: 1 (enabled)
- </li><br>
- <li><a name="ESPEasy_device_attr_setstate">setState</a><br>
- Summarize received values in state reading.<br>
- A positive number determines the number of characters used for abbreviated
- reading names. Only readings with an age less than
- <a href="#ESPEasy_device_attr_interval">interval</a> will be considered. If your are
- not satisfied with format or behavior of setState then disable this
- attribute (set to 0) and use global attributes userReadings and/or
- stateFormat to get what you want.<br>
- Possible values: integer >=0<br>
- Default: 3 (enabled with 3 characters abbreviation)
- </li><br>
- The following two attributes should only be use in cases where ESPEasy
- software do not send data on status changes and no rule/dummy can be used
- to do that. Useful for commands like PWM, STATUS, ...
- <br><br>
- <li><a name="ESPEasy_device_attr_parsecmdresponse">parseCmdResponse</a><br>
- Used to parse response of commands like GPIO, PWM, STATUS, ...<br>
- Specify a module command or comma separated list of commands as argument.
- Commands are case insensitive.<br>
- Only necessary if ESPEasy software plugins do not send their data
- independently. Useful for commands like STATUS, PWM, ...<br>
- Possible values: <set cmd>[,<set cmd>][,...]<br>
- Default: status<br>
- Eg. <code>attr ESPxx parseCmdResponse status,pwm</code>
- </li><br>
- <li><a name="ESPEasy_device_attr_pollgpios">pollGPIOs</a><br>
- Used to enable polling for GPIOs status. This polling will do same as
- command 'set ESPxx status <device> <pin>'<br>
- Possible values: GPIO number or comma separated GPIO number list<br>
- Default: none<br>
- Eg. <code>attr ESPxx pollGPIOs 13,D7,D2</code>
- </li>
- <br>
- The following two attributes control naming of readings that are
- generated by help of parseCmdResponse and pollGPIOs (see above)
- <br><br>
- <li><a name="ESPEasy_device_attr_readingprefixgpio">readingPrefixGPIO</a><br>
- Specifies a prefix for readings based on GPIO numbers. For example:
- "set ESPxx pwm 13 512" will switch GPIO13 into pwm mode and set pwm to
- 512. If attribute readingPrefixGPIO is set to PIN and attribut
- <a href="#ESPEasy_device_attr_parsecmdresponse">parseCmdResponse</a> contains pwm
- command then the reading name will be PIN13.<br>
- Possible Values: <code>string</code><br>
- Default: GPIO
- </li><br>
- <li><a name="ESPEasy_device_attr_readingsuffixgpiostate">readingSuffixGPIOState</a><br>
- Specifies a suffix for the state-reading of GPIOs (see Attribute
- <a href="#ESPEasy_device_attr_pollgpios">pollGPIOs</a>)<br>
- Possible Values: <code>string</code><br>
- Default: no suffix<br>
- Eg. attr ESPxx readingSuffixGPIOState _state
- </li><br>
- <li>
- <a href="#readingFnAttributes">readingFnAttributes</a>
- </li><br>
- <b>Experimental</b> (The following attributes can be changed or removed at
- any time):<br><br>
- <li><a name="ESPEasy_device_attr_rgbgpios">rgbGPIOs</a><br>
- Use to define GPIOs your lamp is conneted to. Must be set to be able to
- use <a href="#ESPEasy_device_set_rgb">rgb</a> set command.<br>
- Possible values: Comma separated tripple of ESP pin numbers or arduino pin
- names<br>
- Eg: 12,13,15<br>
- Eg: D6,D7,D8<br>
- Default: none
- </li><br>
- </ul>
- </ul>
- =end html
- =cut
|