| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769 |
- # $Id: 34_ESPEasy.pm 16307 2018-03-02 06:45:27Z 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.38"; # 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",
- "longpulse_ms" => "3",
- "servo" => "3",
- "lcd" => "3",
- "lcdcmd" => "1",
- "mcpgpio" => "2",
- "mcppulse" => "3", # forum 82174
- "mcplongpulse" => "3", # forum 82174
- "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>",
- "longpulse_ms" => "longpulse_ms <pin> <0|1|off|on> <duration>",
- "servo" => "servo <servoNo> <pin> <position>",
- "lcd" => "lcd <row> <col> <text>",
- "lcdcmd" => "lcdcmd <on|off|clear>",
- "mcpgpio" => "mcpgpio <port> <0|1|off|on>",
- "mcppulse" => "mcppulse <port> <0|1|off|on> <duration>", # forum 82174
- "mcplongpulse" => "mcplongpulse <port> <0|1|off|on> <duration>", # forum 82174
- "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;
- }
- elsif ($params[$pp-1] =~ m/^toggle$/i) {
- $state = "toggle";
- }
- 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 $eecmd = $params[0];
- my $usage = $ESPEasy_setCmdsUsage{$eecmd};
- $usage =~ s/Note:/\nNote:/g if $usage;
- return $usage ? "Usage: set $name $usage" : "unknown command or help not available for '$eecmd'";
- }
- 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 $cd = ESPEasy_isCombineDevices($peer,$espName,AttrVal($bname,"combineDevices",0));
- my $ident = $cd
- ? $espName ne "" ? $espName : $peer
- : $espName.($espName ne "" && $espDevName ne "" ? "_" : "").$espDevName;
- my $d0;
- Log3 $bname, 4, "$btype $name: Src:'$json->{data}{ESP}{name}'/'"
- . "$json->{data}{SENSOR}{0}{deviceName}' => ident:$ident "
- . "dev:"
- . ( ($d0=(devspec2array("i:IDENT=$ident:FILTER=i:TYPE=$btype"))[0])
- ? $d0
- : "undef" )
- . " combinedDevice:".$cd;
- # 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>
- <h4>ESPEasy Bridge</h4>
- <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>
- <h4>ESPEasy Device</h4>
- <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 (duration in s)<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_logpulse_ms">LongPulse_ms</a><br>
- Direct pulse control of output pins (duration in ms)<br>
- <ul>
- <li>arguments: <code><pin> <0|1|off|on> <duration></code></li>
- <li>example: <code>set <esp> longpulse_ms 14 on 10000</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_mcppulse">MCPPulse</a><br>
- Pulse control on MCP23017 output pins (duration in ms)<br>
- <ul>
- <li>arguments: <code><port> <0|1|off|on> <duration></code></li>
- <li>example: <code>set <esp> MCPPulse 48 on 100</code></li>
- </ul>
- </li><br>
- <li><a name="ESPEasy_device_set_mcplongpulse">MCPLongPulse</a><br>
- Longpulse control on MCP23017 output pins (duration in s)<br>
- <ul>
- <li>arguments: <code><port> <0|1|off|on> <duration></code></li>
- <li>example: <code>set <esp> MCPLongPulse 48 on 2</code></li>
- </ul>
- </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
|