10_EnOcean.pm 890 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981
  1. ##############################################
  2. # $Id: 10_EnOcean.pm 17689 2018-11-05 18:02:32Z klaus.schauer $
  3. package main;
  4. use strict;
  5. use warnings;
  6. my $cryptFunc;
  7. my $xmlFunc;
  8. my $xml;
  9. eval "use Crypt::Rijndael";
  10. if ($@) {
  11. $cryptFunc = 0;
  12. } else {
  13. $cryptFunc = 1;
  14. }
  15. eval "use Crypt::Random qw(makerandom)";
  16. if ($@) {
  17. $cryptFunc = 0;
  18. } else {
  19. $cryptFunc = $cryptFunc == 1 ? 1 : 0;
  20. }
  21. eval "use XML::Simple";
  22. if ($@) {
  23. $xmlFunc = 0;
  24. } else {
  25. $xmlFunc = 1;
  26. $xml = new XML::Simple;
  27. }
  28. eval "use Data::Dumper";
  29. if ($@) {
  30. $xmlFunc = 0;
  31. } else {
  32. $xmlFunc = $xmlFunc == 1 ? 1 : 0;
  33. }
  34. use SetExtensions;
  35. sub EnOcean_Define($$);
  36. sub EnOcean_Initialize($);
  37. sub EnOcean_Parse($$);
  38. sub EnOcean_Get($@);
  39. sub EnOcean_Set($@);
  40. sub EnOcean_hvac_01Cmd($$$);
  41. sub EnOcean_roomCtrlPanel_00Snd($$$$$$$$);
  42. sub EnOcean_CheckSenderID($$$);
  43. sub EnOcean_SndRadio($$$$$$$$);
  44. sub EnOcean_ReadingScaled($$$$);
  45. sub EnOcean_TimerSet($);
  46. sub EnOcean_Undef($$);
  47. my %EnO_rorgname = (
  48. "A5" => "4BS", # 4BS, org 07
  49. "A6" => "ADT", # adressing destination telegram
  50. "A7" => "SMREC", # Smart Ack Relaim
  51. "B0" => "GPTI", # GP teach-in request
  52. "B1" => "GPTR", # GP teach-in response
  53. "B2" => "GPCD", # GP complete data
  54. "B3" => "GPSD", # GP selective data
  55. "C5" => "SYSEX", # remote management >> packet type 7 used
  56. "C6" => "SMLRNREQ", # Smart Ack Learn Request
  57. "C7" => "SMLRNANS", # Smart Ack Learn Answer
  58. "D0" => "SIGNAL", # Smart Ack Mail Box Functions
  59. "D1" => "MSC", # MSC
  60. "D2" => "VLD", # VLD
  61. "D4" => "UTE", # UTE
  62. "D5" => "contact", # 1BS, org 06
  63. "F6" => "switch", # RPS, org 05
  64. "30" => "SEC", # secure telegram
  65. "31" => "ENC", # secure telegram with encapsulation
  66. "32" => "SECD", # decrypted secure telegram
  67. "35" => "STE", # secure Teach-In
  68. "40" => "CDM", # chained data message
  69. );
  70. # switch commands
  71. my @EnO_ptm200btn = ("AI", "A0", "BI", "B0", "CI", "C0", "DI", "D0");
  72. my %EnO_ptm200btn;
  73. # switch.00 commands
  74. my %EnO_switch_00Btn = (
  75. "A0" => 14,
  76. "AI" => 13,
  77. "B0" => 12,
  78. "BI" => 11,
  79. "A0,B0" => 7,
  80. "A0,BI" => 10,
  81. "AI,B0" => 5,
  82. "AI,BI" => 9,
  83. "pressed" => 8,
  84. "pressed34" => 6,
  85. "released" => 15,
  86. "teachInSec" => 253,
  87. "teachOut" => 254,
  88. "teachIn" => 255
  89. );
  90. # gateway commands
  91. my @EnO_gwCmd = ("switching", "dimming", "setpointShift", "setpointBasic", "controlVar", "fanStage", "blindCmd");
  92. my %EnO_gwCmd = (
  93. "switching" => 1,
  94. "dimming" => 2,
  95. "setpointShift" => 3,
  96. "setpointBasic" => 4,
  97. "controlVar" => 5,
  98. "fanStage" => 6,
  99. "blindCmd" => 7,
  100. );
  101. # Some Manufacturers (e.g. Jaeger Direkt) also sell EnOcean products without an entry in the table below.
  102. my %EnO_manuf = (
  103. "000" => "Reserved",
  104. "001" => "Peha",
  105. "002" => "Thermokon",
  106. "003" => "Servodan",
  107. "004" => "EchoFlex Solutions",
  108. "005" => "AWAG Elektrotechnik AG (Omnio)",
  109. "006" => "Hardmeier electronics",
  110. "007" => "Regulvar Inc",
  111. "008" => "Ad Hoc Electronics",
  112. "009" => "Distech Controls",
  113. "00A" => "Kieback + Peter",
  114. "00B" => "EnOcean GmbH",
  115. "00C" => "Probare",
  116. "00D" => "Eltako",
  117. "00E" => "Leviton",
  118. "00F" => "Honeywell",
  119. "010" => "Spartan Peripheral Devices",
  120. "011" => "Siemens",
  121. "012" => "T-Mac",
  122. "013" => "Reliable Controls Corporation",
  123. "014" => "Elsner Elektronik GmbH",
  124. "015" => "Diehl Controls",
  125. "016" => "BSC Computer",
  126. "017" => "S+S Regeltechnik GmbH",
  127. "018" => "Masco Corporation",
  128. "019" => "Intesis Software SL",
  129. "01A" => "Viessmann",
  130. "01B" => "Lutuo Technology",
  131. "01C" => "Schneider Electric",
  132. "01D" => "Sauter",
  133. "01E" => "Boot-Up",
  134. "01F" => "Osram Sylvania",
  135. "020" => "Unotech",
  136. "021" => "Delta Controls Inc",
  137. "022" => "Unitronic AG",
  138. "023" => "NanoSense",
  139. "024" => "The S4 Group",
  140. "025" => "MSR Solutions",
  141. "026" => "GE",
  142. "027" => "Maico",
  143. "028" => "Ruskin Company",
  144. "029" => "Magnum Engery Solutions",
  145. "02A" => "KM Controls",
  146. "02B" => "Ecologix Controls",
  147. "02C" => "Trio 2 Sys",
  148. "02D" => "Afriso-Euro-Index",
  149. "030" => "NEC AccessTechnica Ltd",
  150. "031" => "ITEC Corporation",
  151. "032" => "Simix Co Ltd",
  152. "033" => "Permundo GmbH",
  153. "034" => "Eurotronic Technology GmbH",
  154. "035" => "Art Japan Co Ltd",
  155. "036" => "Tiansu Automation Control System Co Ltd",
  156. "038" => "Gruppo Giordano Idea Spa",
  157. "039" => "alphaEOS AG",
  158. "03A" => "Tag Technologies",
  159. "03C" => "Cloud Buildings Ltd",
  160. "03E" => "GIGA Concept",
  161. "03F" => "Sensortec",
  162. "040" => "Jaeger Direkt",
  163. "041" => "Air System Components Inc",
  164. "043" => "SODA GmbH",
  165. "045" => "Holter",
  166. "046" => "ID-RF",
  167. "049" => "Micropelt GmbH",
  168. "7FF" => "Multi user Manufacturer ID",
  169. );
  170. my %EnO_eepConfig = (
  171. "A5.02.01" => {attr => {subType => "tempSensor.01"}, GPLOT => "EnO_temp4:Temp,"},
  172. "A5.02.02" => {attr => {subType => "tempSensor.02"}, GPLOT => "EnO_temp4:Temp,"},
  173. "A5.02.03" => {attr => {subType => "tempSensor.03"}, GPLOT => "EnO_temp4:Temp,"},
  174. "A5.02.04" => {attr => {subType => "tempSensor.04"}, GPLOT => "EnO_temp4:Temp,"},
  175. "A5.02.05" => {attr => {subType => "tempSensor.05"}, GPLOT => "EnO_temp4:Temp,"},
  176. "A5.02.06" => {attr => {subType => "tempSensor.06"}, GPLOT => "EnO_temp4:Temp,"},
  177. "A5.02.07" => {attr => {subType => "tempSensor.07"}, GPLOT => "EnO_temp4:Temp,"},
  178. "A5.02.08" => {attr => {subType => "tempSensor.08"}, GPLOT => "EnO_temp4:Temp,"},
  179. "A5.02.09" => {attr => {subType => "tempSensor.09"}, GPLOT => "EnO_temp4:Temp,"},
  180. "A5.02.0A" => {attr => {subType => "tempSensor.0A"}, GPLOT => "EnO_temp4:Temp,"},
  181. "A5.02.0B" => {attr => {subType => "tempSensor.0B"}, GPLOT => "EnO_temp4:Temp,"},
  182. "A5.02.10" => {attr => {subType => "tempSensor.10"}, GPLOT => "EnO_temp4:Temp,"},
  183. "A5.02.11" => {attr => {subType => "tempSensor.11"}, GPLOT => "EnO_temp4:Temp,"},
  184. "A5.02.12" => {attr => {subType => "tempSensor.12"}, GPLOT => "EnO_temp4:Temp,"},
  185. "A5.02.13" => {attr => {subType => "tempSensor.13"}, GPLOT => "EnO_temp4:Temp,"},
  186. "A5.02.14" => {attr => {subType => "tempSensor.14"}, GPLOT => "EnO_temp4:Temp,"},
  187. "A5.02.15" => {attr => {subType => "tempSensor.15"}, GPLOT => "EnO_temp4:Temp,"},
  188. "A5.02.16" => {attr => {subType => "tempSensor.16"}, GPLOT => "EnO_temp4:Temp,"},
  189. "A5.02.17" => {attr => {subType => "tempSensor.17"}, GPLOT => "EnO_temp4:Temp,"},
  190. "A5.02.18" => {attr => {subType => "tempSensor.18"}, GPLOT => "EnO_temp4:Temp,"},
  191. "A5.02.19" => {attr => {subType => "tempSensor.19"}, GPLOT => "EnO_temp4:Temp,"},
  192. "A5.02.1A" => {attr => {subType => "tempSensor.1A"}, GPLOT => "EnO_temp4:Temp,"},
  193. "A5.02.1B" => {attr => {subType => "tempSensor.1B"}, GPLOT => "EnO_temp4:Temp,"},
  194. "A5.02.20" => {attr => {subType => "tempSensor.20"}, GPLOT => "EnO_temp4:Temp,"},
  195. "A5.02.30" => {attr => {subType => "tempSensor.30"}, GPLOT => "EnO_temp4:Temp,"},
  196. "A5.04.01" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
  197. "A5.04.02" => {attr => {subType => "tempHumiSensor.02"}, GPLOT => "EnO_temp4humi6:Temp/Humi,EnO_voltage4:Voltage,"},
  198. "A5.04.03" => {attr => {subType => "tempHumiSensor.03"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
  199. "A5.05.01" => {attr => {subType => "baroSensor.01"}, GPLOT => "EnO_airPressure4:Airpressure,"},
  200. "A5.06.01" => {attr => {subType => "lightSensor.01"}, GPLOT => "EnO_brightness4:Brightness,EnO_voltage4:Voltage,"},
  201. "A5.06.02" => {attr => {subType => "lightSensor.02"}, GPLOT => "EnO_brightness4:Brightness,EnO_voltage4:Voltage,"},
  202. "A5.06.03" => {attr => {subType => "lightSensor.03"}, GPLOT => "EnO_brightness4:Brightness,"},
  203. "A5.06.04" => {attr => {subType => "lightSensor.04"}, GPLOT => "EnO_temp4brightness4:Temp/Brightness,"},
  204. "A5.06.05" => {attr => {subType => "lightSensor.05"}, GPLOT => "EnO_brightness4:Brightness,EnO_voltage4:Voltage,"},
  205. "A5.07.01" => {attr => {subType => "occupSensor.01"}, GPLOT => "EnO_motion:Motion,EnO_voltage4current4:Voltage/Current,"},
  206. "A5.07.02" => {attr => {subType => "occupSensor.02"}, GPLOT => "EnO_motion:Motion4brightness4:Motion/Brightness,EnO_voltage4:Voltage,"},
  207. "A5.07.03" => {attr => {subType => "occupSensor.03"}, GPLOT => "EnO_motion:Motion4brightness4:Motion/Brightness,EnO_voltage4:Voltage,"},
  208. "A5.08.01" => {attr => {subType => "lightTempOccupSensor.01"}, GPLOT => "EnO_temp4brightness4:Temp/Brightness,EnO_voltage4:Voltage,"},
  209. "A5.08.02" => {attr => {subType => "lightTempOccupSensor.02"}, GPLOT => "EnO_temp4brightness4:Temp/Brightness,EnO_voltage4:Voltage,"},
  210. "A5.08.03" => {attr => {subType => "lightTempOccupSensor.03"}, GPLOT => "EnO_temp4brightness4:Temp/Brightness,EnO_voltage4:Voltage,"},
  211. "A5.09.01" => {attr => {subType => "COSensor.01"}, GPLOT => "EnO_A5-09-01:CO/Temp,"},
  212. "A5.09.02" => {attr => {subType => "COSensor.02"}, GPLOT => "EnO_A5-09-02:CO/Temp,EnO_voltage4:Voltage,"},
  213. "A5.09.04" => {attr => {subType => "tempHumiCO2Sensor.01"}, GPLOT => "EnO_CO2:CO2,EnO_temp4humi6:Temp/Humi,"},
  214. "A5.09.05" => {attr => {subType => "vocSensor.01"}, GPLOT => "EnO_A5-09-05:Concentration,"},
  215. "A5.09.06" => {attr => {subType => "radonSensor.01"}, GPLOT => "EnO_A5-09-06:Radon,"},
  216. "A5.09.07" => {attr => {subType => "particlesSensor.01"}, GPLOT => "EnO_A5-09-07:Particles,"},
  217. "A5.09.08" => {attr => {subType => "CO2Sensor.01"}, GPLOT => "EnO_CO2:CO2,"},
  218. "A5.09.09" => {attr => {subType => "CO2Sensor.01"}, GPLOT => "EnO_CO2:CO2,"},
  219. "A5.09.0A" => {attr => {subType => "HSensor.01"}, GPLOT => "EnO_A5-09-0A:H/Temp,EnO_voltage4:Voltage,"},
  220. "A5.09.0B" => {attr => {subType => "radiationSensor.01"}, GPLOT => "EnO_radioactivity4/Radioactivity,EnO_voltage4:Voltage,"},
  221. "A5.09.0C" => {attr => {subType => "vocSensor.01"}, GPLOT => "EnO_A5-09-05:Concentration,"},
  222. "A5.10.01" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  223. "A5.10.02" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  224. "A5.10.03" => {attr => {subType => "roomSensorControl.05", comMode => "confirm", subDef => "getNextID"}, GPLOT => "EnO_temp4:Temp,"},
  225. "A5.10.04" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  226. "A5.10.05" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  227. "A5.10.06" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  228. "A5.10.07" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  229. "A5.10.08" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  230. "A5.10.09" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  231. "A5.10.0A" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  232. "A5.10.0B" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  233. "A5.10.0C" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  234. "A5.10.0D" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
  235. "A5.10.10" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
  236. "A5.10.11" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
  237. "A5.10.12" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
  238. "A5.10.13" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
  239. "A5.10.14" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
  240. "A5.10.15" => {attr => {subType => "roomSensorControl.02"}, GPLOT => "EnO_temp4:Temp,"},
  241. "A5.10.16" => {attr => {subType => "roomSensorControl.02"}, GPLOT => "EnO_temp4:Temp,"},
  242. "A5.10.17" => {attr => {subType => "roomSensorControl.02"}, GPLOT => "EnO_temp4:Temp,"},
  243. "A5.10.18" => {attr => {subType => "roomSensorControl.18"}, GPLOT => "EnO_temp4:Temp,"},
  244. "A5.10.19" => {attr => {subType => "roomSensorControl.19"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
  245. "A5.10.1A" => {attr => {subType => "roomSensorControl.1A"}, GPLOT => "EnO_temp4:Temp,EnO_voltage4:Voltage,"},
  246. "A5.10.1B" => {attr => {subType => "roomSensorControl.1B"}, GPLOT => "EnO_temp4:Temp,EnO_voltage4:Voltage,"},
  247. "A5.10.1C" => {attr => {subType => "roomSensorControl.1C"}, GPLOT => "EnO_temp4:Temp,"},
  248. "A5.10.1D" => {attr => {subType => "roomSensorControl.1D"}, GPLOT => "EnO_temp4humi6:Temp/Humi"},
  249. "A5.10.1E" => {attr => {subType => "roomSensorControl.1B"}, GPLOT => "EnO_temp4:Temp,"},
  250. "A5.10.1F" => {attr => {subType => "roomSensorControl.1F"}, GPLOT => "EnO_temp4:Temp,"},
  251. "A5.10.20" => {attr => {subType => "roomSensorControl.20"}, GPLOT => "EnO_temp4humi4:Temp/Humi,"},
  252. "A5.10.21" => {attr => {subType => "roomSensorControl.20"}, GPLOT => "EnO_temp4humi4:Temp/Humi,"},
  253. "A5.10.22" => {attr => {subType => "roomSensorControl.22"}, GPLOT => "EnO_temp4humi4:Temp/Humi,"},
  254. "A5.10.23" => {attr => {subType => "roomSensorControl.22"}, GPLOT => "EnO_temp4humi4:Temp/Humi,"},
  255. "A5.11.01" => {attr => {subType => "lightCtrlState.01"}, GPLOT => "EnO_A5-11-01:Dim/Brightness,"},
  256. "A5.11.02" => {attr => {subType => "tempCtrlState.01"}, GPLOT => "EnO_A5-11-02:SetpointTemp/ControlVar,"},
  257. "A5.11.03" => {attr => {subType => "shutterCtrlState.01", subDef => "getNextID", subTypeSet => "gateway", gwCmd => "blindCmd", webCmd => "opens:stop:closes:position"}, GPLOT => "EnO_A5-11-03:Position/AnglePos,"},
  258. "A5.11.04" => {attr => {subType => "lightCtrlState.02", subDef => "getNextID", subTypeSet => "lightCtrl.01", webCmd => "on:off:dim:rgb"}, GPLOT => "EnO_dimFFRGB:DimRGB,"},
  259. "A5.11.05" => {attr => {subType => "switch.05"}},
  260. "A5.12.00" => {attr => {subType => "autoMeterReading.00"}, GPLOT => "EnO_A5-12-00:Value/Counter,"},
  261. "A5.12.01" => {attr => {subType => "autoMeterReading.01"}, GPLOT => "EnO_power4energy4:Power/Energie,"},
  262. "A5.12.02" => {attr => {subType => "autoMeterReading.02"}, GPLOT => "EnO_A5-12-02:Flowrate/Consumption,"},
  263. "A5.12.03" => {attr => {subType => "autoMeterReading.03"}, GPLOT => "EnO_A5-12-03:Flowrate/Consumption,"},
  264. "A5.12.04" => {attr => {subType => "autoMeterReading.04"}, GPLOT => "EnO_A5-12-04:Weight,EnO_A5-12-04_2:Temperature/Battery,"},
  265. "A5.12.05" => {attr => {subType => "autoMeterReading.05"}, GPLOT => "EnO_A5-12-05:Amount,EnO_A5-12-05_2:Temperature/Battery,"},
  266. "A5.12.10" => {attr => {subType => "autoMeterReading.10"}, GPLOT => "EnO_A5-12-10:Current/Change,"},
  267. "A5.13.01" => {attr => {subType => "environmentApp"}, GPLOT => "EnO_A5-13-01:WindSpeed/Raining,EnO_temp4brightness4:Temp/Brightness,"},
  268. "A5.13.02" => {attr => {subType => "environmentApp"}, GPLOT => "EnO_A5-13-01:SunIntensity,"},
  269. "A5.13.03" => {attr => {subType => "environmentApp"}},
  270. "A5.13.04" => {attr => {subType => "environmentApp"}},
  271. "A5.13.05" => {attr => {subType => "environmentApp"}},
  272. "A5.13.06" => {attr => {subType => "environmentApp"}},
  273. "A5.13.07" => {attr => {subType => "windSensor.01"}, GPLOT => "EnO_A5-13-07:WindSpeed,"},
  274. "A5.13.08" => {attr => {subType => "rainSensor.01"}, GPLOT => "EnO_A5-13-08:Raining,"},
  275. "A5.13.10" => {attr => {subType => "environmentApp"}, GPLOT => "EnO_solarRadiation4:SolarRadiation,"},
  276. "A5.14.01" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
  277. "A5.14.02" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
  278. "A5.14.03" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
  279. "A5.14.04" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
  280. "A5.14.05" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
  281. "A5.14.06" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
  282. "A5.14.07" => {attr => {subType => "doorContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
  283. "A5.14.08" => {attr => {subType => "doorContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
  284. "A5.14.09" => {attr => {subType => "windowContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
  285. "A5.14.0A" => {attr => {subType => "windowContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
  286. "A5.20.01" => {attr => {subType => "hvac.01", webCmd => "setpointTemp"}, GPLOT => "EnO_A5-20-01:Temp/SetpointTemp/Setpoint,EnO_A5-20-01_2:PID,"},
  287. #"A5.20.02" => {attr => {subType => "hvac.02"}},
  288. #"A5.20.03" => {attr => {subType => "hvac.03"}},
  289. "A5.20.04" => {attr => {subType => "hvac.04", webCmd => "setpointTemp"}, GPLOT => "EnO_A5-20-04:Temp/FeedTemp,EnO_A5-20-04_2:SetpointTemp/Setpoint,EnO_A5-20-04_3:PID,"},
  290. "A5.20.10" => {attr => {subType => "hvac.10", comMode => "biDir", destinationID => "unicast", subDef => "getNextID"}, GPLOT => "EnO_A5-20-10:FanSpeed,"},
  291. "A5.20.11" => {attr => {subType => "hvac.11", comMode => "biDir", destinationID => "unicast", subDef => "getNextID"}},
  292. #"A5.20.12" => {attr => {subType => "hvac.12"}},
  293. "A5.30.01" => {attr => {subType => "digitalInput.01"}, GPLOT => "EnO_A5-30-01:Contact/Battery,"},
  294. "A5.30.02" => {attr => {subType => "digitalInput.02"}, GPLOT => "EnO_A5-30-02:Contact,"},
  295. "A5.30.03" => {attr => {subType => "digitalInput.03"}, GPLOT => "EnO_A5-30-03:Contact,EnO_temp4:Temp,"},
  296. "A5.30.04" => {attr => {subType => "digitalInput.04"}, GPLOT => "EnO_A5-30-04:Contact/Digital,"},
  297. "A5.30.05" => {attr => {subType => "digitalInput.05"}, GPLOT => "EnO_A5-30-05:Contact/Voltage,"},
  298. "A5.37.01" => {attr => {subType => "energyManagement.01", webCmd => "level:max"}, GPLOT => "EnO_A5-37-01:Level,"},
  299. "A5.38.08" => {attr => {subType => "gateway"}},
  300. "A5.38.09" => {attr => {subType => "lightCtrl.01"}, GPLOT => "EnO_dimFFRGB:DimRGB,"},
  301. "A5.3F.00" => {attr => {subType => "radioLinkTest", comMode => "biDir", destinationID => "unicast", subDef => "getNextID"}},
  302. "A5.3F.7F" => {attr => {subType => "manufProfile"}},
  303. "B0.00.00" => {attr => {subType => "genericProfile"}},
  304. "C5.00.00" => {attr => {subType => "remote", manufID => "7FF"}},
  305. "D2.01.00" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
  306. "D2.01.01" => {attr => {subType => "actuator.01", defaultChannel => 0}},
  307. "D2.01.02" => {attr => {subType => "actuator.01", defaultChannel => 0, webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,EnO_power4energy4:Power/Energie,"},
  308. "D2.01.03" => {attr => {subType => "actuator.01", defaultChannel => 0, webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,"},
  309. "D2.01.04" => {attr => {subType => "actuator.01", defaultChannel => 0, webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,EnO_power4energy4:Power/Energie,"},
  310. "D2.01.05" => {attr => {subType => "actuator.01", defaultChannel => 0, webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,EnO_power4energy4:Power/Energie,"},
  311. "D2.01.06" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
  312. "D2.01.07" => {attr => {subType => "actuator.01", defaultChannel => 0}},
  313. "D2.01.08" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
  314. "D2.01.09" => {attr => {subType => "actuator.01", defaultChannel => 0, webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,EnO_power4energy4:Power/Energie,"},
  315. "D2.01.0A" => {attr => {subType => "actuator.01", defaultChannel => 0}},
  316. "D2.01.0B" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
  317. "D2.01.0C" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
  318. "D2.01.0D" => {attr => {subType => "actuator.01", defaultChannel => 0}},
  319. "D2.01.0E" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
  320. "D2.01.0F" => {attr => {subType => "actuator.01", defaultChannel => 0}},
  321. "D2.01.10" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
  322. "D2.01.11" => {attr => {subType => "actuator.01", defaultChannel => 0}},
  323. "D2.01.12" => {attr => {subType => "actuator.01", defaultChannel => 0}},
  324. "D2.01.13" => {attr => {subType => "actuator.01", defaultChannel => 0}},
  325. "D2.01.14" => {attr => {subType => "actuator.01", defaultChannel => 0}},
  326. "D2.03.00" => {attr => {subType => "switch.00"}},
  327. "D2.03.0A" => {attr => {subType => "switch.0A"}},
  328. "D2.03.10" => {attr => {subType => "windowHandle.10"}, GPLOT => "EnO_windowHandle:WindowHandle,"},
  329. "D2.05.00" => {attr => {subType => "blindsCtrl.00", webCmd => "opens:stop:closes:position"}, GPLOT => "EnO_position4angle4:Position/AnglePos,"},
  330. "D2.05.01" => {attr => {subType => "blindsCtrl.01", webCmd => "opens:stop:closes:position"}},
  331. "D2.05.02" => {attr => {subType => "blindsCtrl.00", webCmd => "opens:stop:closes:position"}, GPLOT => "EnO_position4angle4:Position/AnglePos,"},
  332. "D2.06.01" => {attr => {subType => "multisensor.01"}, GPLOT => "EnO_temp4humi4:Temp/Humi,EnO_brightness4:Brightness,"},
  333. "D2.10.00" => {attr => {subType => "roomCtrlPanel.00", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  334. "D2.10.01" => {attr => {subType => "roomCtrlPanel.00", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  335. "D2.10.02" => {attr => {subType => "roomCtrlPanel.00", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  336. "D2.11.01" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  337. "D2.11.02" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  338. "D2.11.03" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  339. "D2.11.04" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  340. "D2.11.05" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  341. "D2.11.06" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  342. "D2.11.07" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  343. "D2.11.08" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
  344. "D2.14.30" => {attr => {subType => "multiFuncSensor.30"}, GPLOT => "EnO_temp4humi4:Temp/Humi,"},
  345. "D2.20.00" => {attr => {subType => "fanCtrl.00", webCmd => "fanSpeed"}, GPLOT => "EnO_fanSpeed4humi4:FanSpeed/Humi,"},
  346. "D2.32.00" => {attr => {subType => "currentClamp.00"}, GPLOT => "EnO_D2-32-xx:Current,"},
  347. "D2.32.01" => {attr => {subType => "currentClamp.01"}, GPLOT => "EnO_D2-32-xx:Current,"},
  348. "D2.32.02" => {attr => {subType => "currentClamp.02"}, GPLOT => "EnO_D2-32-xx:Current,"},
  349. "D2.40.00" => {attr => {subType => "ledCtrlState.00"}, GPLOT => "EnO_dim4:Dim,"},
  350. "D2.40.01" => {attr => {subType => "ledCtrlState.01"}, GPLOT => "EnO_dim4RGB:DimRGB,"},
  351. "D2.50.00" => {attr => {subType => "heatRecovery.00", webCmd => "ventilation"}, GPLOT => "EnO_D2-50-xx:Temp/AirQuality,EnO_D2-50-xx_2:AirFlow/FanSpeed,"},
  352. "D2.50.01" => {attr => {subType => "heatRecovery.00", webCmd => "ventilation"}, GPLOT => "EnO_D2-50-xx:Temp/AirQuality,EnO_D2-50-xx_2:AirFlow/FanSpeed,"},
  353. "D2.50.10" => {attr => {subType => "heatRecovery.00", webCmd => "ventilation"}, GPLOT => "EnO_D2-50-xx:Temp/AirQuality,EnO_D2-50-xx_2:AirFlow/FanSpeed,"},
  354. "D2.50.11" => {attr => {subType => "heatRecovery.00", webCmd => "ventilation"}, GPLOT => "EnO_D2-50-xx:Temp/AirQuality,EnO_D2-50-xx_2:AirFlow/FanSpeed,"},
  355. "D2.A0.01" => {attr => {subType => "valveCtrl.00", defaultChannel => 0, webCmd => "opens:closes"}, GPLOT => "EnO_valveCtrl:Valve,"},
  356. "D2.B0.51" => {attr => {subType => "liquidLeakage.51"}, GPLOT => "EnO_liquidLeakage:LiquidLeakage,"},
  357. "D5.00.01" => {attr => {subType => "contact", manufID => "7FF"}, GPLOT => "EnO_contact:Contact,"},
  358. "F6.01.01" => {attr => {subType => "switch", sensorMode => "pushbutton"}},
  359. "F6.02.01" => {attr => {subType => "switch"}},
  360. "F6.02.02" => {attr => {subType => "switch"}},
  361. "F6.02.03" => {attr => {subType => "switch"}},
  362. #"F6.02.04" => {attr => {subType => "switch.04"}},
  363. "F6.03.01" => {attr => {subType => "switch"}},
  364. "F6.03.02" => {attr => {subType => "switch"}},
  365. "F6.04.01" => {attr => {subType => "keycard"}, GPLOT => "EnO_keycard:Keycard,"},
  366. #"F6.04.02" => {attr => {subType => "keycard.02"}, GPLOT => "EnO_keycard:Keycard,"},
  367. "F6.05.00" => {attr => {subType => "windSpeed.00"}},
  368. "F6.05.01" => {attr => {subType => "liquidLeakage"}, GPLOT => "EnO_liquidLeakage:LiquidLeakage,"},
  369. "F6.05.02" => {attr => {subType => "smokeDetector.02"}},
  370. "F6.10.00" => {attr => {subType => "windowHandle"}, GPLOT => "EnO_windowHandle:WindowHandle,"},
  371. #"F6.10.01" => {attr => {subType => "windowHandle.01"}, GPLOT => "EnO_windowHandle:WindowHandle,"},
  372. "F6.3F.7F" => {attr => {subType => "switch.7F"}},
  373. # special profiles
  374. "G5.07.01" => {attr => {subType => "occupSensor.01", eep => "A5-07-01", manufID => "00D", model => 'tracker'}, GPLOT => "EnO_motion:Motion,EnO_voltage4current4:Voltage/Current,"},
  375. "G5.10.12" => {attr => {subType => "roomSensorControl.01", eep => "A5-10-12", manufID => "00D", scaleMax => 40, scaleMin => 0, scaleDecimals => 1}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
  376. "G5.38.08" => {attr => {subType => "gateway", eep => "A5-38-08", gwCmd => "dimming", manufID => "00D", webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,"},
  377. "H5.38.08" => {attr => {subType => "gateway", comMode => "confirm", eep => "A5-38-08", gwCmd => "dimming", manufID => "00D", model => "Eltako_TF", teachMethod => "confirm", webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,"},
  378. "G5.3F.7F" => {attr => {subType => "manufProfile", eep => "A5-3F-7F", manufID => "00D", webCmd => "opens:stop:closes"}},
  379. "H5.3F.7F" => {attr => {subType => "manufProfile", comMode => "confirm", eep => "A5-3F-7F", manufID => "00D", model => "Eltako_TF", sensorMode => 'pushbutton', settingAccuracy => "high", teachMethod => "confirm", webCmd => "opens:stop:closes"}},
  380. "M5.38.08" => {attr => {subType => "gateway", eep => "A5-38-08", gwCmd => "switching", manufID => "00D", webCmd => "on:off"}},
  381. "N5.38.08" => {attr => {subType => "gateway", comMode => "confirm", eep => "A5-38-08", gwCmd => "switching", manufID => "00D", model => "Eltako_TF", teachMethod => "confirm", webCmd => "on:off"}},
  382. "G5.ZZ.ZZ" => {attr => {subType => "PM101", manufID => "005"}, GPLOT => "EnO_motion:Motion,EnO_brightness4:Brightness,"},
  383. "L6.02.01" => {attr => {subType => "smokeDetector.02", eep => "F6-05-02", manufID => "00D"}},
  384. "ZZ.ZZ.ZZ" => {attr => {subType => "raw"}},
  385. );
  386. my %EnO_extendedRemoteFunctionCode = (
  387. 0x210 => "remoteLinkTableInfo", # get
  388. 0x211 => "remoteLinkTable", # get
  389. 0x212 => "remoteLinkTable", # set
  390. 0x213 => "remoteLinkTableGP", # get
  391. 0x214 => "remoteLinkTableGP", # set
  392. 0x220 => "remoteLearnMode", # set
  393. 0x221 => "remoteTeach", # set
  394. 0x224 => "remoteReset", # set
  395. 0x225 => "remoteRLT", # set
  396. 0x226 => "remoteApplyChanges", # set
  397. 0x227 => "remoteProductID", # get
  398. 0x230 => "remoteDevCfg", # get
  399. 0x231 => "remoteDevCfg", # set
  400. 0x232 => "remoteLinkCfg", # get
  401. 0x233 => "remoteLinkCfg", # set
  402. 0x240 => "remoteAck", # parse
  403. 0x250 => "remoteRepeater", # get
  404. 0x251 => "remoteRepeater", # set
  405. 0x252 => "remoteRepeaterFilter" # set
  406. );
  407. my %EnO_models = (
  408. "Eltako_FAE14" => {attr => {manufID => "00D"}},
  409. "Eltako_FHK14" => {attr => {manufID => "00D"}},
  410. "Eltako_FHK61" => {attr => {manufID => "00D"}},
  411. "Eltako_FSA12" => {attr => {manufID => "00D"}},
  412. "Eltako_FSB14" => {attr => {manufID => "00D"}},
  413. "Eltako_FSB61" => {attr => {manufID => "00D"}},
  414. "Eltako_FSB70" => {attr => {manufID => "00D"}},
  415. "Eltako_FSB_ACK" => {attr => {manufID => "00D"}},
  416. "Eltako_FSM12" => {attr => {manufID => "00D"}},
  417. "Eltako_FSM61" => {attr => {manufID => "00D"}},
  418. "Eltako_FT55" => {attr => {manufID => "00D"}},
  419. "Eltako_FTS12" => {attr => {manufID => "00D"}},
  420. "Eltako_TF"=> {attr => {manufID => "00D"}},
  421. "Eltako_TF_RWB"=> {attr => {manufID => "00D"}},
  422. "Holter_OEM" => {attr => {pidCtrl => "off"}},
  423. "Micropelt_MVA004" => {attr => {remoteCode => "FFFFFFFE", remoteEEP => "A5-20-01", remoteID => "getNextID", remoteManagement => "manager"}, xml => {productID => "0x004900000000", xmlDescrLocation => "/FHEM/lib/EnO_ReCom_Device_Descr.xml"}},
  424. other => {},
  425. tracker => {}
  426. );
  427. my @EnO_defaultChannel = ("all", "input", 0..29);
  428. my %wakeUpCycle = (
  429. 10 => 0,
  430. 60 => 1,
  431. 90 => 2,
  432. 120 => 3,
  433. 150 => 4,
  434. 180 => 5,
  435. 210 => 6,
  436. 240 => 7,
  437. 270 => 8,
  438. 300 => 9,
  439. 330 => 10,
  440. 360 => 11,
  441. 390 => 12,
  442. 420 => 13,
  443. 450 => 14,
  444. 480 => 15,
  445. 510 => 16,
  446. 540 => 17,
  447. 570 => 18,
  448. 600 => 19,
  449. 630 => 20,
  450. 660 => 21,
  451. 690 => 22,
  452. 720 => 23,
  453. 750 => 24,
  454. 780 => 25,
  455. 810 => 26,
  456. 840 => 27,
  457. 870 => 28,
  458. 900 => 29,
  459. 930 => 30,
  460. 960 => 31,
  461. 990 => 32,
  462. 1020 => 33,
  463. 1050 => 34,
  464. 1080 => 35,
  465. 1110 => 36,
  466. 1140 => 37,
  467. 1170 => 38,
  468. 1200 => 39,
  469. 1230 => 40,
  470. 1260 => 41,
  471. 1290 => 42,
  472. 1320 => 43,
  473. 1350 => 44,
  474. 1380 => 45,
  475. 1410 => 46,
  476. 1440 => 47,
  477. 1470 => 48,
  478. 1500 => 49,
  479. 10800 => 50,
  480. 21600 => 51,
  481. 32400 => 52,
  482. 43200 => 53,
  483. 54000 => 54,
  484. 64800 => 55,
  485. 75600 => 56,
  486. 86400 => 57,
  487. 97200 => 58,
  488. 108000 => 59,
  489. 118800 => 60,
  490. 129600 => 61,
  491. 140400 => 62,
  492. 151200 => 63,
  493. );
  494. my %wakeUpCycleInv = (
  495. 0 => 10,
  496. 1 => 60,
  497. 2 => 90,
  498. 3 => 120,
  499. 4 => 150,
  500. 5 => 180,
  501. 6 => 210,
  502. 7 => 240,
  503. 8 => 270,
  504. 9 => 300,
  505. 10 => 330,
  506. 11 => 360,
  507. 12 => 390,
  508. 13 => 420,
  509. 14 => 450,
  510. 15 => 480,
  511. 16 => 510,
  512. 17 => 540,
  513. 18 => 570,
  514. 19 => 600,
  515. 20 => 630,
  516. 21 => 660,
  517. 22 => 690,
  518. 23 => 720,
  519. 24 => 750,
  520. 25 => 780,
  521. 26 => 810,
  522. 27 => 840,
  523. 28 => 870,
  524. 29 => 900,
  525. 30 => 930,
  526. 31 => 960,
  527. 32 => 990,
  528. 33 => 1020,
  529. 34 => 1050,
  530. 35 => 1080,
  531. 36 => 1110,
  532. 37 => 1140,
  533. 38 => 1170,
  534. 39 => 1200,
  535. 40 => 1230,
  536. 41 => 1260,
  537. 42 => 1290,
  538. 43 => 1320,
  539. 44 => 1350,
  540. 45 => 1380,
  541. 46 => 1410,
  542. 47 => 1440,
  543. 48 => 1470,
  544. 49 => 1500,
  545. 50 => 10800,
  546. 51 => 21600,
  547. 52 => 32400,
  548. 53 => 43200,
  549. 54 => 54000,
  550. 55 => 64800,
  551. 56 => 75600,
  552. 57 => 86400,
  553. 58 => 97200,
  554. 59 => 108000,
  555. 60 => 118800,
  556. 61 => 129600,
  557. 62 => 140400,
  558. 63 => 151200,
  559. );
  560. my @EnO_resolution = (1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32);
  561. my @EnO_scaling = (0, 1, 10, 100, 1000, 10000, 100000, 1e6, 1e7, 0.1, 0.01, 0.001, 1e-6, 1e-9);
  562. my %EnO_gpValueEnum = (
  563. 1 => {name => "multipurpose"},
  564. 2 => {name => "buildingMode", enum => {0 => "in_use", 1 => "not_used", 2 => "protection"},
  565. enumInv => {"in_use" => 0, "not_used" => 1, "protection" => 2}},
  566. 3 => {name => "occupanyMode", enum => {0 => "occupied", 1 => "standby", 2 => "not_occupied"},
  567. enumInv => {"occupied" => 0,"standby" => 1,"not_occupied" => 2}},
  568. 4 => {name => "hvacMode", enum => {0 => "auto", 1 => "comfort", 2 => "standby", 3 => "economy", 4 => "building_protection"},
  569. enumInv => {"auto" => 0, "comfort" => 1, "standby" => 2, "economy" => 3, "building_protection" => 4}},
  570. 5 => {name => "changeoverMode", enum => {0 => "auto", 1 => "cooling", 2 => "heating"},
  571. enumInv => {"auto" => 0,"cooling" => 1,"heating" => 2}},
  572. );
  573. my %EnO_gpValueFlag = (
  574. 1 => {name => "auto/man", flag => {0 => "man", 1 => "auto"},
  575. flagInv => {"man" => 0,"auto" => 1}},
  576. 2 => {name => "buttonPressed", flag => {0 => "not_pressed", 1 => "pressed"},
  577. flagInv => {"not_pressed" => 0,"pressed" => 1}},
  578. 3 => {name => "buttonChanged", flag => {0 => "no_change", 1 => "change"},
  579. flagInv => {"no_change" => 0,"change" => 1}},
  580. 4 => {name => "day/night", flag => {0 => "night", 1 => "day"},
  581. flagInv => {"night" => 0,"day" => 1}},
  582. 5 => {name => "down", flag => {0 => "no_change", 1 => "down"},
  583. flagInv => {"no_change" => 0,"down" => 1}},
  584. 6 => {name => "generalAlarm", flag => {0 => "manual", 1 => "alarm"},
  585. flagInv => {"manual" => 0,"alarm" => 1}},
  586. 7 => {name => "heat/cool", flag => {0 => "cool", 1 => "heat"},
  587. flagInv => {"cool" => 0,"heat" => 1}},
  588. 8 => {name => "high/low", flag => {0 => "low", 1 => "high"},
  589. flagInv => {"low" => 0,"high" => 1}},
  590. 9 => {name => "occupancy", flag => {0 => "unoccupied", 1 => "occupied"},
  591. flagInv => {"unoccupied" => 0,"occupied" => 1}},
  592. 10 => {name => "on/off", flag => {0 => "off", 1 => "on"},
  593. flagInv => {"off" => 0,"on" => 1}},
  594. 11 => {name => "open/closed", flag => {0 => "closed", 1 => "open"},
  595. flagInv => {"closed" => 0,"open" => 1}},
  596. 12 => {name => "powerAlarm", flag => {0 => "no_change", 1 => "alarm"},
  597. flagInv => {"no_change" => 0,"alarm" => 1}},
  598. 13 => {name => "start/stop", flag => {0 => "stop", 1 => "start"},
  599. flagInv => {"stop" => 0,"start" => 1}},
  600. 14 => {name => "up", flag => {0 => "no_change", 1 => "up"},
  601. flagInv => {"no_change" => 0,"up" => 1}},
  602. );
  603. my %EnO_gpValueData = (
  604. 1 => {name => "acceleration", unit => "m/s2"},
  605. 2 => {name => "angle", unit => "deg"},
  606. 3 => {name => "angular_velocity", unit => "rad/s"},
  607. 4 => {name => "area", unit => "m²"},
  608. 5 => {name => "concentration", unit => "ppm"},
  609. 6 => {name => "current", unit => "A"},
  610. 7 => {name => "distance", unit => "m"},
  611. 8 => {name => "electric_field_strength", unit => "V/m"},
  612. 9 => {name => "energy", unit => "J"},
  613. 10 => {name => "number", unit => "N/A"},
  614. 11 => {name => "force", unit => "N"},
  615. 12 => {name => "frequency", unit => "Hz"},
  616. 13 => {name => "heat_flux_density", unit => "W/m2"},
  617. 14 => {name => "impulse", unit => "Ns"},
  618. 15 => {name => "luminance_intensity", unit => "lux"},
  619. 16 => {name => "magnetic_field_strength", unit => "A/m"},
  620. 17 => {name => "mass", unit => "kg"},
  621. 18 => {name => "mass_density", unit => "kg/m2"},
  622. 19 => {name => "mass_flow", unit => "kg/s"},
  623. 20 => {name => "power", unit => "W"},
  624. 21 => {name => "pressure", unit => "Pa"},
  625. 22 => {name => "relative_humidity", unit => "%"},
  626. 23 => {name => "resistance", unit => "Ohm"},
  627. 24 => {name => "temperature", unit => "C"},
  628. 25 => {name => "time", unit => "s"},
  629. 26 => {name => "torque", unit => "Nm"},
  630. 27 => {name => "velocity", unit => "m/s"},
  631. 28 => {name => "voltage", unit => "V"},
  632. 29 => {name => "volume", unit => "m3"},
  633. 30 => {name => "volumetric_flow", unit => "m3/s"},
  634. );
  635. # Initialize
  636. sub
  637. EnOcean_Initialize($)
  638. {
  639. my ($hash) = @_;
  640. my %subTypeList;
  641. my @subTypeList;
  642. foreach my $eep (keys %EnO_eepConfig){
  643. push @subTypeList, $EnO_eepConfig{$eep}{attr}{subType};
  644. }
  645. my $subTypeList = join(",", sort grep { !$subTypeList{$_}++ } @subTypeList);
  646. $hash->{AutoCreate} = {"EnO.*" => {ATTR => "creator:autocreate", FILTER => "%NAME"}};
  647. $hash->{noAutocreatedFilelog} = 1;
  648. $hash->{Match} = "^EnOcean:";
  649. $hash->{DefFn} = "EnOcean_Define";
  650. $hash->{DeleteFn} = "EnOcean_Delete";
  651. $hash->{UndefFn} = "EnOcean_Undef";
  652. $hash->{ParseFn} = "EnOcean_Parse";
  653. $hash->{SetFn} = "EnOcean_Set";
  654. #$hash->{StateFn} = "EnOcean_State";
  655. $hash->{GetFn} = "EnOcean_Get";
  656. $hash->{NotifyFn} = "EnOcean_Notify";
  657. $hash->{AttrFn} = "EnOcean_Attr";
  658. $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 dummy:0,1 " .
  659. "showtime:1,0 " .
  660. "actualTemp angleMax:slider,-180,20,180 alarmAction " .
  661. "angleMin:slider,-180,20,180 " .
  662. "angleTime setCmdTrigger:man,refDev blockUnknownMSC:no,yes blockMotion:no,yes " .
  663. "blockTemp:no,yes blockDisplay:no,yes blockDateTime:no,yes " .
  664. "blockTimeProgram:no,yes blockOccupancy:no,yes blockSetpointTemp:no,yes " .
  665. "blockFanSpeed:no,yes blockKey:no,yes comMode:confirm,biDir,uniDir creator:autocreate,manual " .
  666. "daylightSavingTime:supported,not_supported dataEnc:VAES,AES-CBC " .
  667. "defaultChannel:" . join(",", @EnO_defaultChannel) . " " .
  668. "demandRespAction demandRespRefDev demandRespMax:A0,AI,B0,BI,C0,CI,D0,DI ".
  669. "demandRespMin:A0,AI,B0,BI,C0,CI,D0,DI demandRespRandomTime " .
  670. "demandRespThreshold:slider,0,1,15 demandRespTimeoutLevel:max,last destinationID " .
  671. "devChannel devMode:master,slave devUpdate:off,auto,demand,polling,interrupt " .
  672. "dimMax dimMin dimValueOn disable:0,1 disabledForIntervals " .
  673. "displayContent:default,humidity,off,setpointTemp,tempertureExtern,temperatureIntern,time,no_change " .
  674. "displayOrientation:0,90,180,270 " .
  675. "eep gpDef gwCmd:" . join(",", sort @EnO_gwCmd) . " humitity humidityRefDev " .
  676. "keyRcv keySnd macAlgo:no,3,4 measurementCtrl:disable,enable " .
  677. "manufID:" . join(",", sort keys %EnO_manuf) . " " .
  678. "model:" . join(",", sort keys %EnO_models) . " " .
  679. "observe:on,off observeCmdRepetition:1,2,3,4,5 observeErrorAction observeInterval observeLogic:and,or " .
  680. #observeCmds observeExeptions
  681. "observeRefDev pidActorErrorAction:errorPos,freeze pidActorCallBeforeSetting pidActorErrorPos " .
  682. "pidActorLimitLower pidActorLimitUpper pidCtrl:on,off pidDeltaTreshold pidFactor_D pidFactor_I " .
  683. "pidFactor_P pidIPortionCallBeforeSetting pidSensorTimeout " .
  684. "pollInterval postmasterID productID rampTime rcvRespAction ".
  685. "releasedChannel:A,B,C,D,I,0,auto repeatingAllowed:yes,no remoteCode remoteEEP remoteID remoteManufID " .
  686. "remoteManagement:client,manager,off rlcAlgo:no,2++,3++ rlcRcv rlcSnd rlcTX:true,false " .
  687. "reposition:directly,opens,closes rltRepeat:16,32,64,128,256 rltType:1BS,4BS " .
  688. "scaleDecimals:0,1,2,3,4,5,6,7,8,9 scaleMax scaleMin secMode:rcv,snd,bidir " .
  689. "secLevel:encapsulation,encryption,off sendDevStatus:no,yes sensorMode:switch,pushbutton " .
  690. "serviceOn:no,yes settingAccuracy:high,low setpointRefDev setpointSummerMode:slider,0,5,100 " .
  691. "signal:off,on signOfLife:off,on signOfLifeInterval setpointTempRefDev shutTime shutTimeCloses subDef " .
  692. "subDef0 subDefI subDefA subDefB subDefC subDefD subDefH subDefW " .
  693. "subType:$subTypeList subTypeSet:$subTypeList subTypeReading:$subTypeList " .
  694. "summerMode:off,on switchMode:switch,pushbutton " .
  695. "switchHysteresis switchType:direction,universal,channel,central " .
  696. "teachMethod:1BS,4BS,confirm,GP,RPS,smartAck,STE,UTE temperatureRefDev " .
  697. "temperatureScale:C,F,default,no_change timeNotation:12,24,default,no_change " .
  698. "timeProgram1 timeProgram2 timeProgram3 timeProgram4 trackerWakeUpCycle:10,20,30,40,60,120,180,240,3600,86400 updateState:default,yes,no " .
  699. "uteResponseRequest:yes,no " .
  700. "wakeUpCycle:" . join(",", keys %wakeUpCycle) . " " .
  701. $readingFnAttributes;
  702. for (my $i = 0; $i < @EnO_ptm200btn; $i++) {
  703. $EnO_ptm200btn{$EnO_ptm200btn[$i]} = "$i:30";
  704. }
  705. $EnO_ptm200btn{released} = "0:20";
  706. if ($cryptFunc == 1){
  707. Log3 undef, 2, "EnOcean Cryptographic functions available.";
  708. } else {
  709. Log3 undef, 2, "EnOcean Cryptographic functions are not available.";
  710. }
  711. if ($xmlFunc == 1){
  712. Log3 undef, 2, "EnOcean XML functions available.";
  713. } else {
  714. Log3 undef, 2, "EnOcean XML functions are not available.";
  715. }
  716. return undef;
  717. }
  718. # Define
  719. sub EnOcean_Define($$) {
  720. my ($hash, $def) = @_;
  721. my @a = split("[ \t][ \t]*", $def);
  722. my $name = $hash->{NAME};
  723. my ($autocreateFilelog, $autocreateHash, $autocreateName, $autocreateDeviceRoom, $autocreateWeblinkRoom) =
  724. ('./log/' . $name . '-%Y.log', undef, 'autocreate', 'EnOcean', 'Plots');
  725. my ($cmd, $eep, $ret);
  726. my $filelogName = "FileLog_$name";
  727. $def = "00000000";
  728. if(@a > 2 && @a < 5) {
  729. # find autocreate device
  730. while (($autocreateName, $autocreateHash) = each(%defs)) {
  731. last if ($defs{$autocreateName}{TYPE} eq "autocreate");
  732. }
  733. $autocreateDeviceRoom = AttrVal($autocreateName, "device_room", $autocreateDeviceRoom) if (defined $autocreateName);
  734. $autocreateDeviceRoom = 'EnOcean' if ($autocreateDeviceRoom eq '%TYPE');
  735. $autocreateDeviceRoom = $name if ($autocreateDeviceRoom eq '%NAME');
  736. $autocreateDeviceRoom = AttrVal($name, "room", $autocreateDeviceRoom);
  737. if ($init_done) {
  738. Log3 $name, 2, "EnOcean define " . join(' ', @a);
  739. if (!defined(AttrVal($autocreateName, "disable", undef)) && !exists($defs{$filelogName})) {
  740. # create FileLog
  741. $autocreateFilelog = $attr{$autocreateName}{filelog} if (exists $attr{$autocreateName}{filelog});
  742. $autocreateFilelog =~ s/%NAME/$name/g;
  743. $cmd = "$filelogName FileLog $autocreateFilelog $name";
  744. Log3 $filelogName, 2, "EnOcean define $cmd";
  745. $ret = CommandDefine(undef, $cmd);
  746. if($ret) {
  747. Log3 $filelogName, 2, "EnOcean ERROR: $ret";
  748. } else {
  749. $attr{$filelogName}{room} = $autocreateDeviceRoom;
  750. $attr{$filelogName}{logtype} = 'text';
  751. }
  752. }
  753. }
  754. if ($a[2] eq "getNextID") {
  755. AssignIoPort($hash) if (!exists $hash->{IODev});
  756. if (exists $hash->{OLDDEF}) {
  757. delete $modules{EnOcean}{defptr}{$hash->{OLDDEF}};
  758. }
  759. $hash->{DEF} = $def;
  760. $def = EnOcean_CheckSenderID("getNextID", $hash->{IODev}{NAME}, "00000000");
  761. $hash->{DEF} = $def;
  762. $modules{EnOcean}{defptr}{$def} = $hash;
  763. $attr{$name}{manufID} = "7FF" if (!exists $attr{$name}{manufID});
  764. $attr{$name}{room} = $autocreateDeviceRoom;
  765. $attr{$name}{subType} = "raw" if (!exists $attr{$name}{subType});
  766. } elsif ($a[2] =~ m/^[A-Fa-f0-9]{8}$/i) {
  767. # DestinationID
  768. $def = uc($a[2]);
  769. $hash->{DEF} = $def;
  770. if (defined($a[3]) && $a[3] =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i) {
  771. # EEP
  772. my ($rorg, $func, $type) = (uc($1), uc($2), uc($3));
  773. $rorg = "F6" if ($rorg eq "05");
  774. $rorg = "D5" if ($rorg eq "06");
  775. $rorg = "A5" if ($rorg eq "07");
  776. $eep = "$rorg.$func.$type";
  777. if (exists $EnO_eepConfig{$eep}) {
  778. if ($eep eq 'A5.3F.00') {
  779. my ($rltHash, $rltName);
  780. foreach my $dev (keys %defs) {
  781. next if ($defs{$dev}{TYPE} ne 'EnOcean');
  782. next if (!exists($attr{$dev}{subType}));
  783. next if ($attr{$dev}{subType} ne 'radioLinkTest');
  784. $rltHash = $defs{$dev};
  785. $rltName = $rltHash->{NAME};
  786. last;
  787. }
  788. return "Radio Link Test device already defined, use $rltName" if ($rltHash);
  789. }
  790. AssignIoPort($hash) if (!exists $hash->{IODev});
  791. if (exists $hash->{OLDDEF}) {
  792. delete $modules{EnOcean}{defptr}{$hash->{OLDDEF}};
  793. }
  794. $modules{EnOcean}{defptr}{$def} = $hash;
  795. if (exists($attr{$name}{eep}) && $attr{$name}{eep} ne "$rorg-$func-$type") {
  796. delete $attr{$name};
  797. $attr{$filelogName}{logtype} = $EnO_eepConfig{$eep}{GPLOT} . 'text'
  798. if (exists $attr{$filelogName}{logtype});
  799. # delete SVG devices
  800. my ($weblinkName, $weblinkHash);
  801. while (($weblinkName, $weblinkHash) = each(%defs)) {
  802. if ($weblinkName =~ /^SVG_$name.*/) {
  803. CommandDelete(undef, $weblinkName);
  804. Log3 $hash->{NAME}, 2, "EnOcean $weblinkName deleted";
  805. }
  806. }
  807. }
  808. $attr{$name}{eep} = "$rorg-$func-$type";
  809. $attr{$name}{manufID} = "7FF" if (!exists $attr{$name}{manufID});
  810. $attr{$name}{room} = $autocreateDeviceRoom;
  811. foreach my $attrCntr (keys %{$EnO_eepConfig{$eep}{attr}}) {
  812. if ($attrCntr eq "subDef") {
  813. if (!exists $attr{$name}{$attrCntr}) {
  814. $attr{$name}{$attrCntr} = EnOcean_CheckSenderID($EnO_eepConfig{$eep}{attr}{$attrCntr}, $hash->{IODev}{NAME}, "00000000");
  815. }
  816. } else {
  817. $attr{$name}{$attrCntr} = $EnO_eepConfig{$eep}{attr}{$attrCntr};
  818. }
  819. }
  820. EnOcean_CreateSVG(undef, $hash, $a[3]);
  821. } else {
  822. return "EEP $rorg-$func-$type not supported";
  823. }
  824. } elsif (defined($a[3]) && $a[3] =~ m/^EnOcean:.*/) {
  825. # autocreate: parse received device telegram
  826. AssignIoPort($hash) if (!exists $hash->{IODev});
  827. $modules{EnOcean}{defptr}{$def} = $hash;
  828. my @msg = split(':', $a[3]);
  829. my $packetType = hex $msg[1];
  830. if ($packetType == 1) {
  831. my ($data, $rorg, $status);
  832. #EnOcean:PacketType:RORG:MessageData:SourceID:Status:OptionalData
  833. (undef, undef, $rorg, $data, undef, $status, undef) = @msg;
  834. $attr{$name}{subType} = $EnO_rorgname{$rorg};
  835. if ($attr{$name}{subType} eq "switch") {
  836. my $nu = (hex($status) & 0x10) >> 4;
  837. my $t21 = (hex($status) & 0x20) >> 5;
  838. $attr{$name}{manufID} = "7FF";
  839. if ($t21 && $nu) {
  840. $attr{$name}{eep} = "F6-02-01";
  841. readingsSingleUpdate($hash, "teach", "RPS teach-in accepted EEP F6-02-01 Manufacturer: no ID", 1);
  842. $attr{$name}{teachMethod} = 'RPS';
  843. Log3 $name, 2, "EnOcean $name teach-in EEP F6-02-01 Manufacturer: no ID";
  844. } elsif (!$t21 && $nu) {
  845. $attr{$name}{eep} = "F6-03-01";
  846. readingsSingleUpdate($hash, "teach", "RPS teach-in accepted EEP F6-03-01 Manufacturer: no ID", 1);
  847. $attr{$name}{teachMethod} = 'RPS';
  848. Log3 $name, 2, "EnOcean $name teach-in EEP F6-03-01 Manufacturer: no ID";
  849. } elsif ($t21 && !$nu) {
  850. $attr{$name}{subType} = "windowHandle";
  851. $attr{$name}{eep} = "F6-10-00";
  852. readingsSingleUpdate($hash, "teach", "RPS teach-in accepted EEP F6-10-00 Manufacturer: no ID", 1);
  853. $attr{$name}{teachMethod} = 'RPS';
  854. Log3 $name, 2, "EnOcean $name teach-in EEP F6-10-00 Manufacturer: no ID";
  855. }
  856. } elsif ($attr{$name}{subType} eq "contact" && hex($data) & 8) {
  857. $attr{$name}{eep} = "D5-00-01";
  858. $attr{$name}{manufID} = "7FF";
  859. readingsSingleUpdate($hash, "teach", "1BS teach-in accepted EEP D5-00-01 Manufacturer: no ID", 1);
  860. $attr{$name}{teachMethod} = '1BS';
  861. Log3 $name, 2, "EnOcean $name teach-in EEP D5-00-01 Manufacturer: no ID";
  862. } elsif ($attr{$name}{subType} eq "4BS" && hex(substr($data, 6, 2)) & 8) {
  863. $hash->{helper}{teachInWait} = "4BS";
  864. readingsSingleUpdate($hash, "teach", "4BS teach-in is missing", 1);
  865. Log3 $name, 2, "EnOcean $name 4BS teach-in is missing";
  866. } elsif ($attr{$name}{subType} eq "UTE") {
  867. $hash->{helper}{teachInWait} = "UTE";
  868. } elsif ($attr{$name}{subType} eq "VLD") {
  869. $hash->{helper}{teachInWait} = "UTE";
  870. readingsSingleUpdate($hash, "teach", "UTE teach-in is missing", 1);
  871. Log3 $name, 2, "EnOcean $name UTE teach-in is missing";
  872. } elsif ($attr{$name}{subType} eq "MSC") {
  873. readingsSingleUpdate($hash, "teach", "MSC not supported", 1);
  874. Log3 $name, 2, "EnOcean $name MSC not supported";
  875. } elsif ($attr{$name}{subType} =~ m/^SEC|ENC$/) {
  876. $hash->{helper}{teachInWait} = "STE";
  877. readingsSingleUpdate($hash, "teach", "STE teach-in is missing", 1);
  878. Log3 $name, 2, "EnOcean $name STE teach-in is missing";
  879. } elsif ($attr{$name}{subType} =~ m/^GPCD|GPSD$/) {
  880. $hash->{helper}{teachInWait} = "GPTI";
  881. readingsSingleUpdate($hash, "teach", "GP teach-in is missing", 1);
  882. Log3 $name, 2, "EnOcean $name GP teach-in is missing";
  883. }
  884. } elsif ($packetType == 4) {
  885. $hash->{helper}{smartAckLearnWait} = $name;
  886. } elsif ($packetType == 7) {
  887. # remote management
  888. #EnOcean:PacketType:RORG:MessageData:SourceID:DestinationID:FunctionNumber:ManufacturerID:RSSI:Delay
  889. if (hex($msg[6]) < 0x600) {
  890. $attr{$name}{remoteManagement} = 'client';
  891. # unlock device to send acknowledgment telegram
  892. $hash->{RemoteClientUnlock} = 1;
  893. #####
  894. #my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
  895. #RemoveInternalTimer(\%functionHash);
  896. #InternalTimer(gettimeofday() + 1, 'EnOcean_cdmClearHashVal', \%functionHash, 0);
  897. RemoveInternalTimer($hash->{helper}{timer}{RemoteClientUnlock}) if(exists $hash->{helper}{timer}{RemoteClientUnlock});
  898. $hash->{helper}{timer}{RemoteClientUnlock} = {hash => $hash, param => 'RemoteClientUnlock'};
  899. InternalTimer(gettimeofday() + 1, 'EnOcean_cdmClearHashVal', $hash->{helper}{timer}{RemoteClientUnlock}, 0);
  900. } else {
  901. $attr{$name}{remoteManagement} = 'manager';
  902. }
  903. $attr{$name}{eep} = 'C5-00-00';
  904. $attr{$name}{manufID} = substr($msg[7], 1);
  905. $attr{$name}{remoteEEP} = 'C5-00-00';
  906. $attr{$name}{remoteID} = $msg[4];
  907. $attr{$name}{remoteManufID} = substr($msg[7], 1);
  908. $attr{$name}{subType} = 'remote';
  909. $modules{EnOcean}{defptr}{$msg[4]} = $hash;
  910. }
  911. EnOcean_Parse($hash, $a[3]);
  912. if (exists $attr{$name}{eep}) {
  913. $attr{$name}{eep} =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i;
  914. $eep = uc("$1.$2.$3");
  915. if (exists($attr{$filelogName}{logtype}) && exists($EnO_eepConfig{$eep}{GPLOT})) {
  916. $attr{$filelogName}{logtype} = $EnO_eepConfig{$eep}{GPLOT} . 'text';
  917. EnOcean_CreateSVG(undef, $hash, undef);
  918. }
  919. }
  920. } else {
  921. # no device infos
  922. AssignIoPort($hash) if (!exists $hash->{IODev});
  923. # assign defptr
  924. if (exists $hash->{OLDDEF}) {
  925. delete $modules{EnOcean}{defptr}{$hash->{OLDDEF}};
  926. }
  927. $modules{EnOcean}{defptr}{$def} = $hash;
  928. $attr{$name}{manufID} = "7FF" if (!exists $attr{$name}{manufID});
  929. $attr{$name}{room} = $autocreateDeviceRoom;
  930. $attr{$name}{subType} = "raw" if (!exists $attr{$name}{subType});
  931. }
  932. } elsif ($a[2] =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i) {
  933. # EEP
  934. my ($rorg, $func, $type) = (uc($1), uc($2), uc($3));
  935. $rorg = "F6" if ($rorg eq "05");
  936. $rorg = "D5" if ($rorg eq "06");
  937. $rorg = "A5" if ($rorg eq "07");
  938. $eep = "$rorg.$func.$type";
  939. if (exists $EnO_eepConfig{$eep}) {
  940. if ($eep eq 'A5.3F.00') {
  941. # radio link test device
  942. my ($rltHash, $rltName);
  943. foreach my $dev (keys %defs) {
  944. next if ($defs{$dev}{TYPE} ne 'EnOcean');
  945. next if (!exists($attr{$dev}{subType}));
  946. next if ($attr{$dev}{subType} ne 'radioLinkTest');
  947. $rltHash = $defs{$dev};
  948. $rltName = $rltHash->{NAME};
  949. last;
  950. }
  951. return "Radio Link Test device already defined, use $rltName" if ($rltHash);
  952. }
  953. AssignIoPort($hash) if (!exists $hash->{IODev});
  954. if (exists($hash->{OLDDEF}) && $hash->{OLDDEF} =~ m/^[A-Fa-f0-9]{8}$/i) {
  955. delete $modules{EnOcean}{defptr}{$hash->{OLDDEF}};
  956. if ($hash->{DEF} =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i) {
  957. $def = $hash->{OLDDEF};
  958. $hash->{DEF} = $hash->{OLDDEF};
  959. }
  960. } else {
  961. $hash->{DEF} = $def;
  962. if ($eep eq 'A5.3F.00') {
  963. $attr{$name}{subDef} = EnOcean_CheckSenderID("getNextID", $hash->{IODev}{NAME}, "00000000");
  964. } elsif ($eep eq 'C5.00.00') {
  965. # remote management
  966. } else {
  967. $def = EnOcean_CheckSenderID("getNextID", $hash->{IODev}{NAME}, "00000000");
  968. $hash->{DEF} = $def;
  969. }
  970. }
  971. $modules{EnOcean}{defptr}{$def} = $hash;
  972. if (exists($attr{$name}{eep}) && $attr{$name}{eep} ne "$rorg-$func-$type") {
  973. delete $attr{$name};
  974. if (exists $attr{$filelogName}{logtype}) {
  975. if (exists$EnO_eepConfig{$eep}{GPLOT}) {
  976. $attr{$filelogName}{logtype} = $EnO_eepConfig{$eep}{GPLOT} . 'text';
  977. } else {
  978. $attr{$filelogName}{logtype} = 'text';
  979. }
  980. }
  981. }
  982. $attr{$name}{eep} = "$rorg-$func-$type";
  983. $attr{$name}{manufID} = "7FF" if (!exists $attr{$name}{manufID});
  984. $attr{$name}{room} = $autocreateDeviceRoom;
  985. foreach my $attrCntr (keys %{$EnO_eepConfig{$eep}{attr}}) {
  986. if ($attrCntr ne "subDef") {
  987. $attr{$name}{$attrCntr} = $EnO_eepConfig{$eep}{attr}{$attrCntr};
  988. }
  989. }
  990. EnOcean_CreateSVG('del', $hash, $a[2]);
  991. } else {
  992. return "EEP $rorg-$func-$type not supported";
  993. }
  994. } else {
  995. return "wrong syntax: define <name> EnOcean <8-digit-hex-code> [<EEP>]|getNextID|<EEP>";
  996. }
  997. } else {
  998. return "wrong syntax: define <name> EnOcean <8-digit-hex-code> [<EEP>]|getNextID|<EEP>";
  999. }
  1000. # device specific actions
  1001. if (exists($attr{$name}{subType}) && $attr{$name}{subType} =~ m/^hvac\.0(1|4)$/) {
  1002. # pid parameter
  1003. @{$hash->{helper}{calcPID}} = (undef, $hash, 'defined');
  1004. $hash->{helper}{stopped} = 0;
  1005. #$hash->{helper}{adjust} = '';
  1006. }
  1007. # all notifys needed
  1008. #$hash->{NOTIFYDEV} = "global";
  1009. Log3 $name, 5, "EnOcean_define for device $name executed.";
  1010. return undef;
  1011. }
  1012. # Get
  1013. sub EnOcean_Get($@)
  1014. {
  1015. my ($hash, @a) = @_;
  1016. return "no get value specified" if (@a < 2);
  1017. my $name = $hash->{NAME};
  1018. if (IsDisabled($name)) {
  1019. Log3 $name, 4, "EnOcean $name get commands disabled.";
  1020. return;
  1021. }
  1022. my $cmdID;
  1023. my $cmdList = "";
  1024. my $data;
  1025. my $destinationID = AttrVal($name, "destinationID", undef);
  1026. if (AttrVal($name, "comMode", "uniDir") eq "biDir") {
  1027. $destinationID = defined(AttrVal($name, "subDef", undef)) ? $hash->{DEF} : "FFFFFFFF";
  1028. $destinationID = "FFFFFFFF" if (uc(AttrVal($name, "subDef", $hash->{DEF})) eq uc($hash->{DEF}));
  1029. } elsif (!defined $destinationID || $destinationID eq "multicast") {
  1030. $destinationID = "FFFFFFFF";
  1031. } elsif ($destinationID eq "unicast") {
  1032. $destinationID = defined(AttrVal($name, "subDef", undef)) ? $hash->{DEF} : "FFFFFFFF";
  1033. $destinationID = "FFFFFFFF" if (uc(AttrVal($name, "subDef", $hash->{DEF})) eq uc($hash->{DEF}));
  1034. } elsif ($destinationID !~ m/^[\dA-Fa-f]{8}$/) {
  1035. return "DestinationID $destinationID wrong, choose <8-digit-hex-code>.";
  1036. }
  1037. $destinationID = uc($destinationID);
  1038. my $eep = uc(AttrVal($name, "eep", "00-00-00"));
  1039. if ($eep =~ m/^([A-Fa-f0-9]{2})-([A-Fa-f0-9]{2})-([A-Fa-f0-9]{2})$/i) {
  1040. $eep = (((hex($1) << 6) | hex($2)) << 7) | hex($3);
  1041. } else {
  1042. $eep = (((hex("FF") << 6) | hex("3F")) << 7) | hex("7F");
  1043. }
  1044. my $manufID = uc(AttrVal($name, "manufID", ""));
  1045. my $model = AttrVal($name, "model", "");
  1046. my $packetType = 1;
  1047. $packetType = 0x0A if (ReadingsVal($hash->{IODev}{NAME}, "mode", "00") eq "01");
  1048. my $remoteID = AttrVal($name, "remoteID", undef);
  1049. my $remoteManufID = uc(AttrVal($name, "remoteManufID", AttrVal($name, "manufID", "")));
  1050. my $rorg;
  1051. my $status = '00';
  1052. my $st = AttrVal($name, "subType", "");
  1053. my $stSet = AttrVal($name, "subTypeSet", undef);
  1054. if (defined $stSet) {$st = $stSet;}
  1055. my $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
  1056. if ($subDef !~ m/^[\dA-F]{8}$/) {return "SenderID $subDef wrong, choose <8-digit-hex-code>.";}
  1057. my $timeNow = TimeNow();
  1058. if (AttrVal($name, "remoteManagement", "off") eq "manager") {
  1059. # Remote Management
  1060. $cmdList .= "remoteDevCfg remoteFunctions:noArg remoteID:noArg remoteLinkCfg remoteLinkTableInfo:noArg remoteLinkTable remoteLinkTableGP remotePing:noArg remoteProductID:noArg remoteRepeater:noArg remoteStatus:noArg ";
  1061. }
  1062. if (AttrVal($name, "signal", "off") eq "on") {
  1063. # signal telegram
  1064. $cmdList .= "signal:energy,revision,RXlevel,harvester ";
  1065. }
  1066. # control get actions
  1067. # $updateState = -1: no get commands available e. g. sensors
  1068. # 0: execute get commands
  1069. # 1: execute get commands and and update reading state
  1070. # 2: execute get commands delayed
  1071. my $updateState = 1;
  1072. #Log3 $name, 5, "EnOcean $name EnOcean_Get command: " . join(" ", @a);
  1073. shift @a;
  1074. for (my $i = 0; $i < @a; $i++) {
  1075. my $cmd = $a[$i];
  1076. if ($cmd eq "remoteID") {
  1077. $cmdID = 4;
  1078. $manufID = 0x7FF;
  1079. $packetType = 7;
  1080. $rorg = "C5";
  1081. shift(@a);
  1082. $data = sprintf "0004%04X%06X", $manufID, ($eep << 3);
  1083. $destinationID = "FFFFFFFF";
  1084. $status = '0F';
  1085. $hash->{IODev}{helper}{remoteAnswerWait}{0x604}{hash} = $hash;
  1086. #####
  1087. #my %functionHash = (hash => $hash, param => 0x604);
  1088. #RemoveInternalTimer(\%functionHash);
  1089. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1090. RemoveInternalTimer($hash->{helper}{timer}{0x604}) if(exists $hash->{helper}{timer}{0x604});
  1091. $hash->{helper}{timer}{0x604} = {hash => $hash, param => 0x604};
  1092. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x604}, 0);
  1093. Log3 $name, 3, "EnOcean get $name $cmd";
  1094. } elsif ($cmd eq "remotePing") {
  1095. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1096. $cmdID = 6;
  1097. $manufID = 0x7FF;
  1098. $packetType = 7;
  1099. $rorg = "C5";
  1100. shift(@a);
  1101. $data = sprintf "0006%04X", $manufID;
  1102. $destinationID = $remoteID;
  1103. $status = '0F';
  1104. $hash->{IODev}{helper}{remoteAnswerWait}{0x606}{hash} = $hash;
  1105. #####
  1106. #my %functionHash = (hash => $hash, param => 0x606);
  1107. #RemoveInternalTimer(\%functionHash);
  1108. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1109. RemoveInternalTimer($hash->{helper}{timer}{0x606}) if(exists $hash->{helper}{timer}{0x606});
  1110. $hash->{helper}{timer}{0x606} = {hash => $hash, param => 0x606};
  1111. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x606}, 0);
  1112. Log3 $name, 3, "EnOcean get $name $cmd";
  1113. } elsif ($cmd eq "remoteFunctions") {
  1114. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1115. $cmdID = 7;
  1116. $manufID = 0x7FF;
  1117. $packetType = 7;
  1118. $rorg = "C5";
  1119. shift(@a);
  1120. $data = sprintf "0007%04X", $manufID;
  1121. $destinationID = $remoteID;
  1122. $status = '0F';
  1123. $hash->{IODev}{helper}{remoteAnswerWait}{0x607}{hash} = $hash;
  1124. #####
  1125. #my %functionHash = (hash => $hash, param => 0x607);
  1126. #RemoveInternalTimer(\%functionHash);
  1127. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1128. RemoveInternalTimer($hash->{helper}{timer}{0x607}) if(exists $hash->{helper}{timer}{0x607});
  1129. $hash->{helper}{timer}{0x607} = {hash => $hash, param => 0x607};
  1130. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x607}, 0);
  1131. Log3 $name, 3, "EnOcean get $name $cmd";
  1132. } elsif ($cmd eq "remoteStatus") {
  1133. $cmdID = 8;
  1134. $manufID = 0x7FF;
  1135. $packetType = 7;
  1136. $rorg = "C5";
  1137. shift(@a);
  1138. $data = sprintf "0008%04X", $manufID;
  1139. if (defined $remoteID) {
  1140. $destinationID = $remoteID;
  1141. } else {
  1142. $destinationID = 'F' x 8;
  1143. }
  1144. $status = '0F';
  1145. $hash->{IODev}{helper}{remoteAnswerWait}{0x608}{hash} = $hash;
  1146. #####
  1147. #my %functionHash = (hash => $hash, param => 0x608);
  1148. #RemoveInternalTimer(\%functionHash);
  1149. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1150. RemoveInternalTimer($hash->{helper}{timer}{0x608}) if(exists $hash->{helper}{timer}{0x608});
  1151. $hash->{helper}{timer}{0x608} = {hash => $hash, param => 0x608};
  1152. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x608}, 0);
  1153. Log3 $name, 3, "EnOcean get $name $cmd";
  1154. } elsif ($cmd eq "remoteLinkTableInfo") {
  1155. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1156. $cmdID = 0x210;
  1157. $manufID = 0x7FF;
  1158. $packetType = 7;
  1159. $rorg = "C5";
  1160. shift(@a);
  1161. $data = sprintf "%04X%04X", $cmdID, $manufID;
  1162. $destinationID = $remoteID;
  1163. $status = '0F';
  1164. $hash->{IODev}{helper}{remoteAnswerWait}{0x810}{hash} = $hash;
  1165. #####
  1166. #my %functionHash = (hash => $hash, param => 0x810);
  1167. #RemoveInternalTimer(\%functionHash);
  1168. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1169. RemoveInternalTimer($hash->{helper}{timer}{0x810}) if(exists $hash->{helper}{timer}{0x810});
  1170. $hash->{helper}{timer}{0x810} = {hash => $hash, param => 0x810};
  1171. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x810}, 0);
  1172. Log3 $name, 3, "EnOcean get $name $cmd";
  1173. } elsif ($cmd eq "remoteLinkTable") {
  1174. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1175. $cmdID = 0x211;
  1176. $manufID = 0x7FF;
  1177. $packetType = 7;
  1178. $rorg = "C5";
  1179. my $startRef;
  1180. my $endRef;
  1181. my $direction;
  1182. if (defined($a[1]) && defined($a[2]) && defined($a[3]) && $a[1] =~ m/^in|out$/ && $a[2] =~ m/^[\dA-Fa-f]{2}$/ && $a[3] =~ m/^[\dA-Fa-f]{2}$/) {
  1183. shift(@a);
  1184. $direction = shift(@a);
  1185. $direction = $direction eq 'out' ? '80' : '00';
  1186. $startRef = uc(shift(@a));
  1187. $endRef = uc(shift(@a));
  1188. ($startRef, $endRef) = ($endRef, $startRef) if (hex($startRef) > hex($endRef));
  1189. } else {
  1190. return "Wrong parameter or direction/startRef/endRef not defined.";
  1191. }
  1192. $data = sprintf "%04X%04X%2s%2s%2s", $cmdID, $manufID, $direction, $startRef, $endRef;
  1193. $destinationID = $remoteID;
  1194. $status = '0F';
  1195. $hash->{IODev}{helper}{remoteAnswerWait}{0x811}{hash} = $hash;
  1196. #####
  1197. #my %functionHash = (hash => $hash, param => 0x811);
  1198. #RemoveInternalTimer(\%functionHash);
  1199. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1200. RemoveInternalTimer($hash->{helper}{timer}{0x811}) if(exists $hash->{helper}{timer}{0x811});
  1201. $hash->{helper}{timer}{0x811} = {hash => $hash, param => 0x811};
  1202. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x811}, 0);
  1203. Log3 $name, 3, "EnOcean get $name $cmd";
  1204. } elsif ($cmd eq "remoteLinkTableGP") {
  1205. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1206. $cmdID = 0x213;
  1207. $manufID = 0x7FF;
  1208. $packetType = 7;
  1209. $rorg = "C5";
  1210. my $direction;
  1211. my $index;
  1212. if (defined($a[1]) && defined($a[2]) && $a[1] =~ m/^in|out$/ && $a[2] =~ m/^[\dA-Fa-f]{2}$/) {
  1213. shift(@a);
  1214. $direction = shift(@a);
  1215. $direction = $direction eq 'out' ? '80' : '00';
  1216. $index = uc(shift(@a));
  1217. } else {
  1218. return "Wrong parameter or direction/index not defined.";
  1219. }
  1220. $data = sprintf "%04X%04X%2s%2s", $cmdID, $manufID, $direction, $index;
  1221. $destinationID = $remoteID;
  1222. $status = '0F';
  1223. $hash->{IODev}{helper}{remoteAnswerWait}{0x813}{hash} = $hash;
  1224. #####
  1225. #my %functionHash = (hash => $hash, param => 0x813);
  1226. #RemoveInternalTimer(\%functionHash);
  1227. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1228. RemoveInternalTimer($hash->{helper}{timer}{0x813}) if(exists $hash->{helper}{timer}{0x813});
  1229. $hash->{helper}{timer}{0x813} = {hash => $hash, param => 0x813};
  1230. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x813}, 0);
  1231. Log3 $name, 3, "EnOcean get $name $cmd";
  1232. } elsif ($cmd eq "remoteProductID") {
  1233. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1234. $cmdID = 0x227;
  1235. $manufID = 0x7FF;
  1236. $packetType = 7;
  1237. $rorg = "C5";
  1238. shift(@a);
  1239. $data = sprintf "%04X%04X", $cmdID, $manufID;
  1240. $destinationID = $remoteID;
  1241. $status = '0F';
  1242. $hash->{IODev}{helper}{remoteAnswerWait}{0x827}{hash} = $hash;
  1243. #####
  1244. #my %functionHash = (hash => $hash, param => 0x827);
  1245. #RemoveInternalTimer(\%functionHash);
  1246. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1247. RemoveInternalTimer($hash->{helper}{timer}{0x827}) if(exists $hash->{helper}{timer}{0x827});
  1248. $hash->{helper}{timer}{0x827} = {hash => $hash, param => 0x827};
  1249. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x827}, 0);
  1250. Log3 $name, 3, "EnOcean get $name $cmd";
  1251. } elsif ($cmd eq "remoteDevCfg") {
  1252. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1253. $cmdID = 0x230;
  1254. $manufID = 0x7FF;
  1255. $packetType = 7;
  1256. $rorg = "C5";
  1257. my $startRef;
  1258. my $endRef;
  1259. my $paraLen = '00';
  1260. if (defined($a[1]) && defined($a[2]) && $a[1] =~ m/^[\dA-Fa-f]{4}$/ && $a[2] =~ m/^[\dA-Fa-f]{4}$/) {
  1261. shift(@a);
  1262. $startRef = uc(shift(@a));
  1263. $endRef = uc(shift(@a));
  1264. ($startRef, $endRef) = ($endRef, $startRef) if (hex($startRef) > hex($endRef));
  1265. if (defined($a[0]) && $a[0] =~ m/^[\dA-Fa-f]{2}$/) {
  1266. $paraLen = uc(shift(@a));
  1267. }
  1268. } else {
  1269. return "Wrong parameter or startRef/endRef not defined.";
  1270. }
  1271. $data = sprintf "%04X%04X%4s%4s%2s", $cmdID, $manufID, $startRef, $endRef, $paraLen;
  1272. $destinationID = $remoteID;
  1273. $status = '0F';
  1274. $hash->{IODev}{helper}{remoteAnswerWait}{0x830}{hash} = $hash;
  1275. #####
  1276. #my %functionHash = (hash => $hash, param => 0x830);
  1277. #RemoveInternalTimer(\%functionHash);
  1278. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1279. RemoveInternalTimer($hash->{helper}{timer}{0x830}) if(exists $hash->{helper}{timer}{0x830});
  1280. $hash->{helper}{timer}{0x830} = {hash => $hash, param => 0x830};
  1281. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x830}, 0);
  1282. Log3 $name, 3, "EnOcean get $name $cmd";
  1283. } elsif ($cmd eq "remoteLinkCfg") {
  1284. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1285. $cmdID = 0x232;
  1286. $manufID = 0x7FF;
  1287. $packetType = 7;
  1288. $rorg = "C5";
  1289. my $direction;
  1290. my $idx;
  1291. my $startRef;
  1292. my $endRef;
  1293. my $paraLen = '00';
  1294. if (defined($a[1]) && defined($a[2]) && defined($a[3]) && defined($a[4])
  1295. && $a[1] =~ m/^in|out$/ && $a[2] =~ m/^[\dA-Fa-f]{2}$/ && $a[3] =~ m/^[\dA-Fa-f]{4}$/ && $a[4] =~ m/^[\dA-Fa-f]{4}$/) {
  1296. shift(@a);
  1297. $direction = shift(@a);
  1298. $direction = $direction eq 'out' ? '80' : '00';
  1299. $idx = uc(shift(@a));
  1300. $startRef = uc(shift(@a));
  1301. $endRef = uc(shift(@a));
  1302. ($startRef, $endRef) = ($endRef, $startRef) if (hex($startRef) > hex($endRef));
  1303. if (defined($a[0]) && $a[0] =~ m/^[\dA-Fa-f]{2}$/) {
  1304. $paraLen = uc(shift(@a));
  1305. }
  1306. } else {
  1307. return "Wrong parameter or startRef/endRef not defined.";
  1308. }
  1309. $data = sprintf "%04X%04X%2s%2s%4s%4s%2s", $cmdID, $manufID, $direction, $idx, $startRef, $endRef, $paraLen;
  1310. $destinationID = $remoteID;
  1311. $status = '0F';
  1312. $hash->{IODev}{helper}{remoteAnswerWait}{0x832}{hash} = $hash;
  1313. #####
  1314. #my %functionHash = (hash => $hash, param => 0x832);
  1315. #RemoveInternalTimer(\%functionHash);
  1316. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1317. RemoveInternalTimer($hash->{helper}{timer}{0x832}) if(exists $hash->{helper}{timer}{0x832});
  1318. $hash->{helper}{timer}{0x832} = {hash => $hash, param => 0x832};
  1319. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x832}, 0);
  1320. Log3 $name, 3, "EnOcean get $name $cmd";
  1321. } elsif ($cmd eq "remoteRepeater") {
  1322. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1323. $cmdID = 0x250;
  1324. $manufID = 0x7FF;
  1325. $packetType = 7;
  1326. $rorg = "C5";
  1327. shift(@a);
  1328. $data = sprintf "%04X%04X", $cmdID, $manufID;
  1329. $destinationID = $remoteID;
  1330. $status = '0F';
  1331. $hash->{IODev}{helper}{remoteAnswerWait}{0x850}{hash} = $hash;
  1332. #####
  1333. #my %functionHash = (hash => $hash, param => 0x850);
  1334. #RemoveInternalTimer(\%functionHash);
  1335. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  1336. RemoveInternalTimer($hash->{helper}{timer}{0x850}) if(exists $hash->{helper}{timer}{0x850});
  1337. $hash->{helper}{timer}{0x850} = {hash => $hash, param => 0x850};
  1338. InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', $hash->{helper}{timer}{0x850}, 0);
  1339. Log3 $name, 3, "EnOcean get $name $cmd";
  1340. } elsif ($cmd eq "signal") {
  1341. # trigger status massage of device
  1342. $rorg = "D0";
  1343. $destinationID = $hash->{DEF} if ($destinationID eq 'F' x 8);
  1344. shift(@a);
  1345. my $trigger = shift(@a);
  1346. my %trigger = ('energy' => '01', 'revision' => '02', 'RXlevel' => '03', 'harvester' => '04');
  1347. if (defined $trigger) {
  1348. if (exists $trigger{$trigger}) {
  1349. $data = '04' . $trigger{$trigger};
  1350. } else {
  1351. return "$cmd <trigger> wrong, choose energy|revision|RXlevel|harvester.";
  1352. }
  1353. } else {
  1354. return "$cmd <trigger> wrong, choose energy|revision|RXlevel|harvester.";
  1355. }
  1356. Log3 $name, 3, "EnOcean get $name $cmd $trigger";
  1357. } elsif ($st eq "switch.05") {
  1358. # Dual Channel Switch Actuator
  1359. # (A5-11-05)
  1360. $rorg = "A5";
  1361. shift(@a);
  1362. $updateState = 0;
  1363. if ($cmd eq "state") {
  1364. # query state
  1365. Log3 $name, 3, "EnOcean get $name $cmd";
  1366. $data = "00000008";
  1367. } else {
  1368. $cmdList .= "state:noArg";
  1369. return "Unknown argument $cmd, choose one of $cmdList";
  1370. }
  1371. } elsif ($st eq "lightCtrl.01") {
  1372. # Central Command, Extended Lighting-Control
  1373. # (A5-38-09)
  1374. $rorg = "A5";
  1375. shift(@a);
  1376. $updateState = 0;
  1377. if ($cmd eq "state") {
  1378. # query state
  1379. Log3 $name, 3, "EnOcean get $name $cmd";
  1380. $data = "00000008";
  1381. } else {
  1382. $cmdList .= "state:noArg";
  1383. return "Unknown argument $cmd, choose one of $cmdList";
  1384. }
  1385. } elsif ($st eq "actuator.01") {
  1386. # Electronic switches and dimmers with Energy Measurement and Local Control
  1387. # (D2-01-00 - D2-01-12)
  1388. $rorg = "D2";
  1389. shift(@a);
  1390. my $channel;
  1391. if ($cmd !~ m/^roomCtrlMode$/) {
  1392. $channel = shift(@a);
  1393. $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
  1394. if (!defined $channel || $channel eq "all") {
  1395. $channel = 30;
  1396. } elsif ($channel eq "input" || $channel + 0 == 31) {
  1397. $channel = 31;
  1398. } elsif ($channel + 0 >= 30) {
  1399. $channel = 30;
  1400. } elsif ($channel + 0 >= 0 && $channel + 0 <= 29) {
  1401. } else {
  1402. return "$cmd <channel> wrong, choose 0...29|all|input.";
  1403. }
  1404. }
  1405. if ($cmd eq "state") {
  1406. $cmdID = 3;
  1407. Log3 $name, 3, "EnOcean get $name $cmd $channel";
  1408. $data = sprintf "%02X%02X", $cmdID, $channel;
  1409. } elsif ($cmd eq "measurement") {
  1410. $cmdID = 6;
  1411. my $query = shift(@a);
  1412. if (!defined $query) {
  1413. return "$cmd <channel> <query> wrong, choose 0...30|all|input energy|power.";
  1414. } elsif ($query eq "energy") {
  1415. $query = 0;
  1416. } elsif ($query eq "power") {
  1417. $query = 1;
  1418. } else {
  1419. return "$cmd <channel> <query> wrong, choose 0...30|all|input energy|power.";
  1420. }
  1421. Log3 $name, 3, "EnOcean get $name $cmd $channel $query";
  1422. $data = sprintf "%02X%02X", $cmdID, $query << 5 | $channel;
  1423. } elsif ($cmd eq "roomCtrlMode") {
  1424. Log3 $name, 3, "EnOcean get $name $cmd";
  1425. $data = '09';
  1426. } elsif ($cmd eq "settings") {
  1427. $cmdID = 10;
  1428. Log3 $name, 3, "EnOcean get $name $cmd $channel";
  1429. $data = sprintf "%02X%02X", $cmdID, $channel;
  1430. } elsif ($cmd eq "special") {
  1431. $rorg = "D1";
  1432. my $query = shift(@a);
  1433. if ($manufID eq "033") {
  1434. if (!defined $query) {
  1435. return "$cmd <channel> <query> wrong, choose health|load|voltage|serialNumber.";
  1436. } elsif ($query eq "health") {
  1437. $query = 7;
  1438. } elsif ($query eq "load") {
  1439. $query = 8;
  1440. } elsif ($query eq "voltage") {
  1441. $query = 9;
  1442. } elsif ($query eq "serialNumber") {
  1443. $query = 0x81;
  1444. } else {
  1445. return "$cmd <channel> <query> wrong, choose health|load|voltage|serialNumber.";
  1446. }
  1447. $data = sprintf "0331%02X", $query;
  1448. Log3 $name, 3, "EnOcean get $name $cmd $channel $query";
  1449. readingsSingleUpdate($hash, "getParam", $query, 0);
  1450. } elsif ($manufID eq "046") {
  1451. if (!defined $query) {
  1452. return "$cmd <channel> <query> wrong, choose reset|firmwareVersion|taughtInDevNum|taughtInDevID.";
  1453. } elsif ($query eq "reset") {
  1454. $query = 1;
  1455. $data = sprintf "0046%02X", $query;
  1456. } elsif ($query eq "firmwareVersion") {
  1457. $query = 2;
  1458. $data = sprintf "0046%02X", $query;
  1459. } elsif ($query eq "taughtInDevNum") {
  1460. $query = 4;
  1461. $data = sprintf "0046%02X", $query;
  1462. } elsif ($query eq "taughtInDevID") {
  1463. $query = 6;
  1464. my $taughtInDevNum = shift(@a);
  1465. if (defined $taughtInDevNum && $taughtInDevNum =~ m/^\d+$/ && $taughtInDevNum >= 0 && $taughtInDevNum <= 23) {
  1466. $data = sprintf "0046%02X%02X", $query, $taughtInDevNum;
  1467. } else {
  1468. return "Usage: taughtInDevID is not numeric or out of range";
  1469. }
  1470. } else {
  1471. return "$cmd <channel> <query> wrong, choose reset|firmwareVersion|taughtInDevNum|taughtInDevID.";
  1472. }
  1473. Log3 $name, 3, "EnOcean get $name $cmd $channel $query";
  1474. readingsSingleUpdate($hash, "getParam", $query, 0);
  1475. }
  1476. } else {
  1477. if ($manufID =~ m/^033|046$/) {
  1478. return "Unknown argument $cmd, choose one of " . $cmdList . "state measurement roomCtrlMode:noArg special settings";
  1479. } else {
  1480. return "Unknown argument $cmd, choose one of " . $cmdList . "state measurement roomCtrlMode:noArg settings";
  1481. }
  1482. }
  1483. } elsif ($st =~ m/^blindsCtrl\.0[01]$/) {
  1484. # Blinds Control for Position and Angle
  1485. # (D2-05-xx)
  1486. $rorg = "D2";
  1487. shift(@a);
  1488. $updateState = 0;
  1489. if ($cmd eq "position") {
  1490. # query position and angle
  1491. $cmdID = 3;
  1492. my $channel = shift(@a);
  1493. if (!defined $channel) {
  1494. $channel = AttrVal($name, "defaultChannel", 'all');
  1495. $channel = $channel eq "all" ? 15 : $channel - 1;
  1496. } elsif ($channel =~ m/^all$/) {
  1497. $channel = 15;
  1498. } elsif ($channel =~ m/^[1234]$/) {
  1499. $channel -= 1;
  1500. } else {
  1501. return "$cmd parameter wrong, choose one of 1|2|3|4|all";
  1502. }
  1503. Log3 $name, 3, "EnOcean get $name $cmd";
  1504. $data = sprintf "%02X", $channel << 4 | $cmdID;
  1505. } else {
  1506. $cmdList .= "position";
  1507. return "Unknown argument $cmd, choose one of $cmdList";
  1508. }
  1509. } elsif ($st eq "multisensor.01") {
  1510. #####
  1511. # Multisensor Windows Handle
  1512. # (D2-06-01)
  1513. $rorg = "D2";
  1514. shift(@a);
  1515. $updateState = 2;
  1516. my $waitingCmds = ReadingsVal($name, "waitingCmds", undef);
  1517. if (defined $waitingCmds) {
  1518. # check presence state
  1519. $waitingCmds = ReadingsVal($name, "presence", "present") eq "absent" ? $waitingCmds & 0xDF | 32 : $waitingCmds & 0xDF;
  1520. } else {
  1521. $waitingCmds = ReadingsVal($name, "presence", "present") eq "absent" ? 32 : 0;
  1522. }
  1523. if ($cmd eq "config") {
  1524. # query config
  1525. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds | 0x80, 0);
  1526. Log3 $name, 3, "EnOcean get $name $cmd";
  1527. } elsif ($cmd eq "log") {
  1528. # query log
  1529. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds | 0x40, 0);
  1530. Log3 $name, 3, "EnOcean get $name $cmd";
  1531. } else {
  1532. $cmdList .= "config:noArg log:noArg";
  1533. return "Unknown argument $cmd, choose one of $cmdList";
  1534. }
  1535. } elsif ($st eq "roomCtrlPanel.00") {
  1536. # Room Control Panel
  1537. # (D2-10-00 - D2-10-02)
  1538. $rorg = "D2";
  1539. shift(@a);
  1540. $updateState = 2;
  1541. if ($cmd eq "data") {
  1542. # data request
  1543. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 1, 0);
  1544. Log3 $name, 3, "EnOcean get $name $cmd";
  1545. } elsif ($cmd eq "config") {
  1546. # configuration request
  1547. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 32, 0);
  1548. Log3 $name, 3, "EnOcean get $name $cmd";
  1549. } elsif ($cmd eq "roomCtrl") {
  1550. # room control setup request
  1551. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 128, 0);
  1552. Log3 $name, 3, "EnOcean get $name $cmd";
  1553. } elsif ($cmd eq "timeProgram") {
  1554. # time program request
  1555. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 256, 0);
  1556. Log3 $name, 3, "EnOcean get $name $cmd";
  1557. } else {
  1558. $cmdList .= "data:noArg config:noArg roomCtrl:noArg timeProgram:noArg";
  1559. return "Unknown argument $cmd, choose one of $cmdList";
  1560. }
  1561. } elsif ($st eq "fanCtrl.00") {
  1562. # Fan Control
  1563. # (D2-20-00 - D2-20-02)
  1564. $rorg = "D2";
  1565. shift(@a);
  1566. $updateState = 0;
  1567. if ($cmd eq "state") {
  1568. # query position and angle
  1569. $data = "F6FFFFFF";
  1570. Log3 $name, 3, "EnOcean get $name $cmd DATA: $data";
  1571. } else {
  1572. $cmdList .= "state:noArg";
  1573. return "Unknown argument $cmd, choose one of $cmdList";
  1574. }
  1575. } elsif ($st eq "heatRecovery.00") {
  1576. # heat recovery ventilation
  1577. # (D2-50-00)
  1578. $rorg = "D2";
  1579. shift(@a);
  1580. $updateState = 0;
  1581. if ($cmd eq "basicState") {
  1582. # query switch state
  1583. $data = "00";
  1584. Log3 $name, 3, "EnOcean get $name $cmd";
  1585. } elsif ($cmd eq "extendedState") {
  1586. # query switch state
  1587. $data = "01";
  1588. Log3 $name, 3, "EnOcean get $name $cmd";
  1589. } else {
  1590. $cmdList .= "basicState:noArg extendedState:noArg";
  1591. return "Unknown argument $cmd, choose one of $cmdList";
  1592. }
  1593. } elsif ($st eq "valveCtrl.00" && AttrVal($name, "devMode", "master") eq "master") {
  1594. # Valve Control
  1595. # (D2-A0-01)
  1596. $rorg = "D2";
  1597. shift(@a);
  1598. $updateState = 0;
  1599. if ($cmd eq "state") {
  1600. # query switch state
  1601. $data = "00";
  1602. readingsSingleUpdate($hash, "state", $cmd, 1);
  1603. Log3 $name, 3, "EnOcean get $name $cmd";
  1604. } else {
  1605. $cmdList .= "state:noArg";
  1606. return "Unknown argument $cmd, choose one of $cmdList";
  1607. }
  1608. } elsif ($st eq "remote") {
  1609. return "Unknown argument $cmd, choose one of $cmdList";
  1610. } else {
  1611. # subtype does not support get commands
  1612. if (AttrVal($name, "remoteManagement", "off") eq "manager" || AttrVal($name, "signal", "off") eq "on") {
  1613. return "Unknown argument $cmd, choose one of $cmdList";
  1614. } else {
  1615. return;
  1616. }
  1617. }
  1618. if($updateState != 2) {
  1619. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, $status, $destinationID);
  1620. }
  1621. }
  1622. }
  1623. # Set
  1624. sub EnOcean_Set($@)
  1625. {
  1626. my ($hash, @a) = @_;
  1627. return "no set value specified" if (@a < 2);
  1628. my $name = $hash->{NAME};
  1629. if (IsDisabled($name)) {
  1630. Log3 $name, 4, "EnOcean $name set commands disabled.";
  1631. return;
  1632. }
  1633. my $cmdID;
  1634. my $cmdList = "";
  1635. my @cmdObserve = @a;
  1636. my ($ctrl, $data, $err, $logLevel, $response);
  1637. my $destinationID = AttrVal($name, "destinationID", undef);
  1638. if (AttrVal($name, "comMode", "uniDir") eq "biDir") {
  1639. $destinationID = defined(AttrVal($name, "subDef", undef)) ? $hash->{DEF} : "FFFFFFFF";
  1640. $destinationID = "FFFFFFFF" if (uc(AttrVal($name, "subDef", $hash->{DEF})) eq uc($hash->{DEF}));
  1641. } elsif (!defined $destinationID || $destinationID eq "multicast") {
  1642. $destinationID = "FFFFFFFF";
  1643. } elsif ($destinationID eq "unicast") {
  1644. $destinationID = defined(AttrVal($name, "subDef", undef)) ? $hash->{DEF} : "FFFFFFFF";
  1645. $destinationID = "FFFFFFFF" if (uc(AttrVal($name, "subDef", $hash->{DEF})) eq uc($hash->{DEF}));
  1646. } elsif ($destinationID !~ m/^[\dA-Fa-f]{8}$/) {
  1647. return "DestinationID $destinationID wrong, choose <8-digit-hex-code>.";
  1648. }
  1649. $destinationID = uc($destinationID);
  1650. my $IODev = $hash->{IODev}{NAME};
  1651. my $IOHash = $defs{$IODev};
  1652. my $manufID = uc(AttrVal($name, "manufID", ""));
  1653. my $model = AttrVal($name, "model", "");
  1654. my $packetType = 1;
  1655. $packetType = 0x0A if (ReadingsVal($IODev, "mode", "00") eq "01");
  1656. my $remoteID = AttrVal($name, "remoteID", undef);
  1657. my $remoteManufID = uc(AttrVal($name, "remoteManufID", AttrVal($name, "manufID", "")));
  1658. my $rorg;
  1659. my $sendCmd = 1;
  1660. my $status = "00";
  1661. my $st = AttrVal($name, "subType", "");
  1662. my $stSet = AttrVal($name, "subTypeSet", undef);
  1663. if (defined $stSet) {$st = $stSet;}
  1664. my $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
  1665. if ($subDef !~ m/^[\dA-F]{8}$/) {return "SenderID $subDef wrong, choose <8-digit-hex-code>.";}
  1666. my $switchMode = AttrVal($name, "switchMode", "switch");
  1667. my $timeNow = TimeNow();
  1668. my $remoteManagement = AttrVal($name, "remoteManagement", "off");
  1669. if ($remoteManagement eq "manager") {
  1670. # Remote Management Manager
  1671. $cmdList = "remoteAction:noArg remoteApplyChanges:devCfg,linkTable,no_change remoteDevCfg remoteLinkCfg remoteLock:noArg remoteLearnMode remoteLinkTable remoteLinkTableGP remoteRepeater remoteRepeaterFilter remoteReset:devCfg,linkTableIn,linkTableOut,no_change remoteRLT remoteSetCode:noArg remoteTeach remoteUnlock:noArg ";
  1672. } elsif ($remoteManagement eq "client" || $st eq "remote") {
  1673. # Remote Management Client
  1674. $cmdList .= "remoteLock:noArg remoteUnlock ";
  1675. }
  1676. # control set actions
  1677. # $updateState = -1: no set commands available e. g. sensors
  1678. # 0: execute set commands
  1679. # 1: execute set commands and and update reading state
  1680. # 2: execute set commands delayed
  1681. # 3: internal command
  1682. my $updateState = AttrVal($name, "comMode", "uniDir") eq "uniDir" ? 1 : 0;
  1683. my $updateStateAttr = AttrVal($name, "updateState", "default");
  1684. shift @a;
  1685. for (my $i = 0; $i < @a; $i++) {
  1686. my $cmd = $a[$i];
  1687. my ($cmd1, $cmd2);
  1688. if ($cmd eq "remoteUnlock") {
  1689. if ($remoteManagement eq "manager") {
  1690. # manager
  1691. $cmdID = 1;
  1692. my $remoteCode = AttrVal($name, "remoteCode", undef);
  1693. return "Security Code not defined, set attr $name remoteCode <00000001 ... FFFFFFFE>!" if (!defined($remoteCode));
  1694. $manufID = 0x7FF;
  1695. $packetType = 7;
  1696. $rorg = "C5";
  1697. $data = sprintf "%04X%04X%s", $cmdID, $manufID, uc($remoteCode);
  1698. if (defined $remoteID) {
  1699. $destinationID = $remoteID;
  1700. } else {
  1701. $destinationID = 'F' x 8;
  1702. }
  1703. $updateState = 0;
  1704. } else {
  1705. # client
  1706. my $remoteUnlockPeriod = 1800;
  1707. if (defined $a[1]) {
  1708. if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 1800) {
  1709. $remoteUnlockPeriod = $a[1];
  1710. shift(@a);
  1711. } else {
  1712. return "Usage: $a[1] is not numeric or out of range";
  1713. }
  1714. }
  1715. $hash->{RemoteClientUnlock} = 1;
  1716. #####
  1717. #my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
  1718. #RemoveInternalTimer(\%functionHash);
  1719. #InternalTimer(gettimeofday() + $remoteUnlockPeriod, 'EnOcean_cdmClearHashVal', \%functionHash, 0);
  1720. RemoveInternalTimer($hash->{helper}{timer}{RemoteClientUnlock}) if(exists $hash->{helper}{timer}{RemoteClientUnlock});
  1721. $hash->{helper}{timer}{RemoteClientUnlock} = {hash => $hash, param => 'RemoteClientUnlock'};
  1722. InternalTimer(gettimeofday() + $remoteUnlockPeriod, 'EnOcean_cdmClearHashVal', $hash->{helper}{timer}{RemoteClientUnlock}, 0);
  1723. $updateState = 3;
  1724. }
  1725. Log3 $name, 3, "EnOcean set $name $cmd";
  1726. } elsif ($cmd eq "remoteLock") {
  1727. if ($remoteManagement eq "manager") {
  1728. # manager
  1729. $cmdID = 2;
  1730. my $remoteCode = AttrVal($name, "remoteCode", undef);
  1731. return "Security Code not defined, set attr $name remoteCode <00000001 ... FFFFFFFE>!" if (!defined($remoteCode));
  1732. $manufID = 0x7FF;
  1733. $packetType = 7;
  1734. $rorg = "C5";
  1735. $data = sprintf "%04X%04X%s", $cmdID, $manufID, uc($remoteCode);
  1736. if (defined $remoteID) {
  1737. $destinationID = $remoteID;
  1738. } else {
  1739. $destinationID = 'F' x 8;
  1740. }
  1741. $updateState = 0;
  1742. } else {
  1743. # client
  1744. delete $hash->{RemoteClientUnlock};
  1745. #####
  1746. #my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
  1747. #RemoveInternalTimer(\%functionHash);
  1748. RemoveInternalTimer($hash->{helper}{timer}{RemoteClientUnlock}) if(exists $hash->{helper}{timer}{RemoteClientUnlock});
  1749. $updateState = 3;
  1750. }
  1751. Log3 $name, 3, "EnOcean set $name $cmd";
  1752. } elsif ($cmd eq "remoteSetCode") {
  1753. $cmdID = 3;
  1754. my $remoteCode = AttrVal($name, "remoteCode", undef);
  1755. return "Security Code not defined, set attr $name remoteCode <00000001 ... FFFFFFFE>!" if (!defined($remoteCode));
  1756. $manufID = 0x7FF;
  1757. $packetType = 7;
  1758. $rorg = "C5";
  1759. $data = sprintf "%04X%04X%s", $cmdID, $manufID, uc($remoteCode);
  1760. if (defined $remoteID) {
  1761. $destinationID = $remoteID;
  1762. } else {
  1763. $destinationID = 'F' x 8;
  1764. }
  1765. Log3 $name, 3, "EnOcean set $name $cmd";
  1766. $updateState = 0;
  1767. } elsif ($cmd eq "remoteAction") {
  1768. $cmdID = 5;
  1769. $manufID = 0x7FF;
  1770. $packetType = 7;
  1771. $rorg = "C5";
  1772. $data = sprintf "%04X%04X", $cmdID, $manufID;
  1773. if (defined $remoteID) {
  1774. $destinationID = $remoteID;
  1775. } else {
  1776. $destinationID = 'F' x 8;
  1777. }
  1778. Log3 $name, 3, "EnOcean set $name $cmd";
  1779. $updateState = 0;
  1780. } elsif ($cmd eq "remoteLinkTable") {
  1781. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1782. if (defined $a[1] && defined $a[2] && defined $a[3] && defined $a[4] && defined $a[5] &&
  1783. $a[1] =~ m/^in|out$/ && $a[2] =~ m/^[\dA-Fa-f]{2}$/ && $a[3] =~ m/^[\dA-Fa-f]{8}$/ && $a[4] =~ m/^[\dA-Fa-f]{2}-[\dA-Fa-f]{2}-[\dA-Fa-f]{2}$/ && $a[5] =~ m/^[\dA-Fa-f]{2}$/) {
  1784. $cmdID = 0x212;
  1785. $manufID = 0x7FF;
  1786. $packetType = 7;
  1787. $rorg = "C5";
  1788. $a[4] =~ m/^(..)-(..)-(..)$/;
  1789. $data = sprintf "%04X%04X%s%s%s%s%s%s%s", $cmdID, $manufID, $a[1] eq 'out' ? '80' : '00', uc($a[2]), uc($a[3]), uc($1), uc($2), uc($3), uc($a[5]);
  1790. splice(@a,0,5);
  1791. Log3 $name, 3, "EnOcean set $name $cmd";
  1792. $updateState = 0;
  1793. } else {
  1794. return "Usage: $cmd arguments needed or wrong.";
  1795. }
  1796. } elsif ($cmd eq "remoteLinkTableGP") {
  1797. # gpDef example: ch2:O:1:24:1:7:-40:1:40:1
  1798. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1799. if (defined $a[1] && defined $a[2] && defined $a[3] && $a[1] =~ m/^in|out$/ && $a[2] =~ m/^[\dA-Fa-f]{2}$/) {
  1800. $cmdID = 0x214;
  1801. $manufID = 0x7FF;
  1802. $packetType = 7;
  1803. $rorg = "C5";
  1804. my $direction = $a[1] eq 'out' ? '80' : '00';
  1805. my $gpDef;
  1806. my ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
  1807. split(':', $a[3]);
  1808. if ($channelDir eq "O" && $a[1] eq 'out' || $channelDir eq "I" && $a[1] eq 'in') {
  1809. # add channel-, signal- and valuetype
  1810. $gpDef .= substr(unpack('B8', pack('C', $channelType)), 6) .
  1811. unpack('B8', pack('C', $signalType)) .
  1812. substr(unpack('B8', pack('C', $valueType)), 6);
  1813. if ($channelType == 1 || $channelType == 3) {
  1814. # data, enumeration: add resolution
  1815. $gpDef .= substr(unpack('B8', pack('C', $resolution)), 4);
  1816. }
  1817. if ($channelType == 1) {
  1818. # data: add engineering and scaling
  1819. $gpDef .= unpack('B8', pack('c', $engMin)) .
  1820. substr(unpack('B8', pack('C', $scalingMin)), 4) .
  1821. unpack('B8', pack('c', $engMax)) .
  1822. substr(unpack('B8', pack('C', $scalingMax)), 4);
  1823. }
  1824. } else {
  1825. return "Usage: Link Table GP Entry Direction wrong.";
  1826. }
  1827. if (length($gpDef) % 8) {
  1828. # fill with trailing zeroes to x bytes
  1829. $gpDef .= 0 x (8 - length($gpDef) % 8);
  1830. }
  1831. $data = sprintf "%04X%04X%s%s%s", $cmdID, $manufID, $a[1] eq 'out' ? '80' : '00', uc($a[2]), EnOcean_convBitToHex($gpDef);
  1832. splice(@a,0,3);
  1833. Log3 $name, 3, "EnOcean set $name $cmd";
  1834. $updateState = 0;
  1835. } else {
  1836. return "Usage: $cmd arguments needed or wrong.";
  1837. }
  1838. } elsif ($cmd eq "remoteLearnMode") {
  1839. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1840. if (defined $a[1]) {
  1841. $cmdID = 0x220;
  1842. $manufID = 0x7FF;
  1843. $packetType = 7;
  1844. $rorg = "C5";
  1845. $destinationID = $remoteID;
  1846. my $learnMode;
  1847. my $cmdVal = 0;
  1848. if ($a[1] eq 'in') {
  1849. $learnMode = '00';
  1850. shift(@a);
  1851. } elsif ($a[1] eq 'out') {
  1852. $learnMode = '40';
  1853. shift(@a);
  1854. } elsif ($a[1] eq 'off') {
  1855. $learnMode = '80';
  1856. shift(@a);
  1857. } else {
  1858. return "Usage: $cmd $a[1] argument unknown.";
  1859. }
  1860. if (defined $a[1] && $a[1] =~ m/^[\dA-Fa-f]{2}$/) {
  1861. $cmdVal = $a[1];
  1862. shift(@a);
  1863. } else {
  1864. return "Usage: $cmd <learnMode> argument needed or wrong.";
  1865. }
  1866. $data = sprintf "%04X%04X%s%s", $cmdID, $manufID, $learnMode, $cmdVal;
  1867. Log3 $name, 3, "EnOcean set $name $cmd";
  1868. $updateState = 0;
  1869. }
  1870. } elsif ($cmd eq "remoteTeach") {
  1871. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1872. if (defined $a[1] && $a[1] =~ m/^[\dA-Fa-f]{2}$/) {
  1873. $cmdID = 0x221;
  1874. $manufID = 0x7FF;
  1875. $packetType = 7;
  1876. $rorg = "C5";
  1877. $destinationID = $remoteID;
  1878. $data = sprintf "%04X%04X%s", $cmdID, $manufID, $a[1];
  1879. shift(@a);
  1880. Log3 $name, 3, "EnOcean set $name $cmd";
  1881. $updateState = 0;
  1882. if (!exists($hash->{IODev}{Teach})) {
  1883. # enable teach-in receiving for 3 sec
  1884. $hash->{IODev}{Teach} = 1;
  1885. #####
  1886. #my %functionHash = (hash => $IOHash, param => 'Teach');
  1887. #RemoveInternalTimer(\%functionHash);
  1888. #InternalTimer(gettimeofday() + 3, 'EnOcean_cdmClearHashVal', \%functionHash, 0);
  1889. RemoveInternalTimer($hash->{helper}{timer}{Teach}) if(exists $hash->{helper}{timer}{Teach});
  1890. $hash->{helper}{timer}{Teach} = {hash => $IOHash, param => 'Teach'};
  1891. InternalTimer(gettimeofday() + 3, 'EnOcean_cdmClearHashVal', $hash->{helper}{timer}{Teach}, 0);
  1892. }
  1893. } else {
  1894. return "Usage: $cmd argument needed or wrong.";
  1895. }
  1896. } elsif ($cmd eq "remoteReset") {
  1897. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1898. if (defined $a[1] && $a[1] =~ m/^devCfg|linkTableIn|linkTableOut|no_change$/) {
  1899. $cmdID = 0x224;
  1900. $manufID = 0x7FF;
  1901. $packetType = 7;
  1902. $rorg = "C5";
  1903. $destinationID = $remoteID;
  1904. my %changeCmd = ('devCfg' => '80', 'linkTableIn' => '40', 'linkTableOut' => '20', 'no_change' => '00');
  1905. $data = sprintf "%04X%04X%s", $cmdID, $manufID, $changeCmd{$a[1]};
  1906. shift(@a);
  1907. Log3 $name, 3, "EnOcean set $name $cmd";
  1908. $updateState = 0;
  1909. } else {
  1910. return "Usage: $cmd argument needed or wrong.";
  1911. }
  1912. } elsif ($cmd eq "remoteRLT") {
  1913. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1914. if (defined $a[1] && defined $a[2] && $a[1] =~ m/^off|on$/ && $a[2] =~ m/^[0-7][1-9A-Fa-f]$/) {
  1915. $cmdID = 0x225;
  1916. $manufID = 0x7FF;
  1917. $packetType = 7;
  1918. $rorg = "C5";
  1919. $destinationID = $remoteID;
  1920. my $rltMode = hex $a[2];
  1921. $rltMode |= 0x80 if ($a[1] eq 'on');
  1922. $data = sprintf "%04X%04X%02X", $cmdID, $manufID, $rltMode;
  1923. splice(@a,0,2);
  1924. Log3 $name, 3, "EnOcean set $name $cmd";
  1925. $updateState = 0;
  1926. }
  1927. } elsif ($cmd eq "remoteApplyChanges") {
  1928. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1929. if (defined $a[1] && $a[1] =~ m/^devCfg|linkTable|no_change$/) {
  1930. $cmdID = 0x226;
  1931. $manufID = 0x7FF;
  1932. $packetType = 7;
  1933. $rorg = "C5";
  1934. $destinationID = $remoteID;
  1935. my %changeCmd = ('devCfg' => '40', 'linkTable' => '80', 'no_change' => '00');
  1936. $data = sprintf "%04X%04X%s", $cmdID, $manufID, $changeCmd{$a[1]};
  1937. shift(@a);
  1938. Log3 $name, 3, "EnOcean set $name $cmd";
  1939. $updateState = 0;
  1940. } else {
  1941. return "Usage: $cmd argument needed or wrong.";
  1942. }
  1943. } elsif ($cmd eq "remoteDevCfg") {
  1944. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1945. if (defined $a[1] && defined $a[2] && $a[1] =~ m/^[\dA-Fa-f]{4}$/ && $a[2] =~ m/^[\dA-Fa-f]{2}[\dA-Fa-f]*$/ && length($a[2]) % 2 == 0) {
  1946. $cmdID = 0x231;
  1947. $manufID = 0x7FF;
  1948. $packetType = 7;
  1949. $rorg = "C5";
  1950. $destinationID = $remoteID;
  1951. $data = sprintf "%04X%04X%s%02X%s", $cmdID, $manufID, uc($a[1]), length($a[2]) / 2, uc($a[2]);
  1952. splice(@a,0,2);
  1953. Log3 $name, 3, "EnOcean set $name $cmd";
  1954. $updateState = 0;
  1955. } else {
  1956. return "Usage: $cmd arguments needed or wrong.";
  1957. }
  1958. } elsif ($cmd eq "remoteLinkCfg") {
  1959. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1960. if (defined $a[1] && defined $a[2] && defined $a[3] && defined $a[4] &&
  1961. $a[1] =~ m/^in|out$/ && $a[2] =~ m/^[\dA-Fa-f]{2}$/ && $a[3] =~ m/^[\dA-Fa-f]{4}$/ &&
  1962. $a[4] =~ m/^[\dA-Fa-f]{2}[\dA-Fa-f]*$/ && length($a[4]) % 2 == 0) {
  1963. $cmdID = 0x233;
  1964. $manufID = 0x7FF;
  1965. $packetType = 7;
  1966. $rorg = "C5";
  1967. my $direction = $a[1] eq 'out' ? '80' : '00';
  1968. $destinationID = $remoteID;
  1969. $data = sprintf "%04X%04X%s%s%s%02X%s", $cmdID, $manufID, $direction, uc($a[2]), uc($a[3]), length($a[4]) / 2, uc($a[4]);
  1970. splice(@a,0,4);
  1971. Log3 $name, 3, "EnOcean set $name $cmd";
  1972. $updateState = 0;
  1973. } else {
  1974. return "Usage: $cmd arguments needed or wrong.";
  1975. }
  1976. } elsif ($cmd eq "remoteRepeater") {
  1977. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1978. if (defined $a[1] && defined $a[2] && defined $a[3] &&
  1979. $a[1] =~ m/^on|off|filter$/ && $a[2] =~ m/^[1-2]$/ && $a[3] =~ m/^AND|OR$/) {
  1980. $cmdID = 0x251;
  1981. $manufID = 0x7FF;
  1982. $packetType = 7;
  1983. $rorg = "C5";
  1984. my %repFunc = ('on' => 0x40, 'off' => 0, 'filter' => 0x80);
  1985. my $cmdVal = $repFunc{$a[1]} | ($a[2] == 2 ? 0x20 : 0x10) | ($a[3] eq 'OR' ? 8 : 0);
  1986. $destinationID = $remoteID;
  1987. $data = sprintf "%04X%04X%02X", $cmdID, $manufID, $cmdVal;
  1988. splice(@a,0,3);
  1989. Log3 $name, 3, "EnOcean set $name $cmd";
  1990. $updateState = 0;
  1991. } else {
  1992. return "Usage: $cmd arguments needed or wrong.";
  1993. }
  1994. } elsif ($cmd eq "remoteRepeaterFilter") {
  1995. return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
  1996. if (defined $a[1] && defined $a[2] &&
  1997. $a[1] =~ m/^apply|block|delete|deleteAll$/ && $a[2] =~ m/^destinationID|sourceID|rorg|rssi$/) {
  1998. my %repFilterCtrl = ('deleteAll' => 0x30, 'delete' => 0x20, 'apply' => 0x10, 'block' => 0);
  1999. my $cmdVal = $repFilterCtrl{$a[1]};
  2000. my $cmdAttr;
  2001. if (defined $a[3]) {
  2002. if ($a[2] =~ m/^destinationID$/ && $a[3] =~ m/^[\dA-Fa-f]{8}$/) {
  2003. $cmdVal |= 3;
  2004. $cmdAttr = $a[3];
  2005. } elsif ($a[2] =~ m/^sourceID$/ && $a[3] =~ m/^[\dA-Fa-f]{8}$/) {
  2006. $cmdVal |= 0;
  2007. $cmdAttr = $a[3];
  2008. } elsif ($a[2] =~ m/^rorg$/ && $a[3] =~ m/^[\dA-Fa-f]{2}$/) {
  2009. $cmdVal |= 1;
  2010. $cmdAttr = '0' x 6 . $a[3];
  2011. } elsif ($a[2] =~ m/^rssi$/ && $a[3] =~ m/^-?\d+$/ && abs($a[3]) >= 0 && abs($a[3]) <= 255) {
  2012. $cmdVal |= 2;
  2013. $cmdAttr = sprintf "000000%02X", abs($a[3]);
  2014. } else {
  2015. return "Usage: $cmd $a[2] argument wrong.";
  2016. }
  2017. } else {
  2018. return "Usage: $cmd $a[2] argument needed.";
  2019. }
  2020. $cmdID = 0x252;
  2021. $manufID = 0x7FF;
  2022. $packetType = 7;
  2023. $rorg = "C5";
  2024. $destinationID = $remoteID;
  2025. $data = sprintf "%04X%04X%02X%s", $cmdID, $manufID, $cmdVal, $cmdAttr;
  2026. splice(@a,0,3);
  2027. Log3 $name, 3, "EnOcean set $name $cmd";
  2028. $updateState = 0;
  2029. } else {
  2030. return "Usage: $cmd arguments needed or wrong.";
  2031. }
  2032. } elsif ($st eq "switch") {
  2033. # Rocker Switch, simulate a PTM200 switch module
  2034. # separate first and second action
  2035. ($cmd1, $cmd2) = split(",", $cmd, 2);
  2036. # check values
  2037. if (!defined($EnO_ptm200btn{$cmd1}) || ($cmd2 && !defined($EnO_ptm200btn{$cmd2}))) {
  2038. $cmdList .= join(":noArg ", sort keys %EnO_ptm200btn) . ':noArg';
  2039. return SetExtensions($hash, $cmdList, $name, @a);
  2040. }
  2041. my $channelA = ReadingsVal($name, "channelA", undef);
  2042. my $channelB = ReadingsVal($name, "channelB", undef);
  2043. my $channelC = ReadingsVal($name, "channelC", undef);
  2044. my $channelD = ReadingsVal($name, "channelD", undef);
  2045. my $lastChannel = ReadingsVal($name, ".lastChannel", "A");
  2046. my $releasedChannel = AttrVal($name, "releasedChannel", "auto");
  2047. my $subDefA = AttrVal($name, "subDefA", $subDef);
  2048. my $subDefB = AttrVal($name, "subDefB", $subDef);
  2049. my $subDefC = AttrVal($name, "subDefC", $subDef);
  2050. my $subDefD = AttrVal($name, "subDefD", $subDef);
  2051. my $subDefI = AttrVal($name, "subDefI", $subDef);
  2052. my $subDef0 = AttrVal($name, "subDef0", $subDef);
  2053. my $switchType = AttrVal($name, "switchType", "direction");
  2054. # first action
  2055. if ($cmd1 eq "released") {
  2056. if ($switchType eq "central") {
  2057. if ($releasedChannel eq "auto") {
  2058. if ($lastChannel =~ m/0|I/) {
  2059. $releasedChannel = $lastChannel;
  2060. } else {
  2061. $releasedChannel = 0;
  2062. }
  2063. } elsif ($releasedChannel !~ m/0|I/) {
  2064. $releasedChannel = 0;
  2065. }
  2066. } elsif ($switchType eq "channel") {
  2067. if ($releasedChannel eq "auto") {
  2068. if ($lastChannel =~ m/A|B|C|D/) {
  2069. $releasedChannel = $lastChannel;
  2070. } else {
  2071. $releasedChannel = "A";
  2072. }
  2073. } elsif ($releasedChannel !~ m/A|B|C|D/) {
  2074. $releasedChannel = "A";
  2075. }
  2076. }
  2077. $subDef = AttrVal($name, "subDef" . $releasedChannel, $subDef);
  2078. } elsif ($switchType eq "central") {
  2079. if ($cmd1 =~ m/.0/) {
  2080. $subDef = $subDef0;
  2081. $lastChannel = 0;
  2082. } elsif ($cmd1 =~ m/.I/) {
  2083. $subDef = $subDefI;
  2084. $lastChannel = "I";
  2085. }
  2086. } elsif ($switchType eq "channel") {
  2087. $lastChannel = substr($cmd1, 0, 1);
  2088. $subDef = AttrVal($name, "subDef" . $lastChannel, $subDefA);
  2089. } else {
  2090. $lastChannel = substr($cmd1, 0, 1);
  2091. }
  2092. if ($switchType eq "universal") {
  2093. if ($cmd1 =~ m/A./ && (!defined($channelA) || $cmd1 ne $channelA)) {
  2094. $cmd1 = "A0";
  2095. } elsif ($cmd1 =~ m/B./ && (!defined($channelB) || $cmd1 ne $channelB)) {
  2096. $cmd1 = "B0";
  2097. } elsif ($cmd1 =~ m/C./ && (!defined($channelC) || $cmd1 ne $channelC)) {
  2098. $cmd1 = "C0";
  2099. } elsif ($cmd1 =~ m/D./ && (!defined($channelD) || $cmd1 ne $channelD)) {
  2100. $cmd1 = "D0";
  2101. } elsif ($cmd1 eq "released") {
  2102. } else {
  2103. $sendCmd = undef;
  2104. }
  2105. }
  2106. # second action
  2107. if ($cmd2 && $switchType eq "universal") {
  2108. if ($cmd2 =~ m/A./ && (!defined($channelA) || $cmd2 ne $channelA)) {
  2109. $cmd2 = "A0";
  2110. } elsif ($cmd2 =~ m/B./ && (!defined($channelB) || $cmd2 ne $channelB)) {
  2111. $cmd2 = "B0";
  2112. } elsif ($cmd2 =~ m/C./ && (!defined($channelC) || $cmd2 ne $channelC)) {
  2113. $cmd2 = "C0";
  2114. } elsif ($cmd2 =~ m/D./ && (!defined($channelD) || $cmd2 ne $channelD)) {
  2115. $cmd2 = "D0";
  2116. } else {
  2117. $cmd2 = undef;
  2118. }
  2119. if ($cmd2 && undef($sendCmd)) {
  2120. # only second action has changed, send as first action
  2121. $cmd1 = $cmd2;
  2122. $cmd2 = undef;
  2123. $sendCmd = 1;
  2124. }
  2125. }
  2126. # convert and send first and second command
  2127. my $switchCmd;
  2128. ($switchCmd, $status) = split(':', $EnO_ptm200btn{$cmd1}, 2);
  2129. # reset T21 status flag if 4 rocker
  2130. $status = '10' if ($switchCmd > 3);
  2131. $switchCmd <<= 5;
  2132. if ($cmd1 ne "released") {
  2133. # set the pressed flag
  2134. $switchCmd |= 0x10 ;
  2135. }
  2136. if($cmd2) {
  2137. # execute second action
  2138. if ($switchType =~ m/^central|channel$/) {
  2139. # second action not supported
  2140. $cmd = $cmd1;
  2141. } else {
  2142. my ($d2, undef) = split(':', $EnO_ptm200btn{$cmd2}, 2);
  2143. # reset T21 status flag if 4 rocker
  2144. $status = '10' if ($d2 > 3);
  2145. $switchCmd |= ($d2 << 1) | 0x01;
  2146. }
  2147. }
  2148. if (defined $sendCmd) {
  2149. $data = sprintf "%02X", $switchCmd;
  2150. $rorg = "F6";
  2151. #####
  2152. SetExtensionsCancel($hash);
  2153. Log3 $name, 3, "EnOcean set $name $cmd";
  2154. if ($updateState) {
  2155. readingsSingleUpdate($hash, "channel" . $1, $cmd1, 1) if ($cmd1 =~ m/^([A-D])./);
  2156. readingsSingleUpdate($hash, "channel" . $1, $cmd2, 1) if ($cmd2 && $cmd2 =~ m/^([A-D])./);
  2157. }
  2158. readingsSingleUpdate($hash, ".lastChannel", $lastChannel, 0);
  2159. }
  2160. } elsif ($st eq "switch.00") {
  2161. my $switchCmd = join(",", sort split(",", $cmd, 2));
  2162. ($cmd1, $cmd2) = split(",", $switchCmd, 2);
  2163. $cmd = $switchCmd;
  2164. if ((!defined($switchCmd)) || (!defined($EnO_switch_00Btn{$switchCmd}))) {
  2165. # check values
  2166. $cmdList .= join(":noArg ", keys %EnO_switch_00Btn);
  2167. return SetExtensions($hash, $cmdList, $name, @a);
  2168. } elsif ($switchCmd eq "teachIn") {
  2169. ($err, $rorg, $data) = EnOcean_sndUTE(undef, $hash, AttrVal($name, "comMode", "uniDir"),
  2170. AttrVal($name, "uteResponseRequest", "no"), "in", 0, "D2-03-00");
  2171. $updateState = 0;
  2172. } elsif ($switchCmd eq "teachInSec") {
  2173. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  2174. ($err, $response, $logLevel) = EnOcean_sec_createTeachIn(undef, $hash, "uniDir", "VAES", "D2-03-00", 3,
  2175. "2++", "false", "encryption", $subDef, $destinationID);
  2176. if ($err) {
  2177. Log3 $name, $logLevel, "EnOcean $name Error: $err";
  2178. return $err;
  2179. } else {
  2180. EnOcean_CommandSave(undef, undef);
  2181. Log3 $name, $logLevel, "EnOcean $name $response";
  2182. readingsSingleUpdate($hash, "teach", "STE teach-in sent", 1);
  2183. return(undef);
  2184. }
  2185. } elsif ($switchCmd eq "teachOut") {
  2186. ($err, $rorg, $data) = EnOcean_sndUTE(undef, $hash, AttrVal($name, "comMode", "uniDir"),
  2187. AttrVal($name, "uteResponseRequest", "no"), "out", 0, "D2-03-00");
  2188. $updateState = 0;
  2189. } else {
  2190. $data = sprintf "%02X", $EnO_switch_00Btn{$switchCmd};
  2191. $rorg = "D2";
  2192. #####
  2193. SetExtensionsCancel($hash);
  2194. }
  2195. Log3 $name, 3, "EnOcean set $name $switchCmd";
  2196. } elsif ($st eq "roomSensorControl.01") {
  2197. # Room Sensor and Control Unit (EEP A5-04-01, A5-10-10 ... A5-10-14)
  2198. # [Thermokon SR04 * rH, Thanus SR *, untested]
  2199. # $db[3] is the setpoint where 0x00 = min ... 0xFF = max
  2200. # $db[2] is the humidity where 0x00 = 0%rH ... 0xFA = 100%rH
  2201. # $db[1] is the temperature where 0x00 = 0°C ... 0xFA = +40°C
  2202. # $db[0] bit D0 is the occupy button, pushbutton or slide switch
  2203. $rorg = "A5";
  2204. # primarily temperature from the reference device then the attribute actualTemp is read
  2205. my $temperatureRefDev = AttrVal($name, "temperatureRefDev", undef);
  2206. my $actualTemp = AttrVal($name, "actualTemp", 20);
  2207. $actualTemp = ReadingsVal($temperatureRefDev, "temperature", 20) if (defined $temperatureRefDev);
  2208. $actualTemp = 20 if ($actualTemp !~ m/^[+-]?\d+(\.\d+)?$/);
  2209. $actualTemp = 0 if ($actualTemp < 0);
  2210. $actualTemp = 40 if ($actualTemp > 40);
  2211. $actualTemp = sprintf "%0.1f", $actualTemp;
  2212. # primarily humidity from the reference device then the attribute humidity is read
  2213. my $humidityRefDev = AttrVal($name, "humidityRefDev", undef);
  2214. my $humidity = AttrVal($name, "humidity", 0);
  2215. $humidity = ReadingsVal($humidityRefDev, "humidity", 0) if (defined $humidityRefDev);
  2216. $humidity = 0 if ($humidity !~ m/^\d+(\.\d+)?$/);
  2217. $humidity = 0 if ($humidity < 0);
  2218. $humidity = 100 if ($humidity > 100);
  2219. $humidity = sprintf "%d", $humidity;
  2220. my $setCmd = 8;
  2221. my $setpoint = ReadingsVal($name, "setpoint", 125);
  2222. my $setpointScaled = ReadingsVal($name, "setpointScaled", undef);
  2223. my $switch = ReadingsVal($name, "switch", "off");
  2224. $setCmd |= 1 if ($switch eq "on");
  2225. if ($cmd eq "teach") {
  2226. if ($manufID eq "00D") {
  2227. # teach-in EEP A5-10-12, Manufacturer "Eltako"
  2228. $data = "40900D80";
  2229. $attr{$name}{eep} = "A5-10-12";
  2230. } else {
  2231. # teach-in EEP A5-10-10, Manufacturer "Multi user Manufacturer ID"
  2232. $data = "4087FF80";
  2233. $attr{$name}{eep} = "A5-10-10";
  2234. }
  2235. CommandDeleteReading(undef, "$name .*");
  2236. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  2237. $updateState = 0;
  2238. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  2239. } elsif ($cmd eq "setpoint") {
  2240. #
  2241. if (defined $a[1]) {
  2242. if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 255)) {
  2243. $setpoint = $a[1];
  2244. shift(@a);
  2245. if (defined $setpointScaled) {
  2246. $setpointScaled = EnOcean_ReadingScaled($hash, $setpoint, 0, 255);
  2247. }
  2248. } else {
  2249. return "Usage: $a[1] is not numeric or out of range";
  2250. }
  2251. }
  2252. readingsBeginUpdate($hash);
  2253. readingsBulkUpdate($hash, "temperature", $actualTemp);
  2254. readingsBulkUpdate($hash, "humidity", $humidity);
  2255. readingsBulkUpdate($hash, "setpointScaled", $setpointScaled) if (defined $setpointScaled);
  2256. readingsBulkUpdate($hash, "setpoint", $setpoint);
  2257. readingsBulkUpdate($hash, "switch", $switch);
  2258. readingsBulkUpdate($hash, "state", "T: $actualTemp H: $humidity SP: $setpoint SW: $switch");
  2259. readingsEndUpdate($hash, 1);
  2260. $actualTemp = $actualTemp / 40 * 250;
  2261. $humidity = $humidity / 100 * 250;
  2262. $updateState = 0;
  2263. $data = sprintf "%02X%02X%02X%02X", $setpoint, $humidity, $actualTemp, $setCmd;
  2264. } elsif ($cmd eq "setpointScaled") {
  2265. #
  2266. if (defined $a[1]) {
  2267. my $scaleMin = AttrVal($name, "scaleMin", undef);
  2268. my $scaleMax = AttrVal($name, "scaleMax", undef);
  2269. my ($rangeMin, $rangeMax);
  2270. if (defined $scaleMax && defined $scaleMin &&
  2271. $scaleMax =~ m/^[+-]?\d+(\.\d+)?$/ && $scaleMin =~ m/^[+-]?\d+(\.\d+)?$/) {
  2272. if ($scaleMin > $scaleMax) {
  2273. ($rangeMin, $rangeMax)= ($scaleMax, $scaleMin);
  2274. } else {
  2275. ($rangeMin, $rangeMax)= ($scaleMin, $scaleMax);
  2276. }
  2277. } else {
  2278. return "Usage: Attributes scaleMin and/or scaleMax not defined or not numeric.";
  2279. }
  2280. if ($a[1] =~ m/^[+-]?\d+(\.\d+)?$/ && $a[1] >= $rangeMin && $a[1] <= $rangeMax) {
  2281. $setpointScaled = $a[1];
  2282. shift(@a);
  2283. $setpoint = sprintf "%d", 255 * $scaleMin/($scaleMin-$scaleMax) - 255/($scaleMin-$scaleMax) * $setpointScaled;
  2284. } else {
  2285. return "Usage: $a[1] is not numeric or out of range";
  2286. }
  2287. }
  2288. readingsBeginUpdate($hash);
  2289. readingsBulkUpdate($hash, "temperature", $actualTemp);
  2290. readingsBulkUpdate($hash, "humidity", $humidity);
  2291. readingsBulkUpdate($hash, "setpointScaled", $setpointScaled) if (defined $setpointScaled);
  2292. readingsBulkUpdate($hash, "setpoint", $setpoint);
  2293. readingsBulkUpdate($hash, "switch", $switch);
  2294. readingsBulkUpdate($hash, "state", "T: $actualTemp H: $humidity SP: $setpoint SW: $switch");
  2295. readingsEndUpdate($hash, 1);
  2296. $actualTemp = $actualTemp / 40 * 250;
  2297. $humidity = $humidity / 100 * 250;
  2298. $updateState = 0;
  2299. $data = sprintf "%02X%02X%02X%02X", $setpoint, $humidity, $actualTemp, $setCmd;
  2300. } elsif ($cmd eq "switch") {
  2301. #
  2302. if (defined $a[1]) {
  2303. if ($a[1] eq "on") {
  2304. $switch = $a[1];
  2305. $setCmd |= 1;
  2306. shift(@a);
  2307. } elsif ($a[1] eq "off"){
  2308. $switch = $a[1];
  2309. shift(@a);
  2310. } else {
  2311. return "Usage: $a[1] is unknown";
  2312. }
  2313. }
  2314. readingsBeginUpdate($hash);
  2315. readingsBulkUpdate($hash, "temperature", $actualTemp);
  2316. readingsBulkUpdate($hash, "humidity", $humidity);
  2317. readingsBulkUpdate($hash, "setpointScaled", $setpointScaled) if (defined $setpointScaled);
  2318. readingsBulkUpdate($hash, "setpoint", $setpoint);
  2319. readingsBulkUpdate($hash, "switch", $switch);
  2320. readingsBulkUpdate($hash, "state", "T: $actualTemp H: $humidity SP: $setpoint SW: $switch");
  2321. readingsEndUpdate($hash, 1);
  2322. $actualTemp = $actualTemp / 40 * 250;
  2323. $humidity = $humidity / 100 * 250;
  2324. $updateState = 0;
  2325. $data = sprintf "%02X%02X%02X%02X", $setpoint, $humidity, $actualTemp, $setCmd;
  2326. } else {
  2327. return "Unknown argument " . $cmd . ", choose one of " . $cmdList . " setpoint:slider,0,1,255 setpointScaled switch:on,off teach:noArg"
  2328. }
  2329. Log3 $name, 3, "EnOcean set $name $cmd";
  2330. } elsif ($st eq "roomSensorControl.05") {
  2331. # Room Sensor and Control Unit (EEP A5-10-01 ... A5-10-0D)
  2332. # [Eltako FTR55D, FTR55H, Thermokon SR04 *, Thanos SR *, untested]
  2333. # $db[3] is the fan speed or night reduction for Eltako
  2334. # $db[2] is the setpoint where 0x00 = min ... 0xFF = max or
  2335. # reference temperature for Eltako where 0x00 = 0°C ... 0xFF = 40°C
  2336. # $db[1] is the temperature where 0x00 = +40°C ... 0xFF = 0°C
  2337. # $db[1]_bit_1 is blocking the aditional Room Sensor and Control Unit for Eltako FVS
  2338. # $db[0]_bit_0 is the slide switch
  2339. $rorg = "A5";
  2340. # primarily temperature from the reference device then the attribute actualTemp is read
  2341. my $temperatureRefDev = AttrVal($name, "temperatureRefDev", undef);
  2342. my $actualTemp = AttrVal($name, "actualTemp", 20);
  2343. $actualTemp = ReadingsVal($temperatureRefDev, "temperature", 20) if (defined $temperatureRefDev);
  2344. $actualTemp = 20 if ($actualTemp !~ m/^[+-]?\d+(\.\d+)?$/);
  2345. $actualTemp = 0 if ($actualTemp < 0);
  2346. $actualTemp = 40 if ($actualTemp > 40);
  2347. $actualTemp = sprintf "%0.1f", $actualTemp;
  2348. my $setCmd = 8;
  2349. if ($manufID eq "00D") {
  2350. # EEP A5-10-06 plus DB3 [Eltako FVS]
  2351. my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
  2352. my $nightReduction = ReadingsVal($name, "nightReduction", 0);
  2353. my $block = ReadingsVal($name, "block", "unlock");
  2354. if ($cmd eq "teach") {
  2355. # teach-in EEP A5-10-06 plus "FVS", Manufacturer "Eltako"
  2356. $data = "40300D85";
  2357. $attr{$name}{eep} = "A5-10-06";
  2358. CommandDeleteReading(undef, "$name .*");
  2359. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  2360. $updateState = 0;
  2361. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  2362. } elsif ($cmd eq "desired-temp" || $cmd eq "setpointTemp") {
  2363. #
  2364. if (defined $a[1]) {
  2365. if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 40)) {
  2366. $setpointTemp = sprintf "%0.1f", $a[1];
  2367. shift(@a);
  2368. } else {
  2369. return "Usage: $a[1] is not numeric or out of range";
  2370. }
  2371. }
  2372. if (defined $a[1]) {
  2373. if (($a[1] =~ m/^(lock|unlock)$/) ) {
  2374. $block = $a[1];
  2375. shift(@a);
  2376. } else {
  2377. return "Usage: $a[1] is unknown";
  2378. }
  2379. }
  2380. readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
  2381. readingsSingleUpdate($hash, "setpointTemp", $setpointTemp, 1);
  2382. readingsSingleUpdate($hash, "nightReduction", $nightReduction, 1);
  2383. readingsSingleUpdate($hash, "block", $block, 1);
  2384. readingsSingleUpdate($hash, "state", "T: $actualTemp SPT: $setpointTemp NR: $nightReduction", 1);
  2385. if ($nightReduction == 5) {
  2386. $nightReduction = 31;
  2387. } elsif ($nightReduction == 4) {
  2388. $nightReduction = 25;
  2389. } elsif ($nightReduction == 3) {
  2390. $nightReduction = 19;
  2391. } elsif ($nightReduction == 2) {
  2392. $nightReduction = 12;
  2393. } elsif ($nightReduction == 1) {
  2394. $nightReduction = 6;
  2395. } else {
  2396. $nightReduction = 0;
  2397. }
  2398. $actualTemp = (40 - $actualTemp) / 40 * 255;
  2399. $setpointTemp = $setpointTemp * 255 / 40;
  2400. # control of the aditional Room Sensor and Control Unit
  2401. if ($block eq "lock") {
  2402. # temperature setting is locked
  2403. $setCmd = 0x0D;
  2404. } else {
  2405. # setpointTemp may be subject to change at +/-3 K
  2406. $setCmd = 0x0F;
  2407. }
  2408. $updateState = 0;
  2409. $data = sprintf "%02X%02X%02X%02X", $nightReduction, $setpointTemp, $actualTemp, $setCmd;
  2410. } elsif ($cmd eq "nightReduction") {
  2411. #
  2412. if (defined $a[1]) {
  2413. if ($a[1] =~ m/^[0-5]$/) {
  2414. $nightReduction = $a[1];
  2415. shift(@a);
  2416. } else {
  2417. return "Usage: $a[1] is not numeric or out of range";
  2418. }
  2419. }
  2420. if (defined $a[1]) {
  2421. if (($a[1] =~ m/^(lock|unlock)$/) ) {
  2422. $block = $a[1];
  2423. shift(@a);
  2424. } else {
  2425. return "Usage: $a[1] is unknown";
  2426. }
  2427. }
  2428. readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
  2429. readingsSingleUpdate($hash, "setpointTemp", $setpointTemp, 1);
  2430. readingsSingleUpdate($hash, "nightReduction", $nightReduction, 1);
  2431. readingsSingleUpdate($hash, "block", $block, 1);
  2432. readingsSingleUpdate($hash, "state", "T: $actualTemp SPT: $setpointTemp NR: $nightReduction", 1);
  2433. if ($nightReduction == 5) {
  2434. $nightReduction = 31;
  2435. } elsif ($nightReduction == 4) {
  2436. $nightReduction = 25;
  2437. } elsif ($nightReduction == 3) {
  2438. $nightReduction = 19;
  2439. } elsif ($nightReduction == 2) {
  2440. $nightReduction = 12;
  2441. } elsif ($nightReduction == 1) {
  2442. $nightReduction = 6;
  2443. } else {
  2444. $nightReduction = 0;
  2445. }
  2446. $actualTemp = (40 - $actualTemp) / 40 * 254;
  2447. $setpointTemp = $setpointTemp * 254 / 40;
  2448. # control of the aditional Room Sensor and Control Unit
  2449. if ($block eq "lock") {
  2450. # temperature setting is locked
  2451. $setCmd = 0x0D;
  2452. } else {
  2453. # setpointTemp may be subject to change at +/-3 K
  2454. $setCmd = 0x0F;
  2455. }
  2456. $updateState = 0;
  2457. $data = sprintf "%02X%02X%02X%02X", $nightReduction, $setpointTemp, $actualTemp, $setCmd;
  2458. } else {
  2459. return "Unknown argument " . $cmd . ", choose one of setpointTemp:slider,0,1,40 desired-temp nightReduction:0,1,2,3,4,5 teach:noArg"
  2460. }
  2461. } else {
  2462. # EEP A5-10-02 or EEP A5-10-03
  2463. my $setpoint = ReadingsVal($name, "setpoint", 128);
  2464. my $setpointScaled = ReadingsVal($name, "setpointScaled", undef);
  2465. my $fanStage = ReadingsVal($name, "fanStage", "auto");
  2466. my $switch = ReadingsVal($name, "switch", "off");
  2467. $setCmd |= 1 if ($switch eq "on");
  2468. if ($cmd eq "teach") {
  2469. if ($manufID eq "019") {
  2470. # teach-in EEP A5-10-03, Manufacturer "Multi user Manufacturer ID"
  2471. $data = "401FFF80";
  2472. $attr{$name}{eep} = "A5-10-03";
  2473. } else {
  2474. # teach-in EEP A5-10-02, Manufacturer "Multi user Manufacturer ID"
  2475. $data = "4017FF80";
  2476. $attr{$name}{eep} = "A5-10-02";
  2477. }
  2478. CommandDeleteReading(undef, "$name .*");
  2479. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  2480. $updateState = 0;
  2481. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  2482. } elsif ($cmd eq "fanStage") {
  2483. #
  2484. if (defined $a[1] && ($a[1] =~ m/^[0-3]$/ || $a[1] eq "auto")) {
  2485. $fanStage = $a[1];
  2486. shift(@a);
  2487. readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
  2488. readingsSingleUpdate($hash, "setpointScaled", $setpointScaled, 1) if (defined $setpointScaled);
  2489. readingsSingleUpdate($hash, "setpoint", $setpoint, 1);
  2490. readingsSingleUpdate($hash, "fanStage", $fanStage, 1);
  2491. readingsSingleUpdate($hash, "switch", $switch, 1);
  2492. readingsSingleUpdate($hash, "state", "T: $actualTemp SP: $setpoint F: $fanStage SW: $switch", 1);
  2493. if ($fanStage eq "auto"){
  2494. $fanStage = 255;
  2495. } elsif ($fanStage == 0) {
  2496. $fanStage = 209;
  2497. } elsif ($fanStage == 1) {
  2498. $fanStage = 189;
  2499. } elsif ($fanStage == 2) {
  2500. $fanStage = 164;
  2501. } else {
  2502. $fanStage = 144;
  2503. }
  2504. } else {
  2505. return "Usage: $a[1] is not numeric, out of range or unknown";
  2506. }
  2507. $actualTemp = (40 - $actualTemp) / 40 * 254;
  2508. $updateState = 0;
  2509. $data = sprintf "%02X%02X%02X%02X", $fanStage, $setpoint, $actualTemp, $setCmd;
  2510. } elsif ($cmd eq "setpoint") {
  2511. #
  2512. if (defined $a[1]) {
  2513. if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 255)) {
  2514. $setpoint = $a[1];
  2515. shift(@a);
  2516. if (defined $setpointScaled) {
  2517. $setpointScaled = EnOcean_ReadingScaled($hash, $setpoint, 0, 255);
  2518. }
  2519. } else {
  2520. return "Usage: $a[1] is not numeric or out of range";
  2521. }
  2522. }
  2523. readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
  2524. readingsSingleUpdate($hash, "setpointScaled", $setpointScaled, 1) if (defined $setpointScaled);
  2525. readingsSingleUpdate($hash, "setpoint", $setpoint, 1);
  2526. readingsSingleUpdate($hash, "fanStage", $fanStage, 1);
  2527. readingsSingleUpdate($hash, "switch", $switch, 1);
  2528. readingsSingleUpdate($hash, "state", "T: $actualTemp SP: $setpoint F: $fanStage SW: $switch", 1);
  2529. if ($fanStage eq "auto"){
  2530. $fanStage = 255;
  2531. } elsif ($fanStage == 0) {
  2532. $fanStage = 209;
  2533. } elsif ($fanStage == 1) {
  2534. $fanStage = 189;
  2535. } elsif ($fanStage == 2) {
  2536. $fanStage = 164;
  2537. } else {
  2538. $fanStage = 144;
  2539. }
  2540. $actualTemp = (40 - $actualTemp) / 40 * 255;
  2541. $updateState = 0;
  2542. $data = sprintf "%02X%02X%02X%02X", $fanStage, $setpoint, $actualTemp, $setCmd;
  2543. } elsif ($cmd eq "setpointScaled") {
  2544. #
  2545. if (defined $a[1]) {
  2546. my $scaleMin = AttrVal($name, "scaleMin", undef);
  2547. my $scaleMax = AttrVal($name, "scaleMax", undef);
  2548. my ($rangeMin, $rangeMax);
  2549. if (defined $scaleMax && defined $scaleMin &&
  2550. $scaleMax =~ m/^[+-]?\d+(\.\d+)?$/ && $scaleMin =~ m/^[+-]?\d+(\.\d+)?$/) {
  2551. if ($scaleMin > $scaleMax) {
  2552. ($rangeMin, $rangeMax)= ($scaleMax, $scaleMin);
  2553. } else {
  2554. ($rangeMin, $rangeMax)= ($scaleMin, $scaleMax);
  2555. }
  2556. } else {
  2557. return "Usage: Attributes scaleMin and/or scaleMax not defined or not numeric.";
  2558. }
  2559. if ($a[1] =~ m/^[+-]?\d+(\.\d+)?$/ && $a[1] >= $rangeMin && $a[1] <= $rangeMax) {
  2560. $setpointScaled = $a[1];
  2561. shift(@a);
  2562. $setpoint = sprintf "%d", 255 * $scaleMin/($scaleMin-$scaleMax) - 255/($scaleMin-$scaleMax) * $setpointScaled;
  2563. } else {
  2564. return "Usage: $a[1] is not numeric or out of range";
  2565. }
  2566. }
  2567. readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
  2568. readingsSingleUpdate($hash, "setpointScaled", $setpointScaled, 1);
  2569. readingsSingleUpdate($hash, "setpoint", $setpoint, 1);
  2570. readingsSingleUpdate($hash, "fanStage", $fanStage, 1);
  2571. readingsSingleUpdate($hash, "switch", $switch, 1);
  2572. readingsSingleUpdate($hash, "state", "T: $actualTemp SP: $setpoint F: $fanStage SW: $switch", 1);
  2573. if ($fanStage eq "auto"){
  2574. $fanStage = 255;
  2575. } elsif ($fanStage == 0) {
  2576. $fanStage = 209;
  2577. } elsif ($fanStage == 1) {
  2578. $fanStage = 189;
  2579. } elsif ($fanStage == 2) {
  2580. $fanStage = 164;
  2581. } else {
  2582. $fanStage = 144;
  2583. }
  2584. $actualTemp = (40 - $actualTemp) / 40 * 255;
  2585. $updateState = 0;
  2586. $data = sprintf "%02X%02X%02X%02X", $fanStage, $setpoint, $actualTemp, $setCmd;
  2587. } elsif ($cmd eq "switch") {
  2588. #
  2589. if (defined $a[1]) {
  2590. if ($a[1] eq "on") {
  2591. $switch = $a[1];
  2592. $setCmd |= 1;
  2593. shift(@a);
  2594. } elsif ($a[1] eq "off"){
  2595. $switch = $a[1];
  2596. shift(@a);
  2597. } else {
  2598. return "Usage: $a[1] is unknown";
  2599. }
  2600. }
  2601. readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
  2602. readingsSingleUpdate($hash, "setpointScaled", $setpointScaled, 1) if (defined $setpointScaled);
  2603. readingsSingleUpdate($hash, "setpoint", $setpoint, 1);
  2604. readingsSingleUpdate($hash, "fanStage", $fanStage, 1);
  2605. readingsSingleUpdate($hash, "switch", $switch, 1);
  2606. readingsSingleUpdate($hash, "state", "T: $actualTemp SP: $setpoint F: $fanStage SW: $switch", 1);
  2607. if ($fanStage eq "auto"){
  2608. $fanStage = 255;
  2609. } elsif ($fanStage == 0) {
  2610. $fanStage = 209;
  2611. } elsif ($fanStage == 1) {
  2612. $fanStage = 189;
  2613. } elsif ($fanStage == 2) {
  2614. $fanStage = 164;
  2615. } else {
  2616. $fanStage = 144;
  2617. }
  2618. $actualTemp = (40 - $actualTemp) / 40 * 255;
  2619. $updateState = 0;
  2620. $data = sprintf "%02X%02X%02X%02X", $fanStage, $setpoint, $actualTemp, $setCmd;
  2621. } else {
  2622. return "Unknown argument " . $cmd . ", choose one of " . $cmdList . "setpoint:slider,0,1,255 fanStage:auto,0,1,2,3 setpointScaled switch:on,off teach:noArg"
  2623. }
  2624. }
  2625. Log3 $name, 3, "EnOcean set $name $cmd";
  2626. } elsif ($st eq "hvac.01" || $st eq "MD15") {
  2627. # Battery Powered Actuator (EEP A5-20-01)
  2628. # [Kieback&Peter MD15-FTL-xx]
  2629. $rorg = "A5";
  2630. my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
  2631. my $temperature = ReadingsVal($name, "temperature", 20);
  2632. if ($cmd eq "setpoint") {
  2633. if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
  2634. readingsBeginUpdate($hash);
  2635. readingsBulkUpdate($hash, "setpointSet", $a[1]);
  2636. readingsBulkUpdate($hash, "waitingCmds", $cmd);
  2637. readingsEndUpdate($hash, 0);
  2638. # stop PID regulator
  2639. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  2640. CommandDeleteReading(undef, "$name setpointTempSet");
  2641. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  2642. shift(@a);
  2643. } else {
  2644. return "Usage: $cmd value wrong.";
  2645. }
  2646. $updateState = 2;
  2647. } elsif ($cmd =~ m/^desired-temp|setpointTemp$/) {
  2648. if (defined $a[1] && $a[1] =~ m/^\d+(\.\d)?$/ && $a[1] >= 10 && $a[1] <= 30) {
  2649. $cmd = "setpointTemp";
  2650. $setpointTemp = $a[1];
  2651. readingsBeginUpdate($hash);
  2652. readingsBulkUpdate($hash, "setpointTempSet", sprintf("%0.1f", $setpointTemp));
  2653. readingsBulkUpdate($hash, "waitingCmds", "setpointTemp");
  2654. readingsEndUpdate($hash, 0);
  2655. # PID regulator active
  2656. my $activatePID = AttrVal($name, 'pidCtrl', 'off') eq 'on' ? 'start' : 'stop';
  2657. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, $activatePID, '');
  2658. CommandDeleteReading(undef, "$name setpointSet");
  2659. Log3 $name, 3, "EnOcean set $name $cmd $setpointTemp";
  2660. shift(@a);
  2661. } else {
  2662. return "Usage: $cmd value wrong.";
  2663. }
  2664. $updateState = 2;
  2665. } elsif ($cmd =~ m/^liftSet|runInit|valveOpens|valveCloses$/) {
  2666. # unattended?
  2667. readingsBeginUpdate($hash);
  2668. readingsBulkUpdate($hash, "waitingCmds", $cmd);
  2669. readingsEndUpdate($hash, 0);
  2670. # stop PID regulator
  2671. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  2672. CommandDeleteReading(undef, "$name setpointSet");
  2673. CommandDeleteReading(undef, "$name setpointTempSet");
  2674. Log3 $name, 3, "EnOcean set $name $cmd";
  2675. $updateState = 2;
  2676. } else {
  2677. $cmdList .= "setpointTemp:slider,0,1,40 " if (AttrVal($name, "pidCtrl", 'on') eq 'on' || AttrVal($name, "manufID", '7FF') ne '049');
  2678. $cmdList .= "setpoint:slider,0,5,100 desired-temp liftSet:noArg runInit:noArg valveOpens:noArg valveCloses:noArg";
  2679. return "Unknown command " . $cmd . ", choose one of " . $cmdList;
  2680. }
  2681. } elsif ($st eq "hvac.04") {
  2682. # heating radiator valve actuating drive (EEP A5-20-04)
  2683. $rorg = "A5";
  2684. my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
  2685. my $temperature = ReadingsVal($name, "temperature", 20);
  2686. if ($cmd eq "setpoint") {
  2687. if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
  2688. readingsBeginUpdate($hash);
  2689. readingsBulkUpdate($hash, "setpointSet", $a[1]);
  2690. readingsBulkUpdate($hash, "waitingCmds", $cmd);
  2691. readingsEndUpdate($hash, 0);
  2692. # stop PID regulator
  2693. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  2694. CommandDeleteReading(undef, "$name setpointTempSet");
  2695. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  2696. shift(@a);
  2697. } else {
  2698. return "Usage: $cmd value wrong.";
  2699. }
  2700. $updateState = 2;
  2701. } elsif ($cmd =~ m/^desired-temp|setpointTemp$/) {
  2702. if (defined $a[1] && $a[1] =~ m/^\d+(\.\d)?$/ && $a[1] >= 10 && $a[1] <= 30) {
  2703. $cmd = "setpointTemp";
  2704. $setpointTemp = $a[1];
  2705. readingsBeginUpdate($hash);
  2706. readingsBulkUpdate($hash, "setpointTempSet", sprintf("%0.1f", $setpointTemp));
  2707. readingsBulkUpdate($hash, "waitingCmds", "setpointTemp");
  2708. readingsEndUpdate($hash, 0);
  2709. # PID regulator active
  2710. #####
  2711. my $activatePID = AttrVal($name, 'pidCtrl', 'on') eq 'on' ? 'start' : 'stop';
  2712. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, $activatePID, '');
  2713. CommandDeleteReading(undef, "$name setpointSet");
  2714. Log3 $name, 3, "EnOcean set $name $cmd $setpointTemp";
  2715. shift(@a);
  2716. } else {
  2717. return "Usage: $cmd value wrong.";
  2718. }
  2719. $updateState = 2;
  2720. } elsif ($cmd =~ m/^runInit|valveOpens|valveCloses$/) {
  2721. readingsBeginUpdate($hash);
  2722. readingsBulkUpdate($hash, "waitingCmds", $cmd);
  2723. readingsEndUpdate($hash, 0);
  2724. # stop PID regulator
  2725. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  2726. CommandDeleteReading(undef, "$name setpointSet");
  2727. CommandDeleteReading(undef, "$name setpointTempSet");
  2728. Log3 $name, 3, "EnOcean set $name $cmd";
  2729. $updateState = 2;
  2730. } else {
  2731. $cmdList .= "setpointTemp:slider,10,1,30 " if (AttrVal($name, "pidCtrl", 'on') eq 'on' || AttrVal($name, "model", '') eq 'Holter_OEM');
  2732. $cmdList .= "setpoint:slider,0,5,100 " if (AttrVal($name, "pidCtrl", 'on') eq 'off' && AttrVal($name, "model", '') ne 'Holter_OEM');
  2733. $cmdList .= "runInit:noArg valveCloses:noArg valveOpens:noArg";
  2734. return "Unknown command " . $cmd . ", choose one of " . $cmdList;
  2735. }
  2736. } elsif ($st eq "hvac.10") {
  2737. # Generic HVAC Interface (EEP A5-20-10)
  2738. $rorg = "A5";
  2739. my %ctrlFunc = (
  2740. "off" => 1,
  2741. "on" => 2,
  2742. "occupancy" => 3,
  2743. "ctrl" => 4,
  2744. "fanSpeed" => 5,
  2745. "vanePosition" => 6,
  2746. "mode" => 7,
  2747. "teach" => 255,
  2748. );
  2749. my %mode = (
  2750. "auto" => 0,
  2751. "heat" => 1,
  2752. "morning_warmup" => 2,
  2753. "cool" => 3,
  2754. "night_purge" => 4,
  2755. "precool" => 5,
  2756. "off" => 6,
  2757. "test" => 7,
  2758. "emergency_heat" => 8,
  2759. "fan_only" => 9,
  2760. "free_cool" => 10,
  2761. "ice" => 11,
  2762. "max_heat" => 12,
  2763. "eco" => 13,
  2764. "dehumidification" => 14,
  2765. "calibration" => 15,
  2766. "emergency_cool" => 16,
  2767. "emergency_stream" => 17,
  2768. "max_cool" => 18,
  2769. "hvc_load" => 19,
  2770. "no_load" => 20,
  2771. "auto_heat" => 31,
  2772. "auto_cool" => 32,
  2773. );
  2774. my %vanePosition = (
  2775. "auto" => 0,
  2776. "horizontal" => 1,
  2777. "position_2" => 2,
  2778. "position_3" => 3,
  2779. "position_4" => 4,
  2780. "vertical" => 5,
  2781. "swing" => 6,
  2782. "vertical_swing" => 11,
  2783. "horizontal_swing" => 12,
  2784. "hor_vert_swing" => 13,
  2785. "stop_swing" => 14,
  2786. );
  2787. my $ctrlFuncID;
  2788. if (defined $ctrlFunc{$cmd}) {
  2789. $ctrlFuncID = $ctrlFunc{$cmd};
  2790. } else {
  2791. $cmdList .= "ctrl mode:" . join(",", sort keys %mode) . " fanSpeed:auto,1,2,3,4,5,6,7,8,9,10,11,12,13,14 " .
  2792. "occupancy:occupied,off,standby,unoccupied on:noArg off:noArg teach:noArg vanePosition:" .
  2793. join(",", sort keys %vanePosition);
  2794. return SetExtensions ($hash, $cmdList, $name, @a);
  2795. }
  2796. $ctrl = ReadingsVal($name, "ctrl", "auto");
  2797. my $fanSpeed = ReadingsVal($name, "fanSpeed", "auto");
  2798. my $mode = ReadingsVal($name, "mode", "off");
  2799. my $occupancy = ReadingsVal($name, "occupancy", "off");
  2800. my $powerSwitch = ReadingsVal($name, "powerSwitch", "off");
  2801. my $vanePosition = ReadingsVal($name, "vanePosition", "auto");
  2802. my ($ctrlParam1, $ctrlParam2, $ctrlParam3, $setCmd) = (0, 0, 0, 8);
  2803. if($ctrlFuncID == 255) {
  2804. # teach-in EEP A5-20-10, Manufacturer "Multi user Manufacturer ID"
  2805. $ctrlParam1 = 0x80;
  2806. $ctrlParam2 = 0x87;
  2807. $ctrlParam3 = 0xFF;
  2808. $setCmd = 0x80;
  2809. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
  2810. EnOcean_4BSRespWait(undef, $hash, $subDef);
  2811. readingsSingleUpdate($hash, "teach", "4BS teach-in sent, response requested", 1);
  2812. $updateState = 0;
  2813. } elsif ($ctrlFuncID == 1) {
  2814. # off
  2815. $powerSwitch = "off";
  2816. $updateState = 0;
  2817. readingsBeginUpdate($hash);
  2818. readingsBulkUpdate($hash, "powerSwitch", "off");
  2819. readingsBulkUpdate($hash, "state", "off");
  2820. readingsEndUpdate($hash, 0);
  2821. #####
  2822. SetExtensionsCancel($hash);
  2823. } elsif ($ctrlFuncID == 2) {
  2824. # on
  2825. $powerSwitch = "on";
  2826. $updateState = 0;
  2827. readingsBeginUpdate($hash);
  2828. readingsBulkUpdate($hash, "powerSwitch", "on");
  2829. readingsBulkUpdate($hash, "state", "off");
  2830. readingsEndUpdate($hash, 0);
  2831. #####
  2832. SetExtensionsCancel($hash);
  2833. } elsif ($ctrlFuncID == 3) {
  2834. # occupancy
  2835. if (defined $a[1] && $a[1] =~ m/^occupied|standby|unoccupied|off$/) {
  2836. $occupancy = $a[1];
  2837. readingsSingleUpdate($hash, "occupancy", $a[1], 0);
  2838. shift(@a);
  2839. } else {
  2840. return "Usage: $cmd value wrong.";
  2841. }
  2842. $updateState = 0;
  2843. } elsif ($ctrlFuncID == 4) {
  2844. # ctrl
  2845. if (defined $a[1] && ($a[1] =~ m/^auto$/ || $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 100)) {
  2846. $ctrl = $a[1];
  2847. readingsSingleUpdate($hash, "ctrl", $a[1], 0);
  2848. shift(@a);
  2849. } else {
  2850. return "Usage: $cmd value wrong.";
  2851. }
  2852. $updateState = 0;
  2853. } elsif ($ctrlFuncID == 5) {
  2854. # fanSpeed
  2855. if (defined $a[1] && ($a[1] =~ m/^auto$/ || $a[1] =~ m/^\d+$/ && $a[1] > 0 && $a[1] < 15)) {
  2856. $fanSpeed = $a[1];
  2857. readingsSingleUpdate($hash, "fanSpeed", $a[1], 0);
  2858. shift(@a);
  2859. } else {
  2860. return "Usage: $cmd value wrong.";
  2861. }
  2862. $updateState = 0;
  2863. } elsif ($ctrlFuncID == 6) {
  2864. # vanePosition
  2865. my $vanePositionValues = join("|", keys %vanePosition);
  2866. if (defined $a[1] && $a[1] =~ m/^$vanePositionValues$/) {
  2867. $vanePosition = $a[1];
  2868. readingsSingleUpdate($hash, "vanePosition", $a[1], 0);
  2869. shift(@a);
  2870. } else {
  2871. return "Usage: $cmd value wrong.";
  2872. }
  2873. $updateState = 0;
  2874. } elsif ($ctrlFuncID == 7) {
  2875. # mode
  2876. my $modeValues = join("|", keys %mode);
  2877. if (defined $a[1] && $a[1] =~ m/^$modeValues$/) {
  2878. $mode = $a[1];
  2879. readingsSingleUpdate($hash, "mode", $a[1], 0);
  2880. shift(@a);
  2881. } else {
  2882. return "Usage: $cmd value wrong.";
  2883. }
  2884. $updateState = 0;
  2885. }
  2886. if ($cmd ne "teach") {
  2887. $ctrlParam1 = $mode{$mode};
  2888. $vanePosition = $vanePosition{$vanePosition};
  2889. $fanSpeed = 0 if ($fanSpeed eq "auto");
  2890. $ctrlParam2 = $vanePosition << 4 || $fanSpeed;
  2891. $ctrl = 255 if ($ctrl eq "auto");
  2892. $ctrlParam3 = $ctrl;
  2893. if ($occupancy eq "occupied") {
  2894. $occupancy = 0;
  2895. } elsif ($occupancy eq "standby") {
  2896. $occupancy = 1;
  2897. } elsif ($occupancy eq "unoccupied") {
  2898. $occupancy = 2;
  2899. } else {
  2900. $occupancy = 3;
  2901. }
  2902. $powerSwitch = $powerSwitch eq "on" ? 1 : 0;
  2903. $setCmd |= $occupancy << 1 | $powerSwitch;
  2904. }
  2905. $data = sprintf "%02X%02X%02X%02X", $ctrlParam1, $ctrlParam2, $ctrlParam3, $setCmd;
  2906. Log3 $name, 3, "EnOcean set $name $cmd";
  2907. } elsif ($st eq "hvac.11") {
  2908. # Generic HVAC Interface - Error Control (EEP A5-20-11)
  2909. $rorg = "A5";
  2910. my %ctrlFunc = (
  2911. "externalDisable" => 1,
  2912. "remoteCtrl" => 2,
  2913. "window" => 3,
  2914. "teach" => 255,
  2915. );
  2916. my $ctrlFuncID;
  2917. if (defined $ctrlFunc{$cmd}) {
  2918. $ctrlFuncID = $ctrlFunc{$cmd};
  2919. } else {
  2920. $cmdList .= "externalDisable:disabled,enabled remoteCtrl:disabled,enabled teach:noArg window:closed,opened";
  2921. return "Unknown argument " . $cmd . ", choose one of " . $cmdList;
  2922. }
  2923. my $extern = ReadingsVal($name, "externalDisable", "enable");
  2924. my $remote = ReadingsVal($name, "remoteCtrl", "enable");
  2925. my $window = ReadingsVal($name, "window", "closed");
  2926. my ($ctrlParam1, $ctrlParam2, $ctrlParam3, $setCmd) = (0, 0, 0, 8);
  2927. if($ctrlFuncID == 255) {
  2928. # teach-in EEP A5-20-11, Manufacturer "Multi user Manufacturer ID"
  2929. $ctrlParam1 = 0x80;
  2930. $ctrlParam2 = 0x8F;
  2931. $ctrlParam3 = 0xFF;
  2932. $setCmd = 0x80;
  2933. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
  2934. EnOcean_4BSRespWait(undef, $hash, $subDef);
  2935. readingsSingleUpdate($hash, "teach", "4BS teach-in sent, response requested", 1);
  2936. $updateState = 0;
  2937. } elsif ($ctrlFuncID == 1) {
  2938. # external disablement
  2939. if (defined $a[1] && $a[1] =~ m/^disabled|enabled$/) {
  2940. $extern = $a[1];
  2941. readingsSingleUpdate($hash, "externalDisable", $a[1], 0);
  2942. shift(@a);
  2943. } else {
  2944. return "Usage: $cmd value wrong.";
  2945. }
  2946. $updateState = 0;
  2947. } elsif ($ctrlFuncID == 2) {
  2948. # disable remote controller
  2949. if (defined $a[1] && $a[1] =~ m/^disabled|enabled$/) {
  2950. $remote = $a[1];
  2951. readingsSingleUpdate($hash, "remoteCtrl", $a[1], 0);
  2952. shift(@a);
  2953. } else {
  2954. return "Usage: $cmd value wrong.";
  2955. }
  2956. $updateState = 0;
  2957. } elsif ($ctrlFuncID == 3) {
  2958. # window contact
  2959. if (defined $a[1] && $a[1] =~ m/^closed|opened$/) {
  2960. $window = $a[1];
  2961. readingsSingleUpdate($hash, "window", $a[1], 0);
  2962. shift(@a);
  2963. } else {
  2964. return "Usage: $cmd value wrong.";
  2965. }
  2966. $updateState = 0;
  2967. }
  2968. if ($cmd ne "teach") {
  2969. $ctrlParam3 = $extern eq "disabled" ? 1 : 0;
  2970. $setCmd |= ($remote eq "disabled" ? 1 : 0) << 2 | ($window eq "closed" ? 1 : 0) << 1;
  2971. }
  2972. $data = sprintf "%02X%02X%02X%02X", $ctrlParam1, $ctrlParam2, $ctrlParam3, $setCmd;
  2973. Log3 $name, 3, "EnOcean set $name $cmd";
  2974. } elsif ($st eq "gateway") {
  2975. # Gateway (EEP A5-38-08)
  2976. # select Command from attribute gwCmd or command line
  2977. my $gwCmd = AttrVal($name, "gwCmd", undef);
  2978. if ($gwCmd && $EnO_gwCmd{$gwCmd}) {
  2979. # command from attribute gwCmd
  2980. if ($EnO_gwCmd{$cmd}) {
  2981. # shift $cmd
  2982. $cmd = $a[1];
  2983. shift(@a);
  2984. }
  2985. } elsif ($EnO_gwCmd{$cmd}) {
  2986. # command from command line
  2987. $gwCmd = $cmd;
  2988. $cmd = $a[1];
  2989. shift(@a);
  2990. } else {
  2991. return "Unknown Gateway command " . $cmd . ", choose one of " . $cmdList . join(" ", sort keys %EnO_gwCmd);
  2992. }
  2993. my $gwCmdID;
  2994. $rorg = "A5";
  2995. my $setCmd = 0;
  2996. my $time = 0;
  2997. if ($gwCmd eq "switching") {
  2998. # Switching
  2999. $gwCmdID = 1;
  3000. if($cmd eq "teach") {
  3001. # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
  3002. #$data = sprintf "%02X000000", $gwCmdID;
  3003. if ($model =~ m/TF$/) {
  3004. $data = "E0400D80";
  3005. } else {
  3006. $data = "E047FF80";
  3007. }
  3008. $attr{$name}{eep} = "A5-38-08";
  3009. CommandDeleteReading(undef, "$name .*");
  3010. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  3011. $updateState = 0;
  3012. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  3013. EnOcean_setTeachConfirmWaitHash(undef, $hash);
  3014. } elsif ($cmd eq "on") {
  3015. $setCmd = 9;
  3016. readingsSingleUpdate($hash, "block", "unlock", 1);
  3017. if ($a[1]) {
  3018. return "Usage: $cmd [lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock"));
  3019. if ($a[1] eq "lock") {
  3020. $setCmd = $setCmd | 4;
  3021. readingsSingleUpdate($hash, "block", "lock", 1);
  3022. }
  3023. shift(@a);
  3024. }
  3025. #$updateState = 0;
  3026. #####
  3027. SetExtensionsCancel($hash);
  3028. $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd;
  3029. } elsif ($cmd eq "off") {
  3030. if ($model =~ m/FSA12$/) {
  3031. $setCmd = 0x0E;
  3032. } else {
  3033. $setCmd = 8;
  3034. }
  3035. readingsSingleUpdate($hash, "block", "unlock", 1);
  3036. if ($a[1]) {
  3037. return "Usage: $cmd [lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock"));
  3038. if ($a[1] eq "lock") {
  3039. $setCmd = $setCmd | 4;
  3040. readingsSingleUpdate($hash, "block", "lock", 1);
  3041. }
  3042. shift(@a);
  3043. }
  3044. #$updateState = 0;
  3045. #####
  3046. SetExtensionsCancel($hash);
  3047. $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd;
  3048. } elsif ($cmd eq "local") {
  3049. if ($a[1]) {
  3050. return "Usage: $cmd [learn]" if ($a[1] ne "learn");
  3051. if ($a[1] eq "learn") {
  3052. $cmd = 'off';
  3053. $setCmd = $setCmd | 0x28;
  3054. readingsSingleUpdate($hash, "block", "unlock", 1);
  3055. }
  3056. shift(@a);
  3057. }
  3058. #$updateState = 0;
  3059. $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd;
  3060. } else {
  3061. my $cmdList = "local:learn on:noArg off:noArg teach:noArg";
  3062. return SetExtensions ($hash, $cmdList, $name, @a);
  3063. }
  3064. } elsif ($gwCmd eq "dimming") {
  3065. # Dimming
  3066. $gwCmdID = 2;
  3067. my $dimMax = AttrVal($name, "dimMax", 255);
  3068. my $dimMin = AttrVal($name, "dimMin", "off");
  3069. if ($dimMax =~ m/^\d+$/ && $dimMin =~ m/^\d+$/ && $dimMin > $dimMax) {
  3070. ($dimMax, $dimMin) = ($dimMin , $dimMax);
  3071. }
  3072. my $dimVal = ReadingsVal($name, "dim", undef);
  3073. my $rampTime = AttrVal($name, "rampTime", 1);
  3074. my $sendDimCmd = 0;
  3075. $setCmd = 9;
  3076. if ($cmd =~ m/^\d+$/) {
  3077. # interpretive numeric value as dimming
  3078. unshift(@a, 'dim');
  3079. $cmd = 'dim';
  3080. }
  3081. if ($cmd eq "teach") {
  3082. # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
  3083. #$data = "E047FF80";
  3084. # teach-in Eltako
  3085. if ($model =~ m/TF$/) {
  3086. $data = "E0400D80";
  3087. } else {
  3088. $data = "02000000";
  3089. }
  3090. $attr{$name}{eep} = "A5-38-08";
  3091. CommandDeleteReading(undef, "$name .*");
  3092. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  3093. $updateState = 0;
  3094. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  3095. EnOcean_setTeachConfirmWaitHash(undef, $hash);
  3096. } elsif ($cmd eq "dim") {
  3097. return "Usage: $cmd dim/% [rampTime/s lock|unlock]"
  3098. if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 100);
  3099. # for eltako relative (0-100) (but not compliant to EEP because DB0.2 is 0)
  3100. # >> if manufID needed: set DB2.0
  3101. $dimVal = $a[1];
  3102. if ($dimVal > 0) {
  3103. readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
  3104. }
  3105. shift(@a);
  3106. if (defined($a[1])) {
  3107. return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^\d+$/);
  3108. $rampTime = $a[1];
  3109. shift(@a);
  3110. }
  3111. $sendDimCmd = 1;
  3112. } elsif ($cmd eq "dimup") {
  3113. return "Usage: $cmd dim/% [rampTime/s lock|unlock]"
  3114. if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 100);
  3115. $dimVal += $a[1];
  3116. if ($dimVal > 0) {
  3117. readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
  3118. }
  3119. shift(@a);
  3120. if (defined($a[1])) {
  3121. return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^\d+$/);
  3122. $rampTime = $a[1];
  3123. shift(@a);
  3124. }
  3125. $sendDimCmd = 1;
  3126. } elsif ($cmd eq "dimdown") {
  3127. return "Usage: $cmd dim/% [rampTime/s lock|unlock]"
  3128. if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 100);
  3129. $dimVal -= $a[1];
  3130. if ($dimVal > 0) {
  3131. readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
  3132. }
  3133. shift(@a);
  3134. if (defined($a[1])) {
  3135. return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^\d+$/);
  3136. $rampTime = $a[1];
  3137. shift(@a);
  3138. }
  3139. $sendDimCmd = 1;
  3140. } elsif ($cmd eq "on") {
  3141. $rampTime = 1;
  3142. my $dimValueOn = AttrVal($name, "dimValueOn", 100);
  3143. if ($dimValueOn eq "stored") {
  3144. $dimVal = ReadingsVal($name, "dimValueStored", 100);
  3145. if ($dimVal < 1) {
  3146. $dimVal = 100;
  3147. readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
  3148. }
  3149. } elsif ($dimValueOn eq "last") {
  3150. $dimVal = ReadingsVal ($name, "dimValueLast", 100);
  3151. if ($dimVal < 1) { $dimVal = 100; }
  3152. } else {
  3153. if ($dimValueOn !~ m/^\d+$/) {
  3154. $dimVal = 100;
  3155. } elsif ($dimValueOn > 100) {
  3156. $dimVal = 100;
  3157. } elsif ($dimValueOn < 1) {
  3158. $dimVal = 1;
  3159. } else {
  3160. $dimVal = $dimValueOn;
  3161. }
  3162. }
  3163. $sendDimCmd = 1;
  3164. } elsif ($cmd eq "off") {
  3165. $dimVal = 0;
  3166. $rampTime = 1;
  3167. $setCmd = 8;
  3168. $sendDimCmd = 1;
  3169. } elsif ($cmd eq "local") {
  3170. if ($a[1]) {
  3171. return "Usage: $cmd [learn]" if ($a[1] ne "learn");
  3172. if ($a[1] eq "learn") {
  3173. $cmd = 'off';
  3174. $dimVal = 0;
  3175. $rampTime = 1;
  3176. $setCmd = 0x2C;
  3177. readingsSingleUpdate($hash, "block", "lock", 1);
  3178. }
  3179. shift(@a);
  3180. }
  3181. #$updateState = 0;
  3182. #####
  3183. SetExtensionsCancel($hash);
  3184. $data = sprintf "%02X%02X%02X%02X", $gwCmdID, $dimVal, $rampTime, $setCmd;
  3185. } else {
  3186. my $cmdList = "dim:slider,0,1,100 local:learn on:noArg off:noArg teach:noArg";
  3187. return SetExtensions ($hash, $cmdList, $name, @a);
  3188. }
  3189. if ($sendDimCmd) {
  3190. readingsSingleUpdate($hash, "block", "unlock", 1);
  3191. if (defined $a[1]) {
  3192. return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] ne "lock" && $a[1] ne "unlock");
  3193. # Eltako devices: lock dimming value
  3194. if ($manufID eq "00D" && $a[1] eq "lock" ) {
  3195. $setCmd = $setCmd | 4;
  3196. readingsSingleUpdate($hash, "block", "lock", 1);
  3197. }
  3198. shift(@a);
  3199. } else {
  3200. # Dimming value relative
  3201. if ($manufID ne "00D") {$setCmd = $setCmd | 4;}
  3202. }
  3203. if ($cmd eq "off" && $dimMin =~ m/^\d+$/ && $dimMin == 0) {
  3204. # switch off
  3205. } elsif ($cmd eq "off" && $dimMin =~ m/^\d+$/) {
  3206. $dimVal = $dimMin;
  3207. $setCmd = 9;
  3208. } elsif ($dimMax eq "off" || $dimVal == 0 && $dimMin eq "off" || $dimVal < 0) {
  3209. # switch off
  3210. $dimVal = 0;
  3211. $setCmd = 8;
  3212. } elsif ($dimMin eq "off") {
  3213. } elsif ($dimVal < $dimMin) {
  3214. $dimVal = $dimMin;
  3215. }
  3216. $dimVal = $dimMax if ($dimVal > $dimMax);
  3217. $dimVal = 100 if ($dimVal > 100);
  3218. $rampTime = 0 if ($rampTime < 0);
  3219. $rampTime = 255 if ($rampTime > 255);
  3220. #$updateState = 0;
  3221. readingsSingleUpdate ($hash, "dim", $dimVal, 1);
  3222. $data = sprintf "%02X%02X%02X%02X", $gwCmdID, $dimVal, $rampTime, $setCmd;
  3223. }
  3224. } elsif ($gwCmd eq "setpointShift") {
  3225. $gwCmdID = 3;
  3226. if ($cmd eq "teach") {
  3227. # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
  3228. $data = "E047FF80";
  3229. $attr{$name}{eep} = "A5-38-08";
  3230. CommandDeleteReading(undef, "$name .*");
  3231. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  3232. $updateState = 0;
  3233. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  3234. EnOcean_setTeachConfirmWaitHash(undef, $hash);
  3235. } elsif ($cmd eq "shift") {
  3236. if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= -12.7) && ($a[1] <= 12.8)) {
  3237. #$updateState = 0;
  3238. $data = sprintf "%02X00%02X08", $gwCmdID, ($a[1] + 12.7) * 10;
  3239. shift(@a);
  3240. } else {
  3241. return "Usage: $a[1] is not numeric or out of range";
  3242. }
  3243. } else {
  3244. return "Unknown argument $cmd, choose one of teach:noArg shift";
  3245. }
  3246. } elsif ($gwCmd eq "setpointBasic") {
  3247. $gwCmdID = 4;
  3248. if($cmd eq "teach") {
  3249. # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
  3250. $data = "E047FF80";
  3251. $attr{$name}{eep} = "A5-38-08";
  3252. CommandDeleteReading(undef, "$name .*");
  3253. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  3254. $updateState = 0;
  3255. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  3256. EnOcean_setTeachConfirmWaitHash(undef, $hash);
  3257. } elsif ($cmd eq "basic") {
  3258. if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 51.2)) {
  3259. #$updateState = 0;
  3260. $data = sprintf "%02X00%02X08", $gwCmdID, $a[1] * 5;
  3261. shift(@a);
  3262. } else {
  3263. return "Usage: $cmd parameter is not numeric or out of range.";
  3264. }
  3265. } else {
  3266. return "Unknown argument $cmd, choose one of teach:noArg basic";
  3267. }
  3268. } elsif ($gwCmd eq "controlVar") {
  3269. $gwCmdID = 5;
  3270. my $controlVar = ReadingsVal($name, "controlVar", 0);
  3271. if($cmd eq "teach") {
  3272. # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
  3273. $data = "E047FF80";
  3274. $attr{$name}{eep} = "A5-38-08";
  3275. CommandDeleteReading(undef, "$name .*");
  3276. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  3277. $updateState = 0;
  3278. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  3279. EnOcean_setTeachConfirmWaitHash(undef, $hash);
  3280. } elsif ($cmd eq "presence") {
  3281. if ($a[1] eq "standby") {
  3282. $setCmd = 0x0A;
  3283. } elsif ($a[1] eq "absent") {
  3284. $setCmd = 9;
  3285. } elsif ($a[1] eq "present") {
  3286. $setCmd = 8;
  3287. } else {
  3288. return "Usage: $cmd parameter unknown.";
  3289. }
  3290. shift(@a);
  3291. $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd;
  3292. } elsif ($cmd eq "energyHoldOff") {
  3293. if ($a[1] eq "normal") {
  3294. $setCmd = 8;
  3295. } elsif ($a[1] eq "holdoff") {
  3296. $setCmd = 0x0C;
  3297. } else {
  3298. return "Usage: $cmd parameter unknown.";
  3299. }
  3300. shift(@a);
  3301. $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd;
  3302. } elsif ($cmd eq "controllerMode") {
  3303. if ($a[1] eq "auto") {
  3304. $setCmd = 8;
  3305. } elsif ($a[1] eq "heating") {
  3306. $setCmd = 0x28;
  3307. } elsif ($a[1] eq "cooling") {
  3308. $setCmd = 0x48;
  3309. } elsif ($a[1] eq "off" || $a[1] eq "BI") {
  3310. $setCmd = 0x68;
  3311. } else {
  3312. return "Usage: $cmd parameter unknown.";
  3313. }
  3314. shift(@a);
  3315. $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd;
  3316. } elsif ($cmd eq "controllerState") {
  3317. if ($a[1] eq "auto") {
  3318. $setCmd = 8;
  3319. } elsif ($a[1] eq "override") {
  3320. $setCmd = 0x18;
  3321. if (defined $a[2] && ($a[2] =~ m/^[+-]?\d+$/) && ($a[2] >= 0) && ($a[2] <= 100) ) {
  3322. $controlVar = $a[2] * 255;
  3323. shift(@a);
  3324. } else {
  3325. return "Usage: Control Variable Override is not numeric or out of range.";
  3326. }
  3327. } else {
  3328. return "Usage: $cmd parameter unknown.";
  3329. }
  3330. shift(@a);
  3331. #$updateState = 0;
  3332. $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd;
  3333. } else {
  3334. return "Unknown argument, choose one of teach:noArg presence:absent,present,standby energyHoldOff:holdoff,normal controllerMode:cooling,heating,off controllerState:auto,override";
  3335. }
  3336. } elsif ($gwCmd eq "fanStage") {
  3337. $gwCmdID = 6;
  3338. if($cmd eq "teach") {
  3339. # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
  3340. $data = "E047FF80";
  3341. $attr{$name}{eep} = "A5-38-08";
  3342. CommandDeleteReading(undef, "$name .*");
  3343. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  3344. $updateState = 0;
  3345. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  3346. EnOcean_setTeachConfirmWaitHash(undef, $hash);
  3347. } elsif ($cmd eq "stage") {
  3348. if ($a[1] eq "auto") {
  3349. #$updateState = 0;
  3350. $data = sprintf "%02X00%02X08", $gwCmdID, 255;
  3351. } elsif ($a[1] && $a[1] =~ m/^[0-3]$/) {
  3352. #$updateState = 0;
  3353. $data = sprintf "%02X00%02X08", $gwCmdID, $a[1];
  3354. } else {
  3355. return "Usage: $cmd parameter is not numeric or out of range"
  3356. }
  3357. shift(@a);
  3358. } else {
  3359. return "Unknown argument, choose one of teach:noArg stage:auto,0,1,2,3";
  3360. }
  3361. } elsif ($gwCmd eq "blindCmd") {
  3362. $gwCmdID = 7;
  3363. my %blindFunc = (
  3364. "status" => 0,
  3365. "stop" => 1,
  3366. "opens" => 2,
  3367. "closes" => 3,
  3368. "position" => 4,
  3369. "up" => 5,
  3370. "down" => 6,
  3371. "runtimeSet" => 7,
  3372. "angleSet" => 8,
  3373. "positionMinMax" => 9,
  3374. "angleMinMax" => 10,
  3375. "positionLogic" => 11,
  3376. "teach" => 255,
  3377. );
  3378. my @blindFunc = (
  3379. "position:slider,0,1,100",
  3380. "opens:noArg",
  3381. "closes:noArg",
  3382. "up",
  3383. "down",
  3384. "stop:noArg",
  3385. "status:noArg",
  3386. "runtimeSet",
  3387. "angleSet",
  3388. "positionMinMax",
  3389. "angleMinMax",
  3390. "positionLogic:normal,inverse",
  3391. "teach:noArg",
  3392. );
  3393. my $blindFuncID;
  3394. if (defined $blindFunc {$cmd}) {
  3395. $blindFuncID = $blindFunc {$cmd};
  3396. } elsif ($cmd =~ m/^\d+$/) {
  3397. # interpretive numeric value as position
  3398. unshift(@a, 'position');
  3399. $cmd = 'position';
  3400. $blindFuncID = 4;
  3401. } else {
  3402. return "Unknown Gateway Blind Central Function " . $cmd . ", choose one of ". join(" ", @blindFunc);
  3403. }
  3404. my $blindParam1 = 0;
  3405. my $blindParam2 = 0;
  3406. $setCmd = $blindFuncID << 4 | 8;
  3407. if($blindFuncID == 255) {
  3408. # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
  3409. $gwCmdID = 0xE0;
  3410. $blindParam1 = 0x47;
  3411. $blindParam2 = 0xFF;
  3412. $setCmd = 0x80;
  3413. $attr{$name}{eep} = "A5-38-08";
  3414. CommandDeleteReading(undef, "$name .*");
  3415. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  3416. $updateState = 0;
  3417. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  3418. } elsif ($blindFuncID == 0) {
  3419. # status
  3420. $updateState = 0;
  3421. } elsif ($blindFuncID == 1) {
  3422. # stop
  3423. $updateState = 0;
  3424. } elsif ($blindFuncID == 2) {
  3425. # opens
  3426. $updateState = 0;
  3427. } elsif ($blindFuncID == 3) {
  3428. # closes
  3429. $updateState = 0;
  3430. } elsif ($blindFuncID == 4) {
  3431. # position
  3432. if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
  3433. $blindParam1 = $a[1];
  3434. shift(@a);
  3435. if (defined $a[1]) {
  3436. if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= -180 && $a[1] <= 180) {
  3437. # set angle
  3438. $blindParam2 = abs($a[1]) / 2;
  3439. if ($a[1] < 0) {$blindParam2 |= 0x80;}
  3440. shift(@a);
  3441. } else {
  3442. return "Usage: $cmd variable is not numeric or out of range.";
  3443. }
  3444. } else {
  3445. # set angle defaults
  3446. my $positionLogic = ReadingsVal($name, 'positionLogic', 'normal');
  3447. my $angleMin = ReadingsVal($name, 'angleMin', -180);
  3448. my $angleMax = ReadingsVal($name, 'angleMax', 180);
  3449. if ($blindParam1 == 0) {
  3450. $blindParam2 = $positionLogic eq 'normal' ? $angleMax : $angleMin;
  3451. } elsif ($blindParam1 == 100) {
  3452. $blindParam2 = $positionLogic eq 'normal' ? $angleMin : $angleMax;
  3453. } else {
  3454. $blindParam2 = $angleMin + ($angleMax - $angleMin) / 2;
  3455. }
  3456. if ($blindParam2 < 0) {
  3457. $blindParam2 = abs($blindParam2) / 2;
  3458. $blindParam2 |= 0x80;
  3459. } else {
  3460. $blindParam2 = $blindParam2 / 2;
  3461. }
  3462. }
  3463. } else {
  3464. return "Usage: $cmd variable is not numeric or out of range.";
  3465. }
  3466. # angle und position value available
  3467. $setCmd |= 2;
  3468. $updateState = 0;
  3469. } elsif ($blindFuncID == 5 || $blindFuncID == 6) {
  3470. # up / down
  3471. if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
  3472. $blindParam1 = $a[1];
  3473. if (defined $a[2] && $a[2] =~ m/^[+-]?\d+(\.\d+)?$/ && $a[2] >= 0 && $a[2] <= 25.5) {
  3474. $blindParam2 = $a[2] * 10;
  3475. shift(@a);
  3476. } else {
  3477. return "Usage: $cmd variable is not numeric or out of range.";
  3478. }
  3479. shift(@a);
  3480. } else {
  3481. return "Usage: $cmd variable is not numeric or out of range.";
  3482. }
  3483. $updateState = 0;
  3484. } elsif ($blindFuncID == 7) {
  3485. # runtimeSet
  3486. if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
  3487. $blindParam1 = $a[1];
  3488. if (defined $a[2] && $a[2] =~ m/^[+-]?\d+$/ && $a[2] >= 0 && $a[2] <= 255) {
  3489. $blindParam2 = $a[2];
  3490. shift(@a);
  3491. } else {
  3492. return "Usage: $cmd variable is not numeric or out of range.";
  3493. }
  3494. shift(@a);
  3495. } else {
  3496. return "Usage: $cmd variable is not numeric or out of range.";
  3497. }
  3498. readingsSingleUpdate($hash, "runTimeUp", $blindParam1, 1);
  3499. readingsSingleUpdate($hash, "runTimeDown", $blindParam2, 1);
  3500. $updateState = 0;
  3501. } elsif ($blindFuncID == 8) {
  3502. # angleSet
  3503. if (defined $a[1] && $a[1] =~ m/^[+-]?\d+(\.\d+)?$/ && $a[1] >= 0 && $a[1] <= 25.5) {
  3504. $blindParam1 = $a[1] * 10;
  3505. ##
  3506. readingsSingleUpdate($hash, "angleTime", (sprintf "%0.1f", $a[1]), 1);
  3507. shift(@a);
  3508. } else {
  3509. return "Usage: $cmd variable is not numeric or out of range.";
  3510. }
  3511. $updateState = 0;
  3512. } elsif ($blindFuncID == 9) {
  3513. # positionMinMax
  3514. if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
  3515. $blindParam1 = $a[1];
  3516. if (defined $a[2] && $a[2] =~ m/^[+-]?\d+$/ && $a[2] >= 0 && $a[2] <= 100) {
  3517. $blindParam2 = $a[2];
  3518. shift(@a);
  3519. } else {
  3520. return "Usage: $cmd variable is not numeric or out of range.";
  3521. }
  3522. # angle und position value available
  3523. $setCmd |= 2;
  3524. shift(@a);
  3525. } else {
  3526. return "Usage: $cmd variable is not numeric or out of range.";
  3527. }
  3528. if ($blindParam1 > $blindParam2) {($blindParam1, $blindParam2) = ($blindParam2, $blindParam1);}
  3529. readingsSingleUpdate($hash, "positionMin", $blindParam1, 1);
  3530. readingsSingleUpdate($hash, "positionMax", $blindParam2, 1);
  3531. $updateState = 0;
  3532. } elsif ($blindFuncID == 10) {
  3533. # angleMinMax
  3534. if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= -180 && $a[1] <= 180) {
  3535. if (!defined $a[2] || $a[2] !~ m/^[+-]?\d+$/ || $a[2] < -180 || $a[2] > 180) {
  3536. return "Usage: $cmd variable is not numeric or out of range.";
  3537. }
  3538. if ($a[1] > $a[2]) {($a[1], $a[2]) = ($a[2], $a[1]);}
  3539. $blindParam1 = abs($a[1]) / 2;
  3540. if ($a[1] < 0) {$blindParam1 |= 0x80;}
  3541. $blindParam2 = abs($a[2]) / 2;
  3542. if ($a[2] < 0) {$blindParam2 |= 0x80;}
  3543. # angle und position value available
  3544. $setCmd |= 2;
  3545. } else {
  3546. return "Usage: $cmd variable is not numeric or out of range.";
  3547. }
  3548. readingsSingleUpdate($hash, "angleMin", $a[1], 1);
  3549. readingsSingleUpdate($hash, "angleMax", $a[2], 1);
  3550. splice (@a, 0, 2);
  3551. shift(@a);
  3552. $updateState = 0;
  3553. } elsif ($blindFuncID == 11) {
  3554. # positionLogic
  3555. if ($a[1] eq "normal") {
  3556. $blindParam1 = 0;
  3557. } elsif ($a[1] eq "inverse") {
  3558. $blindParam1 = 1;
  3559. } else {
  3560. return "Usage: $cmd variable is unknown.";
  3561. }
  3562. readingsSingleUpdate($hash, "positionLogic", $a[1], 1);
  3563. shift(@a);
  3564. $updateState = 0;
  3565. } else {
  3566. }
  3567. $setCmd |= 4 if (AttrVal($name, "sendDevStatus", "no") eq "yes");
  3568. $setCmd |= 1 if (AttrVal($name, "serviceOn", "no") eq "yes");
  3569. $data = sprintf "%02X%02X%02X%02X", $gwCmdID, $blindParam1, $blindParam2, $setCmd;
  3570. } else {
  3571. return "Unknown Gateway command " . $cmd . ", choose one of ". $cmdList . join(" ", sort keys %EnO_gwCmd);
  3572. }
  3573. Log3 $name, 3, "EnOcean set $name $cmd";
  3574. } elsif ($st eq "energyManagement.01") {
  3575. # Energy Management, Demand Response (A5-37-01)
  3576. $rorg = "A5";
  3577. $updateState = 0;
  3578. my $drLevel = 15;
  3579. my $powerUsage = 100;
  3580. my $powerUsageLevel = 1;
  3581. my $powerUsageScale = 0;
  3582. my $randomStart = 0;
  3583. my $randomEnd = 0;
  3584. my $randomTime = rand(AttrVal($name, "demandRespRandomTime", 1));
  3585. my $setpoint = 255;
  3586. my $timeout = 0;
  3587. my $threshold = AttrVal($name, "demandRespThreshold", 8);
  3588. if($cmd eq "teach") {
  3589. # teach-in EEP A5-37-01, Manufacturer "Multi user Manufacturer ID"
  3590. $data = "DC0FFF80";
  3591. $attr{$name}{eep} = "A5-37-01";
  3592. CommandDeleteReading(undef, "$name .*");
  3593. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  3594. $updateState = 0;
  3595. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  3596. Log3 $name, 3, "EnOcean set $name $cmd";
  3597. } elsif ($cmd eq "level") {
  3598. return "Usage: $cmd 0...15 [max|rel [yes|no [yes|no [timeout/min]]]]"
  3599. if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 15 );
  3600. $drLevel = $a[1];
  3601. $powerUsage = $a[1] / 15 * 100;
  3602. $powerUsageLevel = $drLevel >= $threshold ? 1 : 0;
  3603. $setpoint = $a[1] * 17;
  3604. shift(@a);
  3605. } elsif ($cmd eq "max") {
  3606. } elsif ($cmd eq "min") {
  3607. $drLevel = 0;
  3608. $powerUsage = 0;
  3609. $powerUsageLevel = 0;
  3610. $setpoint = 0;
  3611. } elsif ($cmd eq "power") {
  3612. return "Usage: $cmd 0...100 [max|rel [yes|no [yes|no [timeout/min]]]]"
  3613. if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 100);
  3614. $drLevel = $a[1] / 100 * 15;
  3615. $powerUsage = $a[1];
  3616. $powerUsageLevel = $drLevel >= $threshold ? 1 : 0;
  3617. $setpoint = $a[1] * 2.55;
  3618. shift(@a);
  3619. } elsif ($cmd eq "setpoint") {
  3620. return "Usage: $cmd 0...255 [max|rel [yes|no [yes|no [timeout/min]]]]"
  3621. if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 255 );
  3622. $drLevel = $a[1] / 255 * 15 ;
  3623. $powerUsage = $a[1] / 255 * 100;
  3624. $powerUsageLevel = $drLevel >= $threshold ? 1 : 0;
  3625. $setpoint = $a[1];
  3626. shift(@a);
  3627. } else {
  3628. return "Unknown argument " . $cmd . ", choose one of " . $cmdList . "level:slider,0,1,15 max:noArg min:noArg power:slider,0,5,100 setpoint:slider,0,5,255 teach:noArg"
  3629. }
  3630. if ($cmd ne "teach") {
  3631. if (@a > 1) {
  3632. return "Usage: $cmd [<cmdValue>] [max|rel [yes|no [yes|no [timeout/min]]]]" if($a[1] !~ m/^max|rel$/);
  3633. $powerUsageScale = $a[1] eq "rel" ? 0x80 : 0;
  3634. shift(@a);
  3635. }
  3636. if (@a > 1) {
  3637. return "Usage: $cmd [<cmdValue>] [max|rel [yes|no [yes|no [timeout/min]]]]" if($a[1] !~ m/^yes|no$/);
  3638. $randomStart = $a[1] eq "yes" ? 4 : 0;
  3639. shift(@a);
  3640. }
  3641. if (@a > 1) {
  3642. return "Usage: $cmd [<cmdValue>] [max|rel [yes|no [yes|no [timeout/min]]]]" if($a[1] !~ m/^yes|no$/);
  3643. $randomEnd = $a[1] eq "yes" ? 2 : 0;
  3644. shift(@a);
  3645. }
  3646. if (@a > 1) {
  3647. return "Usage: $cmd [<cmdValue>] [max|rel [yes|no [yes|no [timeout/min]]]]"
  3648. if($a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 3825);
  3649. $timeout = int($a[1] / 15);
  3650. shift(@a);
  3651. }
  3652. $data = sprintf "%02X%02X%02X%02X", $setpoint, $powerUsageScale | $powerUsage, $timeout,
  3653. $drLevel << 4 | $randomStart | $randomEnd | $powerUsageLevel | 8;
  3654. my @db = ($drLevel << 4 | $randomStart | $randomEnd | $powerUsageLevel | 8,
  3655. $timeout, $powerUsageScale | $powerUsage, $setpoint);
  3656. EnOcean_energyManagement_01Parse($hash, @db);
  3657. }
  3658. Log3 $name, 3, "EnOcean set $name $cmd";
  3659. } elsif ($st eq "lightCtrl.01") {
  3660. # Central Command, Extended Lighting-Control (EEP A5-38-09)
  3661. $rorg = "A5";
  3662. my %ctrlFunc = (
  3663. "off" => 1,
  3664. "on" => 2,
  3665. "dimup" => 3,
  3666. "dimdown" => 4,
  3667. "stop" => 5,
  3668. "dim" => 6,
  3669. "rgb" => 7,
  3670. "scene" => 8,
  3671. "dimMinMax" => 9,
  3672. "lampOpHours" => 10,
  3673. "block" => 11,
  3674. "meteringValue" => 12,
  3675. "teach" => 255,
  3676. );
  3677. my $ctrlFuncID;
  3678. if (exists $ctrlFunc{$cmd}) {
  3679. $ctrlFuncID = $ctrlFunc{$cmd};
  3680. } elsif ($cmd =~ m/^\d+$/) {
  3681. # interpretive numeric value as dimming
  3682. unshift(@a, 'dim');
  3683. $cmd = 'dim';
  3684. $ctrlFuncID = 6;
  3685. } else {
  3686. $cmdList .= "dim:slider,0,5,255 dimup:noArg dimdown:noArg on:noArg off:noArg stop:noArg rgb:colorpicker,RGB scene dimMinMax lampOpHours block meteringValue teach:noArg";
  3687. return SetExtensions ($hash, $cmdList, $name, @a);
  3688. }
  3689. my ($ctrlParam1, $ctrlParam2, $ctrlParam3) = (0, 0, 0);
  3690. my $setCmd = $ctrlFuncID << 4 | 8;
  3691. if($ctrlFuncID == 255) {
  3692. # teach-in EEP A5-38-09, Manufacturer "Multi user Manufacturer ID"
  3693. $ctrlParam1 = 0xE1;
  3694. $ctrlParam2 = 0xC7;
  3695. $ctrlParam3 = 0xFF;
  3696. $setCmd = 0x80;
  3697. $attr{$name}{eep} = "A5-38-09";
  3698. CommandDeleteReading(undef, "$name .*");
  3699. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  3700. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  3701. $updateState = 0;
  3702. } elsif ($ctrlFuncID == 1) {
  3703. # off
  3704. CommandDeleteReading(undef, "$name scene");
  3705. #####
  3706. SetExtensionsCancel($hash);
  3707. $updateState = 0;
  3708. } elsif ($ctrlFuncID == 2) {
  3709. # on
  3710. CommandDeleteReading(undef, "$name scene");
  3711. #####
  3712. SetExtensionsCancel($hash);
  3713. $updateState = 0;
  3714. } elsif ($ctrlFuncID == 3 || $ctrlFuncID == 4) {
  3715. # dimup / dimdown
  3716. my $rampTime = $a[1];
  3717. if (defined $a[1]) {
  3718. if ($a[1] =~ m/^\d+?$/ && $a[1] >= 0 && $a[1] <= 65535) {
  3719. shift(@a);
  3720. } else {
  3721. return "Usage: $cmd ramping time value is not numeric or out of range.";
  3722. }
  3723. } else {
  3724. $rampTime = AttrVal($name, "rampTime", 1);
  3725. }
  3726. $ctrlParam3 = $rampTime & 0xFF;
  3727. $ctrlParam2 = ($rampTime & 0xFF00) >> 8;
  3728. readingsSingleUpdate($hash, "rampTime", $rampTime, 1);
  3729. CommandDeleteReading(undef, "$name scene");
  3730. #####
  3731. SetExtensionsCancel($hash);
  3732. $updateState = 0;
  3733. } elsif ($ctrlFuncID == 5) {
  3734. # stop
  3735. CommandDeleteReading(undef, "$name scene");
  3736. #####
  3737. SetExtensionsCancel($hash);
  3738. $updateState = 0;
  3739. } elsif ($ctrlFuncID == 6) {
  3740. # dim
  3741. if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
  3742. $ctrlParam1 = $a[1];
  3743. shift(@a);
  3744. } else {
  3745. return "Usage: $cmd dimming value is not numeric or out of range.";
  3746. }
  3747. my $rampTime = $a[1];
  3748. if (defined $a[1]) {
  3749. if ($a[1] =~ m/^\d+?$/ && $a[1] >= 0 && $a[1] <= 65535) {
  3750. shift(@a);
  3751. } else {
  3752. return "Usage: $cmd ramping time value is not numeric or out of range.";
  3753. }
  3754. } else {
  3755. $rampTime = AttrVal($name, "rampTime", 1);
  3756. }
  3757. $ctrlParam3 = $rampTime & 0xFF;
  3758. $ctrlParam2 = ($rampTime & 0xFF00) >> 8;
  3759. CommandDeleteReading(undef, "$name scene");
  3760. readingsSingleUpdate($hash, "rampTime", $rampTime, 1);
  3761. #####
  3762. SetExtensionsCancel($hash);
  3763. $updateState = 0;
  3764. } elsif ($ctrlFuncID == 7) {
  3765. # RGB
  3766. if (@a > 1) {
  3767. if ($a[1] =~ m/^[\dA-Fa-f]{6}$/) {
  3768. # red
  3769. $ctrlParam1 = hex substr($a[1], 0, 2);
  3770. # green
  3771. $ctrlParam2 = hex substr($a[1], 2, 2);
  3772. # blue
  3773. $ctrlParam3 = hex substr($a[1], 4, 2);
  3774. readingsBeginUpdate($hash);
  3775. readingsBulkUpdate($hash, "red", $ctrlParam1);
  3776. readingsBulkUpdate($hash, "green", $ctrlParam2);
  3777. readingsBulkUpdate($hash, "blue", $ctrlParam3);
  3778. readingsBulkUpdate($hash, "rgb", uc($a[1]));
  3779. readingsEndUpdate($hash, 0);
  3780. shift(@a);
  3781. } else {
  3782. return "Usage: $cmd value is not hexadecimal or out of range.";
  3783. }
  3784. } else {
  3785. return "Usage: $cmd values are missing";
  3786. }
  3787. $updateState = 0;
  3788. } elsif ($ctrlFuncID == 8) {
  3789. # scene
  3790. if (@a > 2) {
  3791. if ($a[2] =~ m/^\d+?$/ && $a[2] >= 0 && $a[2] <= 15) {
  3792. $ctrlParam3 = $a[2];
  3793. } else {
  3794. return "Usage: $cmd number is not numeric or out of range.";
  3795. }
  3796. if ($a[1] eq "drive") {
  3797. readingsSingleUpdate($hash, "scene", $ctrlParam3, 1);
  3798. splice(@a, 0, 2);
  3799. } elsif ($a[1] eq "store") {
  3800. $ctrlParam3 |= 0x80;
  3801. splice(@a, 0, 2);
  3802. } else {
  3803. return "Usage: $cmd parameter is wrong.";
  3804. }
  3805. } else {
  3806. return "Usage: $cmd values are missing";
  3807. }
  3808. $updateState = 0;
  3809. } elsif ($ctrlFuncID == 9) {
  3810. # dimMinMax
  3811. if (@a > 2) {
  3812. if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
  3813. # dimming limit min
  3814. $ctrlParam1 = $a[1];
  3815. shift(@a);
  3816. } else {
  3817. return "Usage: $cmd value is not numeric or out of range.";
  3818. }
  3819. if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
  3820. # dimming limit max
  3821. $ctrlParam2 = $a[1];
  3822. shift(@a);
  3823. } else {
  3824. return "Usage: $cmd value is not numeric or out of range.";
  3825. }
  3826. readingsBeginUpdate($hash);
  3827. readingsBulkUpdate($hash, "dimMin", $ctrlParam1);
  3828. readingsBulkUpdate($hash, "dimMax", $ctrlParam2);
  3829. readingsEndUpdate($hash, 1);
  3830. } else {
  3831. return "Usage: $cmd values are missing";
  3832. }
  3833. $updateState = 0;
  3834. } elsif ($ctrlFuncID == 10) {
  3835. # set operating hours of the lamp
  3836. if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 65535) {
  3837. $ctrlParam2 = $a[1] & 0xFF;
  3838. $ctrlParam1 = ($a[1] & 0xFF00) >> 8;
  3839. shift(@a);
  3840. } else {
  3841. return "Usage: $cmd variable is not numeric or out of range.";
  3842. }
  3843. $updateState = 0;
  3844. } elsif ($ctrlFuncID == 11) {
  3845. # block
  3846. if (!defined $a[1]) {
  3847. return "Usage: $cmd values are missing";
  3848. } elsif ($a[1] eq "unlock") {
  3849. $ctrlParam3 = 0;
  3850. } elsif ($a[1] eq "on") {
  3851. $ctrlParam3 = 1;
  3852. } elsif ($a[1] eq "off") {
  3853. $ctrlParam3 = 2;
  3854. } elsif ($a[1] eq "local") {
  3855. $ctrlParam3 = 3;
  3856. } else {
  3857. return "Usage: $cmd variable is unknown.";
  3858. }
  3859. readingsSingleUpdate($hash, "block", $a[1], 1);
  3860. shift(@a);
  3861. $updateState = 0;
  3862. } elsif ($ctrlFuncID == 12) {
  3863. # meteringValues
  3864. if (@a > 2) {
  3865. my %unitEnum = (
  3866. "mW" => 0,
  3867. "W" => 1,
  3868. "kW" => 2,
  3869. "MW" => 3,
  3870. "Wh" => 4,
  3871. "kWh" => 5,
  3872. "MWh" => 6,
  3873. "GWh" => 7,
  3874. "mA" => 8,
  3875. "A" => 9,
  3876. "mV" => 10,
  3877. "V" => 11,
  3878. );
  3879. if (exists $unitEnum{$a[2]}) {
  3880. $ctrlParam3 = $unitEnum{$a[2]};
  3881. } else {
  3882. return "Unknown metering value choose one of " . join(" ", sort keys %unitEnum);
  3883. }
  3884. if ($ctrlParam3 == 9 || $ctrlParam3 == 11) {
  3885. if ($a[1] =~ m/^\d+(\.\d+)?$/ && $a[1] >= 0 && $a[1] <= 6553.5) {
  3886. $ctrlParam2 = int($a[1] * 10) & 0xFF;
  3887. $ctrlParam1 = (int($a[1] * 10) & 0xFF00) >> 8;
  3888. } else {
  3889. return "Usage: $cmd value is not numeric or out of range.";
  3890. }
  3891. } else {
  3892. if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 65535) {
  3893. $ctrlParam2 = $a[1] & 0xFF;
  3894. $ctrlParam1 = ($a[1] & 0xFF00) >> 8;
  3895. } else {
  3896. return "Usage: $cmd value is not numeric or out of range.";
  3897. }
  3898. }
  3899. splice(@a, 0, 2);
  3900. } else {
  3901. return "Usage: $cmd values are missing";
  3902. }
  3903. $updateState = 0;
  3904. }
  3905. $setCmd |= 4 if (AttrVal($name, "sendDevStatus", "yes") eq "no");
  3906. $setCmd |= 1 if (AttrVal($name, "serviceOn", "no") eq "yes");
  3907. $data = sprintf "%02X%02X%02X%02X", $ctrlParam1, $ctrlParam2, $ctrlParam3, $setCmd;
  3908. Log3 $name, 3, "EnOcean set $name $cmd";
  3909. } elsif ($st eq "radioLinkTest") {
  3910. # Radio Link Test (A5-3F-00)
  3911. $rorg = "A5";
  3912. $updateState = 3;
  3913. if($cmd =~ m/^standby|stop$/) {
  3914. @{$hash->{helper}{rlt}{param}} = ($cmd, $hash, undef, $subDef, 'master', 0);
  3915. EnOcean_RLT($hash->{helper}{rlt}{param});
  3916. } else {
  3917. return "Unknown argument " . $cmd . ", choose one of " . $cmdList . "standby:noArg stop:noArg"
  3918. }
  3919. Log3 $name, 3, "EnOcean set $name $cmd";
  3920. } elsif ($st eq "manufProfile") {
  3921. if ($manufID eq "00D") {
  3922. # Eltako Shutter
  3923. my $angleMax = AttrVal($name, "angleMax", 90);
  3924. my $angleMin = AttrVal($name, "angleMin", -90);
  3925. my $anglePos = ReadingsVal($name, "anglePos", undef);
  3926. my $anglePosStart;
  3927. my $angleTime = AttrVal($name, "angleTime", 0);
  3928. my $position = ReadingsVal($name, "position", undef);
  3929. my $positionStart;
  3930. my $setCmd = 8;
  3931. my $settingAccuracy = 1;
  3932. if (AttrVal($name, 'settingAccuracy', 'low') eq 'high') {
  3933. $setCmd = 0x0A;
  3934. $settingAccuracy = 10;
  3935. }
  3936. if ($cmd eq "?" || $cmd eq "stop") {
  3937. } else {
  3938. # check actual shutter position
  3939. my $actualState = ReadingsVal($name, "state", undef);
  3940. if (defined $actualState) {
  3941. if ($actualState eq "open") {
  3942. $position = 0;
  3943. $anglePos = 0;
  3944. } elsif ($actualState eq "closed") {
  3945. $position = 100;
  3946. $anglePos = $angleMax;
  3947. }
  3948. }
  3949. $anglePosStart = $anglePos;
  3950. $positionStart = $position;
  3951. readingsSingleUpdate($hash, ".anglePosStart", $anglePosStart, 0);
  3952. readingsSingleUpdate($hash, ".positionStart", $positionStart, 0);
  3953. }
  3954. $rorg = "A5";
  3955. my $shutTime = AttrVal($name, "shutTime", 255);
  3956. my $shutTimeCloses = AttrVal($name, "shutTimeCloses", $shutTime);
  3957. $shutTimeCloses = $shutTime if ($shutTimeCloses < $shutTime);
  3958. my $shutCmd = 0;
  3959. $angleMax = 90 if ($angleMax !~ m/^[+-]?\d+$/);
  3960. $angleMax = 180 if ($angleMax > 180);
  3961. $angleMax = -180 if ($angleMax < -180);
  3962. $angleMin = -90 if ($angleMin !~ m/^[+-]?\d+$/);
  3963. $angleMin = 180 if ($angleMin > 180);
  3964. $angleMin = -180 if ($angleMin < -180);
  3965. ($angleMax, $angleMin) = ($angleMin, $angleMax) if ($angleMin > $angleMax);
  3966. $angleMax ++ if ($angleMin == $angleMax);
  3967. $angleTime = 6 if ($angleTime !~ m/^[+-]?\d+$/);
  3968. $angleTime = 6 if ($angleTime > 6);
  3969. $angleTime = 0 if ($angleTime < 0);
  3970. $shutTime = 255 if ($shutTime !~ m/^[+-]?\d+$/);
  3971. $shutTime = 255 if ($shutTime > 255);
  3972. $shutTime = 1 if ($shutTime < 1);
  3973. if ($cmd =~ m/^\d+$/) {
  3974. # interpretive numeric value as position
  3975. unshift(@a, 'position');
  3976. $cmd = 'position';
  3977. }
  3978. if ($cmd eq "teach") {
  3979. # teach-in EEP A5-3F-7F, Manufacturer "Eltako"
  3980. $data = "FFF80D80";
  3981. $attr{$name}{eep} = "A5-3F-7F";
  3982. CommandDeleteReading(undef, "$name .*");
  3983. readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
  3984. $updateState = 0;
  3985. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
  3986. EnOcean_setTeachConfirmWaitHash(undef, $hash);
  3987. } elsif ($cmd eq "stop") {
  3988. # stop
  3989. # delete readings, as they are undefined
  3990. CommandDeleteReading(undef, "$name anglePos");
  3991. CommandDeleteReading(undef, "$name position");
  3992. readingsSingleUpdate($hash, "endPosition", "not_reached", 1);
  3993. readingsSingleUpdate($hash, "state", "stop", 1);
  3994. $shutCmd = 0;
  3995. } elsif ($cmd eq "opens") {
  3996. # opens >> B0
  3997. $anglePos = 0;
  3998. $position = 0;
  3999. readingsSingleUpdate($hash, "anglePos", $anglePos, 1);
  4000. readingsSingleUpdate($hash, "position", $position, 1);
  4001. readingsSingleUpdate($hash, "endPosition", "open", 1);
  4002. $cmd = "open";
  4003. $shutTime = $shutTimeCloses;
  4004. $shutCmd = 1;
  4005. #$updateState = 0;
  4006. } elsif ($cmd eq "closes") {
  4007. # closes >> BI
  4008. $anglePos = $angleMax;
  4009. $position = 100;
  4010. readingsSingleUpdate($hash, "anglePos", $anglePos, 1);
  4011. readingsSingleUpdate($hash, "position", $position, 1);
  4012. readingsSingleUpdate($hash, "endPosition", "closed", 1);
  4013. $cmd = "closed";
  4014. $shutTime = $shutTimeCloses;
  4015. $shutCmd = 2;
  4016. #$updateState = 0;
  4017. } elsif ($cmd eq "up") {
  4018. # up
  4019. if (defined $a[1]) {
  4020. #if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
  4021. if ($a[1] =~ m/^[+-]?\d*[.]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
  4022. $position = $positionStart - $a[1] / $shutTime * 100;
  4023. if ($angleTime) {
  4024. $anglePos = $anglePosStart - ($angleMax - $angleMin) * $a[1] / $angleTime;
  4025. if ($anglePos < $angleMin) {
  4026. $anglePos = $angleMin;
  4027. }
  4028. } else {
  4029. $anglePos = $angleMin;
  4030. }
  4031. if ($position <= 0) {
  4032. $anglePos = 0;
  4033. $position = 0;
  4034. readingsSingleUpdate($hash, "endPosition", "open", 1);
  4035. $cmd = "open";
  4036. } else {
  4037. readingsSingleUpdate($hash, "endPosition", "not_reached", 1);
  4038. $cmd = "not_reached";
  4039. }
  4040. $shutTime = $a[1];
  4041. shift(@a);
  4042. } else {
  4043. return "Usage: $a[1] is not numeric or out of range";
  4044. }
  4045. } else {
  4046. $anglePos = 0;
  4047. $position = 0;
  4048. readingsSingleUpdate($hash, "endPosition", "open", 1);
  4049. $cmd = "open";
  4050. }
  4051. readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1);
  4052. readingsSingleUpdate($hash, "position", sprintf("%d", $position), 1);
  4053. $shutCmd = 1;
  4054. } elsif ($cmd eq "down") {
  4055. # down
  4056. if (defined $a[1]) {
  4057. #if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
  4058. if ($a[1] =~ m/^[+-]?\d*[.]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
  4059. $position = $positionStart + $a[1] / $shutTime * 100;
  4060. if ($angleTime) {
  4061. $anglePos = $anglePosStart + ($angleMax - $angleMin) * $a[1] / $angleTime;
  4062. if ($anglePos > $angleMax) {
  4063. $anglePos = $angleMax;
  4064. }
  4065. } else {
  4066. $anglePos = $angleMax;
  4067. }
  4068. if($position >= 100) {
  4069. $anglePos = $angleMax;
  4070. $position = 100;
  4071. readingsSingleUpdate($hash, "endPosition", "closed", 1);
  4072. $cmd = "closed";
  4073. } else {
  4074. readingsSingleUpdate($hash, "endPosition", "not_reached", 1);
  4075. $cmd = "not_reached";
  4076. }
  4077. $shutTime = $a[1];
  4078. shift(@a);
  4079. } else {
  4080. return "Usage: $a[1] is not numeric or out of range";
  4081. }
  4082. } else {
  4083. $anglePos = $angleMax;
  4084. $position = 100;
  4085. readingsSingleUpdate($hash, "endPosition", "closed", 1);
  4086. $cmd = "closed";
  4087. }
  4088. readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1);
  4089. readingsSingleUpdate($hash, "position", sprintf("%d", $position), 1);
  4090. $shutCmd = 2;
  4091. } elsif ($cmd eq "position") {
  4092. if (!defined $positionStart) {
  4093. return "Position unknown, please first opens the blinds completely."
  4094. } elsif ($angleTime > 0 && !defined $anglePosStart){
  4095. return "Slats angle position unknown, please first opens the blinds completely."
  4096. } else {
  4097. my $shutTimeSet = $shutTime;
  4098. if (defined $a[2]) {
  4099. if ($a[2] =~ m/^[+-]?\d+$/ && $a[2] >= $angleMin && $a[2] <= $angleMax) {
  4100. $anglePos = $a[2];
  4101. } else {
  4102. return "Usage: $a[1] $a[2] is not numeric or out of range";
  4103. }
  4104. splice(@a,2,1);
  4105. } else {
  4106. $anglePos = $angleMax;
  4107. }
  4108. if ($positionStart <= $angleTime * $angleMax / ($angleMax - $angleMin) / $shutTimeSet * 100) {
  4109. $anglePosStart = $angleMax;
  4110. }
  4111. if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
  4112. if ($positionStart < $a[1]) {
  4113. # down
  4114. $angleTime = $angleTime * ($angleMax - $anglePos) / ($angleMax - $angleMin);
  4115. $shutTime = $shutTime * ($a[1] - $positionStart) / 100 + $angleTime;
  4116. # round up
  4117. $angleTime = int($angleTime) + 1 if ($settingAccuracy == 1 && $angleTime > int($angleTime));
  4118. $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
  4119. $position = $a[1] + $angleTime / $shutTimeSet * 100;
  4120. if ($position >= 100) {
  4121. $position = 100;
  4122. }
  4123. $shutCmd = 2;
  4124. if ($angleTime) {
  4125. my @timerCmd = ($name, "up", $angleTime);
  4126. my %par = (hash => $hash, timerCmd => \@timerCmd);
  4127. InternalTimer(gettimeofday() + $shutTime + 1, "EnOcean_TimerSet", \%par, 0);
  4128. }
  4129. } elsif ($positionStart > $a[1]) {
  4130. # up
  4131. $angleTime = $angleTime * ($anglePos - $angleMin) /($angleMax - $angleMin);
  4132. $shutTime = $shutTime * ($positionStart - $a[1]) / 100 + $angleTime;
  4133. # round up
  4134. $angleTime = int($angleTime) + 1 if ($settingAccuracy == 1 && $angleTime > int($angleTime));
  4135. $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
  4136. $position = $a[1] - $angleTime / $shutTimeSet * 100;
  4137. if ($position <= 0) {
  4138. $position = 0;
  4139. $anglePos = 0;
  4140. }
  4141. $shutCmd = 1;
  4142. if ($angleTime && $a[1] > 0) {
  4143. my @timerCmd = ($name, "down", $angleTime);
  4144. my %par = (hash => $hash, timerCmd => \@timerCmd);
  4145. InternalTimer(gettimeofday() + $shutTime + 1, "EnOcean_TimerSet", \%par, 0);
  4146. }
  4147. } else {
  4148. if ($anglePosStart > $anglePos) {
  4149. # up >> reduce slats angle
  4150. $shutTime = $angleTime * ($anglePosStart - $anglePos)/($angleMax - $angleMin);
  4151. # round up
  4152. $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
  4153. $shutCmd = 1;
  4154. } elsif ($anglePosStart < $anglePos) {
  4155. # down >> enlarge slats angle
  4156. $shutTime = $angleTime * ($anglePos - $anglePosStart) /($angleMax - $angleMin);
  4157. # round up
  4158. $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
  4159. $shutCmd = 2;
  4160. } else {
  4161. # position and slats angle ok
  4162. $data = '00000008';
  4163. $shutCmd = 0;
  4164. $updateState = 3;
  4165. }
  4166. }
  4167. if ($position == 0) {
  4168. readingsSingleUpdate($hash, "endPosition", "open", 1);
  4169. $cmd = "open";
  4170. } elsif ($position == 100) {
  4171. readingsSingleUpdate($hash, "endPosition", "closed", 1);
  4172. $cmd = "closed";
  4173. } else {
  4174. readingsSingleUpdate($hash, "endPosition", "not_reached", 1);
  4175. $cmd = "not_reached";
  4176. }
  4177. readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1);
  4178. readingsSingleUpdate($hash, "position", sprintf("%d", $position), 1);
  4179. shift(@a);
  4180. } else {
  4181. return "Usage: $a[1] is not numeric or out of range";
  4182. }
  4183. }
  4184. } elsif ($cmd eq "anglePos") {
  4185. if (!defined $positionStart) {
  4186. return "Position unknown, please first opens the blinds completely."
  4187. } elsif ($angleTime > 0 && !defined $anglePosStart){
  4188. return "Slats angle position unknown, please first opens the blinds completely."
  4189. } else {
  4190. if (defined $a[1]) {
  4191. if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= $angleMin && $a[1] <= $angleMax) {
  4192. $anglePos = $a[1];
  4193. shift(@a);
  4194. if ($anglePosStart > $anglePos) {
  4195. # up >> reduce slats angle
  4196. $shutTime = $angleTime * ($anglePosStart - $anglePos)/($angleMax - $angleMin);
  4197. # round up
  4198. $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
  4199. $shutCmd = 1;
  4200. } elsif ($anglePosStart < $anglePos) {
  4201. # down >> enlarge slats angle
  4202. $shutTime = $angleTime * ($anglePos - $anglePosStart) /($angleMax - $angleMin);
  4203. # round up
  4204. $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
  4205. $shutCmd = 2;
  4206. } else {
  4207. # slats angle ok
  4208. $data = '00000008';
  4209. $shutCmd = 0;
  4210. $updateState = 3;
  4211. }
  4212. readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1);
  4213. } else {
  4214. return "Usage: $a[1] is not numeric or out of range";
  4215. }
  4216. } else {
  4217. return "Usage: $cmd values are missing";
  4218. }
  4219. }
  4220. } elsif ($cmd eq "local") {
  4221. if ($a[1]) {
  4222. return "Usage: $cmd [learn]" if ($a[1] ne "learn");
  4223. if ($a[1] eq "learn") {
  4224. $setCmd = $setCmd | 0x20;
  4225. }
  4226. shift(@a);
  4227. }
  4228. $updateState = 0;
  4229. $data = sprintf "%04X%02X%02X", int($shutTime * $settingAccuracy), $shutCmd, $setCmd;
  4230. } else {
  4231. return "Unknown argument " . $cmd . ", choose one of " . $cmdList . "position:slider,0,5,100 anglePos:slider,-180,5,180 closes:noArg down local:learn opens:noArg stop:noArg teach:noArg up"
  4232. }
  4233. if ($shutCmd || $cmd eq "stop") {
  4234. #$updateState = 0;
  4235. $data = sprintf "%04X%02X%02X", int($shutTime * $settingAccuracy), $shutCmd, $setCmd;
  4236. }
  4237. Log3 $name, 3, "EnOcean set $name $cmd";
  4238. }
  4239. } elsif ($st eq "actuator.01") {
  4240. # Electronic switches and dimmers with Energy Measurement and Local Control
  4241. # (D2-01-00 - D2-01-12)
  4242. $rorg = "D2";
  4243. #$updateState = 0;
  4244. my $cmdID;
  4245. my $channel;
  4246. my $dimValTimer = 0;
  4247. my $outputVal;
  4248. if ($cmd =~ m/^\d+$/) {
  4249. # interpretive numeric value as dimming
  4250. unshift(@a, 'dim');
  4251. $cmd = 'dim';
  4252. }
  4253. if ($cmd eq "on") {
  4254. shift(@a);
  4255. $cmdID = 1;
  4256. my $dimValueOn = AttrVal($name, "dimValueOn", 100);
  4257. if ($dimValueOn eq "stored") {
  4258. $outputVal = ReadingsVal($name, "dimValueStored", 100);
  4259. if ($outputVal < 1) {
  4260. $outputVal = 100;
  4261. readingsSingleUpdate ($hash, "dimValueStored", $outputVal, 1);
  4262. }
  4263. } elsif ($dimValueOn eq "last") {
  4264. $outputVal = ReadingsVal ($name, "dimValueLast", 100);
  4265. if ($outputVal < 1) { $outputVal = 100; }
  4266. } else {
  4267. if ($dimValueOn !~ m/^[+-]?\d+$/) {
  4268. $outputVal = 100;
  4269. } elsif ($dimValueOn > 100) {
  4270. $outputVal = 100;
  4271. } elsif ($dimValueOn < 1) {
  4272. $outputVal = 1;
  4273. } else {
  4274. $outputVal = $dimValueOn;
  4275. }
  4276. }
  4277. $channel = shift(@a);
  4278. $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
  4279. if (!defined $channel || $channel eq "all") {
  4280. CommandDeleteReading(undef, "$name channel.*");
  4281. CommandDeleteReading(undef, "$name dim.*");
  4282. readingsSingleUpdate($hash, "channelAll", "on", 1);
  4283. readingsSingleUpdate($hash, "dim", $outputVal, 1);
  4284. $channel = 30;
  4285. } elsif ($channel eq "input" || $channel + 0 == 31) {
  4286. readingsSingleUpdate($hash, "channelInput", "on", 1);
  4287. readingsSingleUpdate($hash, "dimInput", $outputVal, 1);
  4288. $channel = 31;
  4289. } elsif ($channel + 0 >= 30) {
  4290. CommandDeleteReading(undef, "$name channel.*");
  4291. CommandDeleteReading(undef, "$name dim.*");
  4292. readingsSingleUpdate($hash, "channelAll", "on", 1);
  4293. readingsSingleUpdate($hash, "dim", $outputVal, 1);
  4294. $channel = 30;
  4295. } elsif ($channel + 0 >= 0 && $channel + 0 <= 29) {
  4296. readingsSingleUpdate($hash, "channel" . $channel, "on", 1);
  4297. readingsSingleUpdate($hash, "dim" . $channel, $outputVal, 1);
  4298. } else {
  4299. return "$cmd $channel wrong, choose 0...29|all|input.";
  4300. }
  4301. #readingsSingleUpdate($hash, "state", "on", 1);
  4302. $data = sprintf "%02X%02X%02X", $cmdID, $dimValTimer << 5 | $channel, $outputVal;
  4303. } elsif ($cmd eq "off") {
  4304. shift(@a);
  4305. $cmdID = 1;
  4306. $outputVal = 0;
  4307. $channel = shift(@a);
  4308. $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
  4309. if (!defined $channel || $channel eq "all") {
  4310. CommandDeleteReading(undef, "$name channel.*");
  4311. CommandDeleteReading(undef, "$name dim.*");
  4312. readingsSingleUpdate($hash, "channelAll", "off", 1);
  4313. readingsSingleUpdate($hash, "dim", $outputVal, 1);
  4314. $channel = 30;
  4315. } elsif ($channel eq "input" || $channel + 0 == 31) {
  4316. readingsSingleUpdate($hash, "channelInput", "off", 1);
  4317. readingsSingleUpdate($hash, "dimInput", $outputVal, 1);
  4318. $channel = 31;
  4319. } elsif ($channel + 0 >= 30) {
  4320. CommandDeleteReading(undef, "$name channel.*");
  4321. CommandDeleteReading(undef, "$name dim.*");
  4322. readingsSingleUpdate($hash, "channelAll", "off", 1);
  4323. readingsSingleUpdate($hash, "dim", $outputVal, 1);
  4324. $channel = 30;
  4325. } elsif ($channel >= 0 && $channel <= 29) {
  4326. readingsSingleUpdate($hash, "channel" . $channel, "off", 1);
  4327. readingsSingleUpdate($hash, "dim" . $channel, $outputVal, 1);
  4328. } else {
  4329. return "$cmd $channel wrong, choose 0...39|all|input.";
  4330. }
  4331. #readingsSingleUpdate($hash, "state", "off", 1);
  4332. $data = sprintf "%02X%02X%02X", $cmdID, $dimValTimer << 5 | $channel, $outputVal;
  4333. } elsif ($cmd eq "dim") {
  4334. shift(@a);
  4335. $cmdID = 1;
  4336. $outputVal = shift(@a);
  4337. if (!defined $outputVal || $outputVal !~ m/^[+-]?\d+$/ || $outputVal < 0 || $outputVal > 100) {
  4338. return "Usage: $cmd variable is not numeric or out of range.";
  4339. }
  4340. $channel = shift(@a);
  4341. $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
  4342. if (!defined $channel) {
  4343. CommandDeleteReading(undef, "$name channel.*");
  4344. CommandDeleteReading(undef, "$name dim.*");
  4345. if ($outputVal == 0) {
  4346. readingsSingleUpdate($hash, "channelAll", "off", 1);
  4347. } else {
  4348. readingsSingleUpdate($hash, "channelAll", "on", 1);
  4349. }
  4350. readingsSingleUpdate($hash, "dim", $outputVal, 1);
  4351. $channel = 30;
  4352. } else {
  4353. if ($channel eq "all") {
  4354. CommandDeleteReading(undef, "$name channel.*");
  4355. CommandDeleteReading(undef, "$name dim.*");
  4356. if ($outputVal == 0) {
  4357. readingsSingleUpdate($hash, "channelAll", "off", 1);
  4358. } else {
  4359. readingsSingleUpdate($hash, "channelAll", "on", 1);
  4360. }
  4361. readingsSingleUpdate($hash, "dim", $outputVal, 1);
  4362. $channel = 30;
  4363. } elsif ($channel eq "input" || $channel + 0 == 31) {
  4364. if ($outputVal == 0) {
  4365. readingsSingleUpdate($hash, "channelInput", "off", 1);
  4366. } else {
  4367. readingsSingleUpdate($hash, "channelInput", "on", 1);
  4368. }
  4369. readingsSingleUpdate($hash, "dimInput", $outputVal, 1);
  4370. $channel = 31;
  4371. } elsif ($channel + 0 >= 30) {
  4372. CommandDeleteReading(undef, "$name channel.*");
  4373. CommandDeleteReading(undef, "$name dim.*");
  4374. if ($outputVal == 0) {
  4375. readingsSingleUpdate($hash, "channelAll", "off", 1);
  4376. } else {
  4377. readingsSingleUpdate($hash, "channelAll", "on", 1);
  4378. }
  4379. readingsSingleUpdate($hash, "dim", $outputVal, 1);
  4380. $channel = 30;
  4381. } elsif ($channel >= 0 && $channel <= 29) {
  4382. if ($outputVal == 0) {
  4383. readingsSingleUpdate($hash, "channel" . $channel, "off", 1);
  4384. } else {
  4385. readingsSingleUpdate($hash, "channel" . $channel, "on", 1);
  4386. }
  4387. readingsSingleUpdate($hash, "dim" . $channel, $outputVal, 1);
  4388. } else {
  4389. return "Usage: $cmd $channel wrong, choose 0...39|all|input.";
  4390. }
  4391. $dimValTimer = shift(@a);
  4392. if (defined $dimValTimer) {
  4393. if ($dimValTimer eq "switch") {
  4394. $dimValTimer = 0;
  4395. } elsif ($dimValTimer eq "stop") {
  4396. $dimValTimer = 4;
  4397. } elsif ($dimValTimer =~ m/^[1-3]$/) {
  4398. } else {
  4399. return "Usage: $cmd <channel> $dimValTimer wrong, choose 1..3|switch|stop.";
  4400. }
  4401. } else {
  4402. $dimValTimer = 0;
  4403. }
  4404. }
  4405. if ($outputVal == 0) {
  4406. $cmd = "off";
  4407. #readingsSingleUpdate($hash, "state", "off", 1);
  4408. } else {
  4409. $cmd = "on";
  4410. #readingsSingleUpdate($hash, "state", "on", 1);
  4411. }
  4412. $data = sprintf "%02X%02X%02X", $cmdID, $dimValTimer << 5 | $channel, $outputVal;
  4413. } elsif ($cmd eq "local") {
  4414. shift(@a);
  4415. $updateState = 0;
  4416. $cmdID = 2;
  4417. # same configuration for all channels
  4418. $channel = 30;
  4419. my $dayNight = ReadingsVal($name, "dayNight", "day");
  4420. my $dayNightCmd = ($dayNight eq "night")? 1:0;
  4421. my $defaultState = ReadingsVal($name, "defaultState", "off");
  4422. my $defaultStateCmd;
  4423. if ($defaultState eq "off") {
  4424. $defaultStateCmd = 0;
  4425. } elsif ($defaultState eq "on") {
  4426. $defaultStateCmd = 1;
  4427. } elsif ($defaultState eq "last") {
  4428. $defaultStateCmd = 2;
  4429. } else {
  4430. $defaultStateCmd = 0;
  4431. }
  4432. my $localControl = ReadingsVal($name, "localControl", "disabled");
  4433. my $localControlCmd = ($localControl eq "enabled")? 1:0;
  4434. my $overCurrentShutdown = ReadingsVal($name, "overCurrentShutdown", "off");
  4435. my $overCurrentShutdownCmd = ($overCurrentShutdown eq "restart")? 1:0;
  4436. my $overCurrentShutdownReset = "not_active";
  4437. my $overCurrentShutdownResetCmd = 0;
  4438. my $rampTime1 = ReadingsVal($name, "rampTime1", 0);
  4439. my $rampTime1Cmd = $rampTime1 * 2;
  4440. if ($rampTime1Cmd <= 0) {
  4441. $rampTime1Cmd = 0;
  4442. } elsif ($rampTime1Cmd >= 15) {
  4443. $rampTime1Cmd = 15;
  4444. }
  4445. my $rampTime2 = ReadingsVal($name, "rampTime2", 0);
  4446. my $rampTime2Cmd = $rampTime2 * 2;
  4447. if ($rampTime2Cmd <= 0) {
  4448. $rampTime2Cmd = 0;
  4449. } elsif ($rampTime2Cmd >= 15) {
  4450. $rampTime2Cmd = 15;
  4451. }
  4452. my $rampTime3 = ReadingsVal($name, "rampTime3", 0);
  4453. my $rampTime3Cmd = $rampTime3 * 2;
  4454. if ($rampTime3Cmd <= 0) {
  4455. $rampTime3Cmd = 0;
  4456. } elsif ($rampTime3Cmd >= 15) {
  4457. $rampTime3Cmd = 15;
  4458. }
  4459. my $teachInDev = ReadingsVal($name, "teachInDev", "disabled");
  4460. my $teachInDevCmd = ($teachInDev eq "enabled")? 1:0;
  4461. my $localCmd = shift(@a);
  4462. my $localCmdVal = shift(@a);
  4463. if ($localCmd eq "dayNight") {
  4464. if ($localCmdVal eq "day") {
  4465. $dayNight = "day";
  4466. $dayNightCmd = 0;
  4467. } elsif ($localCmdVal eq "night") {
  4468. $dayNight = "night";
  4469. $dayNightCmd = 1;
  4470. } else {
  4471. return "Usage: $cmd $localCmd <value> wrong, choose day night.";
  4472. }
  4473. } elsif ($localCmd eq "defaultState"){
  4474. if ($localCmdVal eq "off") {
  4475. $defaultState = "off";
  4476. $defaultStateCmd = 0;
  4477. } elsif ($localCmdVal eq "on") {
  4478. $defaultState = "on";
  4479. $defaultStateCmd = 1;
  4480. } elsif ($localCmdVal eq "last") {
  4481. $defaultState = "last";
  4482. $defaultStateCmd = 2;
  4483. } else {
  4484. return "Usage: $cmd $localCmd <value> wrong, choose on off last.";
  4485. }
  4486. } elsif ($localCmd eq "localControl"){
  4487. if ($localCmdVal eq "disabled") {
  4488. $localControl = "disabled";
  4489. $localControlCmd = 0;
  4490. } elsif ($localCmdVal eq "enabled") {
  4491. $localControl = "enabled";
  4492. $localControlCmd = 1;
  4493. } else {
  4494. return "Usage: $cmd $localCmd <value> wrong, choose disabled enabled.";
  4495. }
  4496. } elsif ($localCmd eq "overCurrentShutdown"){
  4497. if ($localCmdVal eq "off") {
  4498. $overCurrentShutdown = "off";
  4499. $overCurrentShutdownCmd = 0;
  4500. } elsif ($localCmdVal eq "restart") {
  4501. $overCurrentShutdown = "restart";
  4502. $overCurrentShutdownCmd = 1;
  4503. } else {
  4504. return "Usage: $cmd $localCmd <value> wrong, choose off restart.";
  4505. }
  4506. } elsif ($localCmd eq "overCurrentShutdownReset"){
  4507. if ($localCmdVal eq "not_active") {
  4508. $overCurrentShutdownReset = "not_active";
  4509. $overCurrentShutdownResetCmd = 0;
  4510. } elsif ($localCmdVal eq "trigger") {
  4511. $overCurrentShutdownReset = "trigger";
  4512. $overCurrentShutdownResetCmd = 1;
  4513. } else {
  4514. return "Usage: $cmd $localCmd <value> wrong, choose not_active trigger.";
  4515. }
  4516. } elsif ($localCmd eq "rampTime1"){
  4517. if ($localCmdVal >= 0 || $localCmdVal <= 7.5) {
  4518. $rampTime1 = $localCmdVal;
  4519. $rampTime1Cmd = $localCmdVal * 2;
  4520. } else {
  4521. return "Usage: $cmd $localCmd <value> wrong, choose 0, 0.5, ..., 7, 7.5";
  4522. }
  4523. } elsif ($localCmd eq "rampTime2"){
  4524. if ($localCmdVal >= 0 || $localCmdVal <= 7.5) {
  4525. $rampTime2 = $localCmdVal;
  4526. $rampTime2Cmd = $localCmdVal * 2;
  4527. } else {
  4528. return "Usage: $cmd $localCmd <value> wrong, choose 0, 0.5, ..., 7, 7.5";
  4529. }
  4530. } elsif ($localCmd eq "rampTime3"){
  4531. if ($localCmdVal >= 0 || $localCmdVal <= 7.5) {
  4532. $rampTime3 = $localCmdVal;
  4533. $rampTime3Cmd = $localCmdVal * 2;
  4534. } else {
  4535. return "Usage: $cmd $localCmd <value> wrong, choose 0, 0.5, ..., 7, 7.5";
  4536. }
  4537. } elsif ($localCmd eq "teachInDev"){
  4538. if ($localCmdVal eq "disabled") {
  4539. $teachInDev = "disabled";
  4540. $teachInDevCmd = 0;
  4541. } elsif ($localCmdVal eq "enabled") {
  4542. $teachInDev = "enabled";
  4543. $teachInDevCmd = 1;
  4544. } else {
  4545. return "Usage: $cmd $localCmd <value> wrong, choose disabled enabled.";
  4546. }
  4547. } else {
  4548. return "Usage: $cmd <localCmd> wrong, choose dayNight|defaultState|localControl|" .
  4549. "overCurrentShutdown|overCurrentShutdownReset|rampTime1|rampTime2|rampTime3|teachInDev.";
  4550. }
  4551. readingsSingleUpdate($hash, "dayNight", $dayNight, 1);
  4552. readingsSingleUpdate($hash, "defaultState", $defaultState, 1);
  4553. readingsSingleUpdate($hash, "localControl", $localControl, 1);
  4554. readingsSingleUpdate($hash, "overCurrentShutdown", $overCurrentShutdown, 1);
  4555. readingsSingleUpdate($hash, "overCurrentShutdownReset", $overCurrentShutdownReset, 1);
  4556. readingsSingleUpdate($hash, "rampTime1", $rampTime1, 1);
  4557. readingsSingleUpdate($hash, "rampTime2", $rampTime2, 1);
  4558. readingsSingleUpdate($hash, "rampTime3", $rampTime3, 1);
  4559. readingsSingleUpdate($hash, "teachInDev", $teachInDev, 1);
  4560. $data = sprintf "%02X%02X%02X%02X", $teachInDevCmd << 7 | $cmdID,
  4561. $overCurrentShutdownCmd << 7 | $overCurrentShutdownResetCmd << 6 | $localControlCmd << 5 | $channel,
  4562. int($rampTime2Cmd) << 4 | int($rampTime3Cmd),
  4563. $dayNightCmd << 7 | $defaultStateCmd << 4 | int($rampTime1Cmd);
  4564. } elsif ($cmd eq "measurement") {
  4565. shift(@a);
  4566. $updateState = 0;
  4567. $cmdID = 5;
  4568. # same configuration for all channels
  4569. $channel = 30;
  4570. my $measurementMode = ReadingsVal($name, "measurementMode", "energy");
  4571. my $measurementModeCmd = ($measurementMode eq "power")? 0:1;
  4572. my $measurementReport = ReadingsVal($name, "measurementReport", "query");
  4573. my $measurementReportCmd = ($measurementReport eq "auto")? 0:1;
  4574. my $measurementReset = "not_active";
  4575. my $measurementResetCmd = 0;
  4576. my $measurementDelta = int(ReadingsVal($name, "measurementDelta", 0));
  4577. if ($measurementDelta <= 0) {
  4578. $measurementDelta = 0;
  4579. } elsif ($measurementDelta >= 4095) {
  4580. $measurementDelta = 4095;
  4581. }
  4582. my $unit = ReadingsVal($name, "measurementUnit", "Ws");
  4583. my $unitCmd;
  4584. if ($unit eq "Ws") {
  4585. $unitCmd = 0;
  4586. } elsif ($unit eq "Wh") {
  4587. $unitCmd = 1;
  4588. } elsif ($unit eq "KWh") {
  4589. $unitCmd = 2;
  4590. } elsif ($unit eq "W") {
  4591. $unitCmd = 3;
  4592. } elsif ($unit eq "KW") {
  4593. $unitCmd = 4;
  4594. } else {
  4595. $unitCmd = 0;
  4596. }
  4597. my $responseTimeMax = ReadingsVal($name, "responseTimeMax", 10);
  4598. my $responseTimeMaxCmd = $responseTimeMax / 10;
  4599. if ($responseTimeMaxCmd <= 1) {
  4600. $responseTimeMaxCmd = 1;
  4601. } elsif ($responseTimeMaxCmd >= 255) {
  4602. $responseTimeMaxCmd = 255;
  4603. }
  4604. my $responseTimeMin = ReadingsVal($name, "responseTimeMin", 1);
  4605. if ($responseTimeMin <= 1) {
  4606. $responseTimeMin = 1;
  4607. } elsif ($responseTimeMin >= 255) {
  4608. $responseTimeMin = 255;
  4609. }
  4610. my $measurementCmd = shift(@a);
  4611. my $measurementCmdVal = shift(@a);
  4612. if (!defined $measurementCmdVal) {
  4613. return "Usage: $cmd $measurementCmd <value> needed.";
  4614. }
  4615. if (!defined $measurementCmd) {
  4616. return "Usage: $cmd <measurementCmd> wrong, choose mode|report|" .
  4617. "reset|delta|unit|responseTimeMax|responseTimeMin.";
  4618. } elsif ($measurementCmd eq "mode") {
  4619. if ($measurementCmdVal eq "energy") {
  4620. $measurementMode = "energy";
  4621. $measurementModeCmd = 0;
  4622. } elsif ($measurementCmdVal eq "power") {
  4623. $measurementMode = "power";
  4624. $measurementModeCmd = 1;
  4625. } else {
  4626. return "Usage: $cmd $measurementCmd <value> wrong, choose energy power.";
  4627. }
  4628. } elsif ($measurementCmd eq "report"){
  4629. if ($measurementCmdVal eq "query") {
  4630. $measurementReport = "query";
  4631. $measurementReportCmd = 0;
  4632. } elsif ($measurementCmdVal eq "auto") {
  4633. $measurementReport = "auto";
  4634. $measurementReportCmd = 1;
  4635. } else {
  4636. return "Usage: $cmd $measurementCmd <value> wrong, choose query auto.";
  4637. }
  4638. } elsif ($measurementCmd eq "reset"){
  4639. if ($measurementCmdVal eq "not_active") {
  4640. $measurementReset = "not_active";
  4641. $measurementResetCmd = 0;
  4642. } elsif ($measurementCmdVal eq "trigger") {
  4643. $measurementReset = "trigger";
  4644. $measurementResetCmd = 1;
  4645. } else {
  4646. return "Usage: $cmd $measurementCmd <value> wrong, choose not_active trigger.";
  4647. }
  4648. } elsif ($measurementCmd eq "unit"){
  4649. if ($measurementCmdVal eq "Ws") {
  4650. $unit = "Ws";
  4651. $unitCmd = 0;
  4652. } elsif ($measurementCmdVal eq "Wh") {
  4653. $unit = "Wh";
  4654. $unitCmd = 1;
  4655. } elsif ($measurementCmdVal eq "KWh") {
  4656. $unit = "KWh";
  4657. $unitCmd = 2;
  4658. } elsif ($measurementCmdVal eq "W") {
  4659. $unit = "W";
  4660. $unitCmd = 3;
  4661. } elsif ($measurementCmdVal eq "KW") {
  4662. $unit = "KW";
  4663. $unitCmd = 4;
  4664. } else {
  4665. return "Usage: $cmd $measurementCmd <value> wrong, choose Ws Wh KWh W KW.";
  4666. }
  4667. } elsif ($measurementCmd eq "delta"){
  4668. if ($measurementCmdVal >= 0 || $measurementCmdVal <= 4095) {
  4669. $measurementDelta = int($measurementCmdVal);
  4670. } else {
  4671. return "Usage: $cmd $measurementCmd <value> wrong, choose 0 ... 4095";
  4672. }
  4673. } elsif ($measurementCmd eq "responseTimeMax"){
  4674. if ($measurementCmdVal >= 10 || $measurementCmdVal <= 2550) {
  4675. $responseTimeMax = int($measurementCmdVal);
  4676. $responseTimeMaxCmd = int($measurementCmdVal) / 10;
  4677. } else {
  4678. return "Usage: $cmd $measurementCmd <value> wrong, choose 10 ... 2550";
  4679. }
  4680. } elsif ($measurementCmd eq "responseTimeMin"){
  4681. if ($measurementCmdVal >= 1 || $measurementCmdVal <= 255) {
  4682. $responseTimeMin = int($measurementCmdVal);
  4683. } else {
  4684. return "Usage: $cmd $measurementCmd <value> wrong, choose 1 ... 255";
  4685. }
  4686. } else {
  4687. return "Usage: $cmd <measurementCmd> wrong, choose mode|report|" .
  4688. "reset|delta|unit|responseTimeMax|responseTimeMin.";
  4689. }
  4690. readingsSingleUpdate($hash, "measurementMode", $measurementMode, 1);
  4691. readingsSingleUpdate($hash, "measurementReport", $measurementReport, 1);
  4692. readingsSingleUpdate($hash, "measurementReset", $measurementReset, 1);
  4693. readingsSingleUpdate($hash, "measurementDelta", $measurementDelta, 1);
  4694. readingsSingleUpdate($hash, "measurementUnit", $unit, 1);
  4695. readingsSingleUpdate($hash, "responseTimeMax", $responseTimeMax, 1);
  4696. readingsSingleUpdate($hash, "responseTimeMin", $responseTimeMin, 1);
  4697. $data = sprintf "%02X%02X%02X%02X%02X%02X", $cmdID,
  4698. $measurementReportCmd << 7 | $measurementResetCmd << 6 | $measurementModeCmd << 5 | $channel,
  4699. ($measurementDelta | 0x0F) << 4 | $unitCmd, ($measurementDelta | 0xFF00) >> 8,
  4700. $responseTimeMax, $responseTimeMin;
  4701. } elsif ($cmd eq "roomCtrlMode") {
  4702. shift(@a);
  4703. $updateState = 0;
  4704. $cmdID = 8;
  4705. my $roomCtrlModeCmd = shift(@a);
  4706. return "$cmd <channel> <roomCtrlMode> is missing, choose off|comfort|comfort-1|comfort-2|economy|buildingProtection." if (!defined $roomCtrlModeCmd);
  4707. if ($roomCtrlModeCmd eq "off") {
  4708. $roomCtrlModeCmd = 0;
  4709. } elsif ($roomCtrlModeCmd eq "comfort") {
  4710. $roomCtrlModeCmd = 1;
  4711. } elsif ($roomCtrlModeCmd eq "economy") {
  4712. $roomCtrlModeCmd = 2;
  4713. } elsif ($roomCtrlModeCmd eq "buildingProtection") {
  4714. $roomCtrlModeCmd = 3;
  4715. } elsif ($roomCtrlModeCmd eq "comfort-1") {
  4716. $roomCtrlModeCmd = 4;
  4717. } elsif ($roomCtrlModeCmd eq "comfort-2") {
  4718. $roomCtrlModeCmd = 5;
  4719. } else {
  4720. return "$cmd <channel> <roomCtrlMode> wrong, choose off|comfort|comfort-1|comfort-2|economy|buildingProtection.";
  4721. }
  4722. $data = sprintf "%02X%02X", $cmdID, $roomCtrlModeCmd;
  4723. } elsif ($cmd eq "autoOffTime") {
  4724. shift(@a);
  4725. $updateState = 0;
  4726. $cmdID = 0x0B;
  4727. $outputVal = int(shift(@a) * 10);
  4728. if (!defined $outputVal || $outputVal !~ m/^[+-]?\d+$/ || $outputVal < 0 || $outputVal > 65534) {
  4729. return "Usage: $cmd variable is not numeric or out of range.";
  4730. }
  4731. $channel = shift(@a);
  4732. $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
  4733. if (!defined $channel || $channel eq "all") {
  4734. CommandDeleteReading(undef, "$name autoOffTime.*");
  4735. $channel = 30;
  4736. } elsif ($channel eq "input" || $channel + 0 == 31) {
  4737. $channel = 31;
  4738. } elsif ($channel + 0 >= 30) {
  4739. CommandDeleteReading(undef, "$name autoOffTime.*");
  4740. $channel = 30;
  4741. } elsif ($channel >= 0 && $channel <= 29) {
  4742. } else {
  4743. return "$cmd $channel wrong, choose 0...31|all|input.";
  4744. }
  4745. my $extSwitchMode = ReadingsVal($name, "extSwitchMode", 'unavailable');
  4746. my %extSwitchMode = (
  4747. "unavailable" => 0,
  4748. "switch" => 1,
  4749. "pushbutton" => 2,
  4750. "auto" => 3
  4751. );
  4752. if (exists $extSwitchMode{$extSwitchMode}) {
  4753. $extSwitchMode = $extSwitchMode{$extSwitchMode};
  4754. } else {
  4755. $extSwitchMode = 0;
  4756. }
  4757. my $extSwitchType = ReadingsVal($name, "extSwitchType", 'toggle') eq 'direction' ? 1 : 0;
  4758. $data = sprintf "%02X%02X%04XFFFF%02X", $cmdID, $channel, $outputVal, $extSwitchMode << 6 | $extSwitchType << 5;
  4759. } elsif ($cmd eq "delayOffTime") {
  4760. shift(@a);
  4761. $updateState = 0;
  4762. $cmdID = 0x0B;
  4763. $outputVal = int(shift(@a) * 10);
  4764. if (!defined $outputVal || $outputVal !~ m/^[+-]?\d+$/ || $outputVal < 0 || $outputVal > 65534) {
  4765. return "Usage: $cmd variable is not numeric or out of range.";
  4766. }
  4767. $channel = shift(@a);
  4768. $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
  4769. if (!defined $channel || $channel eq "all") {
  4770. CommandDeleteReading(undef, "$name delayOffTime.*");
  4771. $channel = 30;
  4772. } elsif ($channel eq "input" || $channel + 0 == 31) {
  4773. $channel = 31;
  4774. } elsif ($channel + 0 >= 30) {
  4775. CommandDeleteReading(undef, "$name delayOffTime.*");
  4776. $channel = 30;
  4777. } elsif ($channel >= 0 && $channel <= 29) {
  4778. } else {
  4779. return "$cmd $channel wrong, choose 0...31|all|input.";
  4780. }
  4781. my $extSwitchMode = ReadingsVal($name, "extSwitchMode", 'unavailable');
  4782. my %extSwitchMode = (
  4783. "unavailable" => 0,
  4784. "switch" => 1,
  4785. "pushbutton" => 2,
  4786. "auto" => 3
  4787. );
  4788. if (exists $extSwitchMode{$extSwitchMode}) {
  4789. $extSwitchMode = $extSwitchMode{$extSwitchMode};
  4790. } else {
  4791. $extSwitchMode = 0;
  4792. }
  4793. my $extSwitchType = ReadingsVal($name, "extSwitchType", 'toggle') eq 'direction' ? 1 : 0;
  4794. $data = sprintf "%02X%02XFFFF%04X%02X", $cmdID, $channel, $outputVal, $extSwitchMode << 6 | $extSwitchType << 5;
  4795. } elsif ($cmd eq "extSwitchMode") {
  4796. shift(@a);
  4797. $updateState = 0;
  4798. $cmdID = 0x0B;
  4799. my $extSwitchMode = shift(@a);
  4800. return "Usage: $cmd variable is missing, choose unavailable|switch|pushbutton|auto." if (!defined $extSwitchMode);
  4801. my %extSwitchMode = (
  4802. "unavailable" => 0,
  4803. "switch" => 1,
  4804. "pushbutton" => 2,
  4805. "auto" => 3
  4806. );
  4807. if (exists $extSwitchMode{$extSwitchMode}) {
  4808. $extSwitchMode = $extSwitchMode{$extSwitchMode};
  4809. } else {
  4810. return "Usage: $cmd variable wrong, choose unavailable|switch|pushbutton|auto.";
  4811. }
  4812. $channel = shift(@a);
  4813. $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
  4814. if (!defined $channel || $channel eq "all") {
  4815. CommandDeleteReading(undef, "$name extSwitchMode.*");
  4816. $channel = 30;
  4817. } elsif ($channel eq "input" || $channel + 0 == 31) {
  4818. $channel = 31;
  4819. } elsif ($channel + 0 >= 30) {
  4820. CommandDeleteReading(undef, "$name extSwitchMode.*");
  4821. $channel = 30;
  4822. } elsif ($channel >= 0 && $channel <= 29) {
  4823. } else {
  4824. return "$cmd $channel wrong, choose 0...31|all|input.";
  4825. }
  4826. my $extSwitchType = ReadingsVal($name, "extSwitchType", 'toggle') eq 'direction' ? 1 : 0;
  4827. $data = sprintf "%02X%02XFFFFFFFF%02X", $cmdID, $channel, $extSwitchMode << 6 | $extSwitchType << 5;
  4828. } elsif ($cmd eq "extSwitchType") {
  4829. shift(@a);
  4830. $updateState = 0;
  4831. $cmdID = 0x0B;
  4832. my $extSwitchType = shift(@a);
  4833. return "Usage: $cmd variable is missing, choose toggle|direction." if (!defined $extSwitchType);
  4834. my %extSwitchType = (
  4835. "toggle" => 0,
  4836. "direction" => 1,
  4837. );
  4838. if (exists $extSwitchType{$extSwitchType}) {
  4839. $extSwitchType = $extSwitchType{$extSwitchType};
  4840. } else {
  4841. return "Usage: $cmd variable wrong, choose toggle|direction.";
  4842. }
  4843. $channel = shift(@a);
  4844. $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
  4845. if (!defined $channel || $channel eq "all") {
  4846. CommandDeleteReading(undef, "$name extSwitchMode.*");
  4847. $channel = 30;
  4848. } elsif ($channel eq "input" || $channel + 0 == 31) {
  4849. $channel = 31;
  4850. } elsif ($channel + 0 >= 30) {
  4851. CommandDeleteReading(undef, "$name extSwitchMode.*");
  4852. $channel = 30;
  4853. } elsif ($channel >= 0 && $channel <= 29) {
  4854. } else {
  4855. return "$cmd $channel wrong, choose 0...31|all|input.";
  4856. }
  4857. my $extSwitchMode = ReadingsVal($name, "extSwitchMode", 'unavailable');
  4858. my %extSwitchMode = (
  4859. "unavailable" => 0,
  4860. "switch" => 1,
  4861. "pushbutton" => 2,
  4862. "auto" => 3
  4863. );
  4864. if (exists $extSwitchMode{$extSwitchMode}) {
  4865. $extSwitchMode = $extSwitchMode{$extSwitchMode};
  4866. } else {
  4867. $extSwitchMode = 0;
  4868. }
  4869. $data = sprintf "%02X%02XFFFFFFFF%02X", $cmdID, $channel, $extSwitchMode << 6 | $extSwitchType << 5;
  4870. } elsif ($cmd eq "special") {
  4871. $rorg = "D1";
  4872. shift(@a);
  4873. $updateState = 0;
  4874. my $repeaterActive = 0;
  4875. my $repeaterLevel = 0;
  4876. my $specialCmd = shift(@a);
  4877. if ($manufID eq "046") {
  4878. if (!defined $specialCmd) {
  4879. return "$cmd <command> wrong, choose repeaterLevel.";
  4880. } elsif ($specialCmd eq "repeaterLevel") {
  4881. $cmdID = 8;
  4882. my $repeaterLevel = shift(@a);
  4883. if (defined $repeaterLevel && $repeaterLevel =~ m/^off|1|2$/) {
  4884. if ($repeaterLevel eq "off") {
  4885. $repeaterLevel = 0;
  4886. } else {
  4887. $repeaterActive = 1;
  4888. }
  4889. } else {
  4890. return "Usage: repeaterLevel is wrong";
  4891. }
  4892. } else {
  4893. return "$cmd $specialCmd <arg> wrong, choose repeaterLevel off|1|2.";
  4894. }
  4895. }
  4896. $data = sprintf "0046%02X%02X%02X", $cmdID, $repeaterActive, $repeaterLevel;
  4897. } else {
  4898. if ($manufID =~ m/^046$/) {
  4899. $cmdList .= "dim:slider,0,1,100 on off autoOffTime delayOffTime extSwitchMode extSwitchType local measurement roomCtrlMode:off,buildingProtection,economy,comfort-2,comfort-1,comfort special";
  4900. } else {
  4901. $cmdList .= "dim:slider,0,1,100 on off autoOffTime delayOffTime extSwitchMode extSwitchType local measurement roomCtrlMode:off,buildingProtection,economy,comfort-2,comfort-1,comfort";
  4902. }
  4903. return SetExtensions ($hash, $cmdList, $name, @a);
  4904. }
  4905. Log3 $name, 3, "EnOcean set $name $cmd";
  4906. } elsif ($st =~ m/^blindsCtrl\.0[01]$/) {
  4907. # Blinds Control for Position and Angle
  4908. # (D2-05-xx)
  4909. $rorg = "D2";
  4910. $updateState = 0;
  4911. my $cmdID;
  4912. my $channel = AttrVal($name, "defaultChannel", 'all');
  4913. my $position = 127;
  4914. my $angle = 127;
  4915. my $repo = AttrVal($name, "reposition", "directly");
  4916. if ($repo eq "directly") {
  4917. $repo = 0;
  4918. } elsif ($repo eq "opens") {
  4919. $repo = 1;
  4920. } elsif ($repo eq "closes") {
  4921. $repo = 2;
  4922. } else {
  4923. $repo = 0;
  4924. }
  4925. my $lock = 0;
  4926. if ($cmd =~ m/^\d+$/) {
  4927. # interpretive numeric value as position
  4928. unshift(@a, 'position');
  4929. $cmd = 'position';
  4930. }
  4931. if ($cmd eq "position") {
  4932. $cmdID = 1;
  4933. shift(@a);
  4934. if (ReadingsVal($name, "block", "unlock") ne "unlock") {
  4935. return "Attention: Device locked";
  4936. }
  4937. if (defined $a[0]) {
  4938. # position value
  4939. if (($a[0] =~ m/^\d+$/) && ($a[0] >= 0) && ($a[0] <= 100)) {
  4940. $position = shift(@a);
  4941. if (defined $a[0]) {
  4942. # angle value
  4943. if (($a[0] =~ m/^\d+$/) && ($a[0] >= 0) && ($a[0] <= 100)) {
  4944. $angle = shift(@a);
  4945. if (defined $a[0]) {
  4946. # channel
  4947. $channel = shift(@a);
  4948. if ($channel =~ m/^all$/) {
  4949. $channel = 15;
  4950. } elsif ($channel =~ m/^[1234]$/) {
  4951. $channel -= 1;
  4952. } else {
  4953. return "Usage: $position $angle $channel argument unknown, choose one of 1|2|3|4|all";
  4954. }
  4955. } else {
  4956. $channel = 15;
  4957. }
  4958. if (defined $a[0]) {
  4959. # reposition value
  4960. $repo = shift(@a);
  4961. if ($repo eq "directly") {
  4962. $repo = 0;
  4963. } elsif ($repo eq "opens") {
  4964. $repo = 1;
  4965. } elsif ($repo eq "closes") {
  4966. $repo = 2;
  4967. } else {
  4968. return "Usage: $position $angle $channel $repo argument unknown, choose one of directly opens closes";
  4969. }
  4970. }
  4971. readingsSingleUpdate($hash, "anglePos", $angle, 1);
  4972. } else {
  4973. return "Usage: $position $a[0] is not numeric or out of range";
  4974. }
  4975. } else {
  4976. $channel = 15;
  4977. }
  4978. readingsSingleUpdate($hash, "state", "in_motion", 1);
  4979. } else {
  4980. return "Usage: $a[0] is not numeric or out of range";
  4981. }
  4982. } else {
  4983. return "Usage: set <name> position <position> [<angle> [<repo>]]";
  4984. }
  4985. $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
  4986. } elsif ($cmd eq "anglePos") {
  4987. $cmdID = 1;
  4988. shift(@a);
  4989. if (ReadingsVal($name, "block", "unlock") ne "unlock") {
  4990. return "Attention: Device locked";
  4991. }
  4992. if (defined $a[0]) {
  4993. if (($a[0] =~ m/^\d+$/) && ($a[0] >= 0) && ($a[0] <= 100)) {
  4994. $angle = shift(@a);
  4995. if (defined $a[0]) {
  4996. # channel
  4997. $channel = shift(@a);
  4998. if ($channel =~ m/^all$/) {
  4999. $channel = 15;
  5000. } elsif ($channel =~ m/^[1234]$/) {
  5001. $channel -= 1;
  5002. } else {
  5003. return "Usage: $angle $channel argument unknown, choose one of 1|2|3|4|all";
  5004. }
  5005. } else {
  5006. $channel = 15;
  5007. }
  5008. readingsSingleUpdate($hash, "state", "in_motion", 1);
  5009. } else {
  5010. return "Usage: $a[0] is not numeric or out of range";
  5011. }
  5012. } else {
  5013. return "Usage: set <name> anglePos <angle>";
  5014. }
  5015. $repo = 0;
  5016. $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
  5017. } elsif ($cmd eq "stop") {
  5018. $cmdID = 2;
  5019. shift(@a);
  5020. if (defined $a[0]) {
  5021. # channel
  5022. $channel = shift(@a);
  5023. if ($channel =~ m/^all$/) {
  5024. $channel = 15;
  5025. } elsif ($channel =~ m/^[1234]$/) {
  5026. $channel -= 1;
  5027. } else {
  5028. return "Usage: stop $channel argument unknown, choose one of 1|2|3|4|all";
  5029. }
  5030. } else {
  5031. $channel = 15;
  5032. }
  5033. readingsSingleUpdate($hash, "state", "stopped", 1);
  5034. $data = sprintf "%02X", $channel << 4 | $cmdID;
  5035. } elsif ($cmd eq "opens") {
  5036. $cmdID = 1;
  5037. shift(@a);
  5038. if (ReadingsVal($name, "block", "unlock") ne "unlock") {
  5039. return "Attention: Device locked";
  5040. }
  5041. if (defined $a[0]) {
  5042. # channel
  5043. $channel = shift(@a);
  5044. if ($channel =~ m/^all$/) {
  5045. $channel = 15;
  5046. } elsif ($channel =~ m/^[1234]$/) {
  5047. $channel -= 1;
  5048. } else {
  5049. return "Usage: opens $channel argument unknown, choose one of 1|2|3|4|all";
  5050. }
  5051. } else {
  5052. $channel = 15;
  5053. }
  5054. $position = 0;
  5055. $repo = 0;
  5056. $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
  5057. } elsif ($cmd eq "closes") {
  5058. $cmdID = 1;
  5059. shift(@a);
  5060. if (ReadingsVal($name, "block", "unlock") ne "unlock") {
  5061. return "Attention: Device locked";
  5062. }
  5063. if (defined $a[0]) {
  5064. # channel
  5065. $channel = shift(@a);
  5066. if ($channel =~ m/^all$/) {
  5067. $channel = 15;
  5068. } elsif ($channel =~ m/^[1234]$/) {
  5069. $channel -= 1;
  5070. } else {
  5071. return "Usage: closes $channel argument unknown, choose one of 1|2|3|4|all";
  5072. }
  5073. } else {
  5074. $channel = 15;
  5075. }
  5076. $position = 100;
  5077. $repo = 0;
  5078. $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
  5079. } elsif ($cmd eq "unlock") {
  5080. $cmdID = 1;
  5081. shift(@a);
  5082. if (defined $a[0]) {
  5083. # channel
  5084. $channel = shift(@a);
  5085. if ($channel =~ m/^all$/) {
  5086. $channel = 15;
  5087. } elsif ($channel =~ m/^[1234]$/) {
  5088. $channel -= 1;
  5089. } else {
  5090. return "Usage: unlock $channel argument unknown, choose one of 1|2|3|4|all";
  5091. }
  5092. } else {
  5093. $channel = 15;
  5094. }
  5095. $repo = 0;
  5096. $lock = 7;
  5097. $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
  5098. } elsif ($cmd eq "lock") {
  5099. $cmdID = 1;
  5100. shift(@a);
  5101. if (defined $a[0]) {
  5102. # channel
  5103. $channel = shift(@a);
  5104. if ($channel =~ m/^all$/) {
  5105. $channel = 15;
  5106. } elsif ($channel =~ m/^[1234]$/) {
  5107. $channel -= 1;
  5108. } else {
  5109. return "Usage: lock $channel argument unknown, choose one of 1|2|3|4|all";
  5110. }
  5111. } else {
  5112. $channel = 15;
  5113. }
  5114. $repo = 0;
  5115. $lock = 1;
  5116. $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
  5117. } elsif ($cmd eq "alarm") {
  5118. $cmdID = 1;
  5119. shift(@a);
  5120. if (defined $a[0]) {
  5121. # channel
  5122. $channel = shift(@a);
  5123. if ($channel =~ m/^all$/) {
  5124. $channel = 15;
  5125. } elsif ($channel =~ m/^[1234]$/) {
  5126. $channel -= 1;
  5127. } else {
  5128. return "Usage: alarm $channel argument unknown, choose one of 1|2|3|4|all";
  5129. }
  5130. } else {
  5131. $channel = 15;
  5132. }
  5133. $repo = 0;
  5134. $lock = 2;
  5135. $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
  5136. } else {
  5137. $cmdList .= "position:slider,0,1,100 anglePos:slider,0,1,100 stop opens closes lock unlock alarm";
  5138. return "Unknown argument $cmd, choose one of $cmdList";
  5139. }
  5140. Log3 $name, 3, "EnOcean set $name $cmd";
  5141. } elsif ($st eq "multisensor.01") {
  5142. #####
  5143. # Multisensor Windows Handle
  5144. # (D2-06-01)
  5145. $rorg = "D2";
  5146. $updateState = 2;
  5147. my $waitingCmds = ReadingsVal($name, "waitingCmds", undef);
  5148. if (defined $waitingCmds) {
  5149. # check presence state
  5150. $waitingCmds = ReadingsVal($name, "presence", "present") eq "absent" ? $waitingCmds & 0xDF | 32 : $waitingCmds & 0xDF;
  5151. } else {
  5152. $waitingCmds = ReadingsVal($name, "presence", "present") eq "absent" ? 32 : 0;
  5153. }
  5154. if ($cmd eq "presence") {
  5155. # set presence
  5156. if (defined $a[1]) {
  5157. if ($a[1] =~ m/^absent$/) {
  5158. readingsSingleUpdate($hash, "presence", $a[1], 1);
  5159. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5160. shift(@a);
  5161. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xDF | 32, 0);
  5162. } elsif ($a[1] =~ m/^present$/) {
  5163. readingsSingleUpdate($hash, "presence", $a[1], 1);
  5164. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5165. shift(@a);
  5166. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xDF, 0);
  5167. } else {
  5168. return "Usage: $a[1] is not numeric or out of range";
  5169. }
  5170. }
  5171. } elsif ($cmd eq "handleClosedClick") {
  5172. # set battery closed click
  5173. if (defined $a[1]) {
  5174. if ($a[1] =~ m/^disable$/) {
  5175. readingsSingleUpdate($hash, "handleClosedClick", $a[1] . 'd', 1);
  5176. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5177. shift(@a);
  5178. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xE7 | 8, 0);
  5179. } elsif ($a[1] =~ m/^enable$/) {
  5180. readingsSingleUpdate($hash, "handleClosedClick", $a[1] . 'd', 1);
  5181. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5182. shift(@a);
  5183. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xE7 | 16, 0);
  5184. } else {
  5185. return "Usage: $a[1] is not numeric or out of range";
  5186. }
  5187. }
  5188. } elsif ($cmd eq "batteryLowClick") {
  5189. # set battery click low
  5190. if (defined $a[1]) {
  5191. if ($a[1] =~ m/^disable$/) {
  5192. readingsSingleUpdate($hash, "batteryLowClick", $a[1] . 'd', 1);
  5193. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5194. shift(@a);
  5195. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xF9 | 2, 0);
  5196. } elsif ($a[1] =~ m/^enable$/) {
  5197. readingsSingleUpdate($hash, "batteryLowClick", $a[1] . 'd', 1);
  5198. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5199. shift(@a);
  5200. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xF9 | 4, 0);
  5201. } else {
  5202. return "Usage: $a[1] is not numeric or out of range";
  5203. }
  5204. }
  5205. } elsif ($cmd eq "updateInterval") {
  5206. # set update interval
  5207. if (defined $a[1]) {
  5208. if ($a[1] =~ m/^\d+$/ && $a[1] >= 5 && $a[1] <= 65535) {
  5209. readingsSingleUpdate($hash, "updateInterval", $a[1], 1);
  5210. readingsSingleUpdate($hash, "updateIntervalSet", $a[1], 0);
  5211. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5212. shift(@a);
  5213. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds | 1, 0);
  5214. } else {
  5215. return "Usage: $a[1] is not numeric or out of range";
  5216. }
  5217. }
  5218. } elsif ($cmd eq "blinkInterval") {
  5219. # set blick interval
  5220. if (defined $a[1]) {
  5221. if ($a[1] =~ m/^\d+$/ && $a[1] >= 3 && $a[1] <= 255) {
  5222. readingsSingleUpdate($hash, "blinkInterval", $a[1], 1);
  5223. readingsSingleUpdate($hash, "blinkIntervalSet", $a[1], 0);
  5224. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5225. shift(@a);
  5226. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds | 1, 0);
  5227. } else {
  5228. return "Usage: $a[1] is not numeric or out of range";
  5229. }
  5230. }
  5231. } elsif ($cmd eq "teachSlave") {
  5232. # teach slave
  5233. $updateState = 0;
  5234. $destinationID = "FFFFFFFF";
  5235. if (defined $a[1]) {
  5236. if ($a[1] =~ m/^contact$/) {
  5237. $rorg = "D5";
  5238. $data = '00';
  5239. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDefW", "biDir");
  5240. readingsSingleUpdate($hash, "teachSlave", '1BS teach-in sent', 1);
  5241. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5242. shift(@a);
  5243. } elsif ($a[1] =~ m/^windowHandleOpen$/) {
  5244. $rorg = "F6";
  5245. $data = 'E0';
  5246. $status = '20';
  5247. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDefH", "biDir");
  5248. readingsSingleUpdate($hash, "teachSlave", 'RPS teach-in sent', 1);
  5249. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5250. shift(@a);
  5251. } elsif ($a[1] =~ m/^windowHandleClosed$/) {
  5252. $rorg = "F6";
  5253. $data = 'F0';
  5254. $status = '20';
  5255. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDefH", "biDir");
  5256. readingsSingleUpdate($hash, "teachSlave", 'RPS teach-in sent', 1);
  5257. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5258. shift(@a);
  5259. } elsif ($a[1] =~ m/^windowHandleTilted$/) {
  5260. $rorg = "F6";
  5261. $data = 'D0';
  5262. $status = '20';
  5263. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDefH", "biDir");
  5264. readingsSingleUpdate($hash, "teachSlave", 'RPS teach-in sent', 1);
  5265. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5266. shift(@a);
  5267. } else {
  5268. return "Usage: $a[1] is wrong";
  5269. }
  5270. }
  5271. } else {
  5272. $cmdList .= "presence:absent,present handleClosedClick:enable,disable batteryLowClick:enable,disable " .
  5273. "updateInterval blinkInterval teachSlave:contact,windowHandleClosed,windowHandleOpen,windowHandleTilted";
  5274. return "Unknown argument $cmd, choose one of $cmdList";
  5275. }
  5276. } elsif ($st eq "roomCtrlPanel.00") {
  5277. # Room Control Panel
  5278. # (D2-10-00 - D2-10-02)
  5279. $rorg = "D2";
  5280. $updateState = 2;
  5281. if ($cmd eq "desired-temp"|| $cmd eq "setpointTemp") {
  5282. if (defined $a[1]) {
  5283. if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 40)) {
  5284. $a[1] = sprintf "%0.1f", $a[1];
  5285. readingsSingleUpdate($hash, "setpointTemp", $a[1], 1);
  5286. readingsSingleUpdate($hash, "setpointTempSet", $a[1], 0);
  5287. Log3 $name, 3, "EnOcean set $name setpointTemp $a[1]";
  5288. shift(@a);
  5289. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
  5290. } else {
  5291. return "Usage: $a[1] is not numeric or out of range";
  5292. }
  5293. }
  5294. } elsif ($cmd eq "economyTemp" || $cmd eq "preComfortTemp" || $cmd eq "buildingProtectionTemp" || $cmd eq "comfortTemp") {
  5295. if (defined $a[1]) {
  5296. if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 40)) {
  5297. Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
  5298. $cmd =~ s/(\b)([a-z])/$1\u$2/g;
  5299. $a[1] = sprintf "%0.1f", $a[1];
  5300. readingsSingleUpdate($hash, "setpoint" . $cmd, $a[1], 1);
  5301. readingsSingleUpdate($hash, "setpoint" . $cmd . "Set", $a[1], 0);
  5302. shift(@a);
  5303. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 8, 0);
  5304. } else {
  5305. return "Usage: $a[1] is not numeric or out of range";
  5306. }
  5307. }
  5308. } elsif ($cmd eq "fanSpeed") {
  5309. if (defined $a[1]) {
  5310. if ($a[1] >= 0 && $a[1] <= 100) {
  5311. readingsSingleUpdate($hash, "fanSpeed", $a[1], 1);
  5312. readingsSingleUpdate($hash, "fanSpeedSet", $a[1], 0);
  5313. Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]";
  5314. shift(@a);
  5315. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
  5316. } else {
  5317. return "Usage: $a[1] is wrong.";
  5318. }
  5319. }
  5320. } elsif ($cmd eq "fanSpeedMode") {
  5321. if (defined $a[1]) {
  5322. if ($a[1] =~ m/^(central|local)$/) {
  5323. readingsSingleUpdate($hash, "fanSpeedMode", $a[1], 1);
  5324. readingsSingleUpdate($hash, "fanSpeedModeSet", $a[1], 0);
  5325. Log3 $name, 3, "EnOcean set $name fanSpeedMode $a[1]";
  5326. shift(@a);
  5327. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
  5328. } else {
  5329. return "Usage: $a[1] is wrong.";
  5330. }
  5331. }
  5332. } elsif ($cmd eq "cooling") {
  5333. if (defined $a[1]) {
  5334. if ($a[1] =~ m/^(on|off|auto|no_change)$/) {
  5335. readingsSingleUpdate($hash, "cooling", $a[1], 1);
  5336. readingsSingleUpdate($hash, "coolingSet", $a[1], 0);
  5337. Log3 $name, 3, "EnOcean set $name cooling $a[1]";
  5338. shift(@a);
  5339. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
  5340. } else {
  5341. return "Usage: $a[1] is wrong.";
  5342. }
  5343. }
  5344. } elsif ($cmd eq "heating") {
  5345. if (defined $a[1]) {
  5346. if ($a[1] =~ m/^(on|off|auto|no_change)$/) {
  5347. readingsSingleUpdate($hash, "heating", $a[1], 1);
  5348. readingsSingleUpdate($hash, "heatingSet", $a[1], 0);
  5349. Log3 $name, 3, "EnOcean set $name heating $a[1]";
  5350. shift(@a);
  5351. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
  5352. } else {
  5353. return "Usage: $a[1] is wrong.";
  5354. }
  5355. }
  5356. } elsif ($cmd eq "roomCtrlMode") {
  5357. if (defined $a[1]) {
  5358. if ($a[1] =~ m/^(comfort|preComfort|economy|buildingProtection)$/) {
  5359. readingsSingleUpdate($hash, "roomCtrlMode", $a[1], 1);
  5360. readingsSingleUpdate($hash, "roomCtrlModeSet", $a[1], 0);
  5361. Log3 $name, 3, "EnOcean set $name roomCtrlMode $a[1]";
  5362. shift(@a);
  5363. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
  5364. } else {
  5365. return "Usage: $a[1] is wrong.";
  5366. }
  5367. }
  5368. } elsif ($cmd eq "config") {
  5369. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 64, 0);
  5370. Log3 $name, 3, "EnOcean set $name $cmd";
  5371. } elsif ($cmd eq "timeProgram") {
  5372. # delete remote and send new time program
  5373. delete $hash->{helper}{4}{telegramWait};
  5374. for (my $messagePartCntr = 1; $messagePartCntr <= 4; $messagePartCntr ++) {
  5375. if (defined AttrVal($name, "timeProgram" . $messagePartCntr, undef)) {
  5376. $hash->{helper}{4}{telegramWait}{$messagePartCntr} = 1;
  5377. Log3 $name, 3, "EnOcean $name EnOcean_Set timeProgram" . $messagePartCntr . " set";
  5378. }
  5379. }
  5380. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 528, 0);
  5381. Log3 $name, 3, "EnOcean set $name $cmd";
  5382. } elsif ($cmd eq "deleteTimeProgram") {
  5383. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 512, 0);
  5384. Log3 $name, 3, "EnOcean set $name $cmd";
  5385. } elsif ($cmd eq "time") {
  5386. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 4, 0);
  5387. Log3 $name, 3, "EnOcean set $name $cmd";
  5388. } elsif ($cmd eq "window") {
  5389. if (defined $a[1]) {
  5390. if ($a[1] =~ m/^closed|open$/) {
  5391. readingsSingleUpdate($hash, "window", $a[1], 1);
  5392. readingsSingleUpdate($hash, "windowSet", $a[1], 0);
  5393. Log3 $name, 3, "EnOcean set $name window $a[1]";
  5394. shift(@a);
  5395. readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
  5396. } else {
  5397. return "Usage: $a[1] is wrong.";
  5398. }
  5399. }
  5400. } elsif ($cmd eq "clearCmds") {
  5401. CommandDeleteReading(undef, "$name waitingCmds");
  5402. Log3 $name, 3, "EnOcean set $name $cmd";
  5403. } else {
  5404. $cmdList .= "cooling:auto,off,on,no_change desired-temp setpointTemp:slider,0,1,40 " .
  5405. "comfortTemp deleteTimeProgram:noArg " .
  5406. "economyTemp preComfortTemp buildingProtectionTemp config:noArg " .
  5407. "clearCmds:noArg fanSpeed:slider,0,1,100 heating:auto,off,on,no_change " .
  5408. "fanSpeedMode:central,local time:noArg " .
  5409. "roomCtrlMode:comfort,economy,preComfort,buildingProtection timeProgram:noArg window:closed,open";
  5410. return "Unknown argument $cmd, choose one of $cmdList";
  5411. }
  5412. } elsif ($st eq "roomCtrlPanel.01") {
  5413. # Room Control Panel
  5414. # (D2-11-01 - D2-11-08)
  5415. $rorg = "D2";
  5416. $updateState = 0;
  5417. my $cooling = ReadingsVal($name, "colling", 'off');
  5418. my $fanSpeed = ReadingsVal($name, "fanSpeed", 'auto');
  5419. my $heating = ReadingsVal($name, "heating", 'off');
  5420. my $humidity = ReadingsVal($name, "humidity", 0);
  5421. my $occupancy = ReadingsVal($name, "occupancy", 'unoccupied');
  5422. my $setpointBase = ReadingsVal($name, "setpointBase", 20);
  5423. my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
  5424. my $setpointShift = ReadingsVal($name, "setpointShift", 0);
  5425. my $setpointShiftMax = ReadingsVal($name, "setpointShiftMax", 10);
  5426. my $setpointType = ReadingsVal($name, "setpointType", 'setpointShift');
  5427. my $temperature = ReadingsVal($name, "temperature", 20);
  5428. my $waitingCmds = ReadingsVal($name, "waitingCmds", 0);
  5429. my $window = ReadingsVal($name, "window", 'closed');
  5430. if ($cmd eq "desired-temp"|| $cmd eq "setpointTemp") {
  5431. if (defined $a[1]) {
  5432. if ($a[1] =~ m/^\d+$/ && $a[1] >= 5 && $a[1] <= 40) {
  5433. readingsBeginUpdate($hash);
  5434. if ($a[1] < 15) {
  5435. $setpointBase = 15;
  5436. $setpointShift = $a[1] - $setpointBase;
  5437. $setpointShiftMax = $setpointBase - $a[1] if ($setpointShiftMax < -$setpointShift);
  5438. readingsBulkUpdate($hash, "setpointShiftMax", $setpointShiftMax);
  5439. } elsif ($a[1] > 30) {
  5440. $setpointBase = 30;
  5441. $setpointShift = $a[1] - $setpointBase;
  5442. $setpointShiftMax = $a[1] - $setpointBase if ($setpointShiftMax < $setpointShift);
  5443. readingsBulkUpdate($hash, "setpointShiftMax", $setpointShiftMax);
  5444. } else {
  5445. $setpointBase = $a[1];
  5446. $setpointShift = 0;
  5447. }
  5448. readingsBulkUpdate($hash, "setpointShift", $setpointShift);
  5449. readingsBulkUpdate($hash, "setpointBase", $setpointBase);
  5450. $setpointTemp = $a[1];
  5451. readingsBulkUpdate($hash, "setpointTemp", $a[1]);
  5452. readingsBulkUpdate($hash, "state", "T: $temperature H: $humidity SPT: $setpointTemp F: $fanSpeed");
  5453. readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 1);
  5454. readingsEndUpdate($hash, 1);
  5455. CommandDeleteReading(undef, "$name smartAckMailbox");
  5456. Log3 $name, 3, "EnOcean set $name setpointTemp $a[1]";
  5457. shift(@a);
  5458. } else {
  5459. return "Usage: $a[1] is not numeric or out of range";
  5460. }
  5461. } else {
  5462. return "Usage: set <name> setpointTemp 5...40";
  5463. }
  5464. } elsif ($cmd eq "setpointShiftMax") {
  5465. if (defined $a[1]) {
  5466. if ($a[1] =~ m/^\d+$/ && $a[1] >= 1 && $a[1] <= 10) {
  5467. readingsBeginUpdate($hash);
  5468. if ($setpointTemp < 15 - $a[1]) {
  5469. $setpointShiftMax = 15 - $setpointTemp;
  5470. } elsif ($setpointTemp > 30 + $a[1]) {
  5471. $setpointShiftMax = $setpointTemp - 30;
  5472. } else {
  5473. $setpointShiftMax = $a[1];
  5474. }
  5475. readingsBulkUpdate($hash, "setpointShiftMax", $setpointShiftMax);
  5476. readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 2);
  5477. readingsEndUpdate($hash, 1);
  5478. CommandDeleteReading(undef, "$name smartAckMailbox");
  5479. Log3 $name, 3, "EnOcean set $name setpointShiftMax $a[1]";
  5480. shift(@a);
  5481. } else {
  5482. return "Usage: $a[1] is not numeric or out of range";
  5483. }
  5484. } else {
  5485. return "Usage: set <name> setpointShiftMax 1...10";
  5486. }
  5487. } elsif ($cmd eq "setpointType") {
  5488. if (defined $a[1]) {
  5489. if ($a[1] =~ m/^setpointTemp|setpointShift$/) {
  5490. readingsBeginUpdate($hash);
  5491. $setpointType = $a[1];
  5492. readingsBulkUpdate($hash, "setpointType", $setpointType);
  5493. readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 0x80);
  5494. readingsEndUpdate($hash, 1);
  5495. CommandDeleteReading(undef, "$name smartAckMailbox");
  5496. Log3 $name, 3, "EnOcean set $name setpointType $a[1]";
  5497. shift(@a);
  5498. } else {
  5499. return "Usage: $a[1] is wrong.";
  5500. }
  5501. } else {
  5502. return "Usage: set <name> setpointType setpointTemp|setpointShift";
  5503. }
  5504. } elsif ($cmd eq 'fanSpeed') {
  5505. if (defined $a[1]) {
  5506. if ($a[1] =~ m/^auto|off|1|2|3$/) {
  5507. $fanSpeed = $a[1];
  5508. readingsBeginUpdate($hash);
  5509. readingsBulkUpdate($hash, "fanSpeed", $a[1]);
  5510. readingsBulkUpdate($hash, "state", "T: $temperature H: $humidity SPT: $setpointTemp F: $fanSpeed");
  5511. readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 4);
  5512. readingsEndUpdate($hash, 1);
  5513. CommandDeleteReading(undef, "$name smartAckMailbox");
  5514. Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]";
  5515. shift(@a);
  5516. } else {
  5517. return "Usage: $a[1] is wrong.";
  5518. }
  5519. } else {
  5520. return "Usage: set <name> fanspeed 1...3|auto|off";
  5521. }
  5522. } elsif ($cmd eq "cooling") {
  5523. if (defined $a[1]) {
  5524. if ($a[1] =~ m/^on|off$/) {
  5525. $cooling = $a[1];
  5526. readingsBeginUpdate($hash);
  5527. readingsBulkUpdate($hash, "cooling", $a[1]);
  5528. if ($a[1] eq 'on') {
  5529. $heating = 'off';
  5530. readingsBulkUpdate($hash, "heating", 'off');
  5531. }
  5532. readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 0x20);
  5533. readingsEndUpdate($hash, 1);
  5534. CommandDeleteReading(undef, "$name smartAckMailbox");
  5535. Log3 $name, 3, "EnOcean set $name cooling $a[1]";
  5536. shift(@a);
  5537. } else {
  5538. return "Usage: $a[1] is wrong.";
  5539. }
  5540. } else {
  5541. return "Usage: set <name> cooling on|off";
  5542. }
  5543. } elsif ($cmd eq "heating") {
  5544. if (defined $a[1]) {
  5545. if ($a[1] =~ m/^on|off$/) {
  5546. $heating = $a[1];
  5547. readingsBeginUpdate($hash);
  5548. readingsBulkUpdate($hash, "heating", $a[1]);
  5549. if ($a[1] eq 'on') {
  5550. $cooling = 'off';
  5551. readingsBulkUpdate($hash, "cooling", 'off');
  5552. }
  5553. readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 0x40);
  5554. readingsEndUpdate($hash, 1);
  5555. CommandDeleteReading(undef, "$name smartAckMailbox");
  5556. Log3 $name, 3, "EnOcean set $name heating $a[1]";
  5557. shift(@a);
  5558. } else {
  5559. return "Usage: $a[1] is wrong.";
  5560. }
  5561. } else {
  5562. return "Usage: set <name> heating on|off";
  5563. }
  5564. } elsif ($cmd eq "occupancy") {
  5565. if (defined $a[1]) {
  5566. if ($a[1] =~ m/^occupied|unoccupied$/) {
  5567. $occupancy = $a[1];
  5568. readingsBeginUpdate($hash);
  5569. readingsBulkUpdate($hash, "occupancy", $a[1]);
  5570. readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 8);
  5571. readingsEndUpdate($hash, 1);
  5572. CommandDeleteReading(undef, "$name smartAckMailbox");
  5573. Log3 $name, 3, "EnOcean set $name occupancy $a[1]";
  5574. shift(@a);
  5575. } else {
  5576. return "Usage: $a[1] is wrong.";
  5577. }
  5578. } else {
  5579. return "Usage: set <name> occupancy occupied|unoccupied";
  5580. }
  5581. } elsif ($cmd eq "window") {
  5582. if (defined $a[1]) {
  5583. if ($a[1] =~ m/^closed|open$/) {
  5584. $window = $a[1];
  5585. readingsBeginUpdate($hash);
  5586. readingsBulkUpdate($hash, "window", $a[1]);
  5587. readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 0x10);
  5588. readingsEndUpdate($hash, 1);
  5589. CommandDeleteReading(undef, "$name smartAckMailbox");
  5590. Log3 $name, 3, "EnOcean set $name window $a[1]";
  5591. shift(@a);
  5592. } else {
  5593. return "Usage: $a[1] is wrong.";
  5594. }
  5595. } else {
  5596. return "Usage: set <name> window open|closed";
  5597. }
  5598. } else {
  5599. $cmdList .= "setpointTemp:slider,5,1,40 cooling:off,on desired-temp " .
  5600. "fanSpeed:auto,off,1,2,3 heating:off,on occupancy:occupied,unoccupied " .
  5601. "setpointShiftMax:slider,1,1,10 setpointType:setpointShift,setpointTemp window:closed,open";
  5602. return "Unknown argument $cmd, choose one of $cmdList";
  5603. }
  5604. $setpointType = $setpointType eq 'setpointTemp' ? 0x80 : 0;
  5605. $heating = $heating eq 'on' ? 0x40 : 0;
  5606. $cooling = $cooling eq 'on' ? 0x20 : 0;
  5607. $window = $window eq 'open' ? 0x10 : 0;
  5608. $setpointShift = int(($setpointShift + $setpointShiftMax) * 255 / ($setpointShiftMax * 2));
  5609. #$setpointShift = unpack('C', pack('c', $setpointShift));
  5610. my %fanSpeed = ('auto' => 0, 'off' =>1, 1 => 2, 2 => 3, 3 => 4);
  5611. $occupancy = $occupancy eq 'occupied' ? 1 : 0;
  5612. $data = sprintf "%02X%02X%02X%02X", $setpointType |$heating | $cooling | $window | 1,
  5613. $setpointShift, $setpointBase,
  5614. $setpointShiftMax << 4 | $fanSpeed{$fanSpeed} << 1 | $occupancy;
  5615. } elsif ($st eq "fanCtrl.00") {
  5616. # Fan Control
  5617. # (D2-20-00 - D2-20-02)
  5618. $rorg = "D2";
  5619. #$updateState = 0;
  5620. my ($fanSpeed, $humiThreshold, $roomSize, $roomSizeRef, $humidityCtrl, $tempLevel, $opMode) =
  5621. (255, 255, 15, 3, 3, 3, 15);
  5622. my $messageType = 0;
  5623. my @roomSizeTbl = (25, 50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300, 325, 350);
  5624. if ($cmd eq "fanSpeed") {
  5625. $updateState = 0;
  5626. if (defined $a[1]) {
  5627. if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
  5628. $fanSpeed = $a[1];
  5629. readingsSingleUpdate($hash, "fanSpeed", $a[1], 1);
  5630. Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]";
  5631. shift(@a);
  5632. } elsif ($a[1] eq "auto") {
  5633. $fanSpeed = 253;
  5634. readingsSingleUpdate($hash, "fanSpeed", $a[1], 1);
  5635. Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]";
  5636. shift(@a);
  5637. } elsif ($a[1] eq "default") {
  5638. $fanSpeed = 254;
  5639. readingsSingleUpdate($hash, "fanSpeed", $a[1], 1);
  5640. Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]";
  5641. shift(@a);
  5642. } else {
  5643. return "Usage: $a[1] is wrong.";
  5644. }
  5645. } else {
  5646. return "Usage: set <name> fanspeed 0...100|auto|default";
  5647. }
  5648. shift(@a);
  5649. } elsif ($cmd eq "on") {
  5650. $opMode = 1;
  5651. Log3 $name, 3, "EnOcean set $name $a[0]";
  5652. shift(@a);
  5653. } elsif ($cmd eq "off") {
  5654. $opMode = 0;
  5655. Log3 $name, 3, "EnOcean set $name $a[0]";
  5656. shift(@a);
  5657. } elsif ($cmd eq "desired-temp" || $cmd eq "setpointTemp") {
  5658. $updateState = 0;
  5659. my $temperatureRefDev = AttrVal($name, "temperatureRefDev", undef);
  5660. return "attribute missing: attr $name temperatureRefDev <refDev> must be defined" if (!defined $temperatureRefDev);
  5661. my $temperature = ReadingsVal($temperatureRefDev, "temperature", 20);
  5662. my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
  5663. my $switchHysteresis = AttrVal($name, "switchHysteresis", 1);
  5664. if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 40) {
  5665. $setpointTemp = $a[1];
  5666. shift(@a);
  5667. }
  5668. if ($setpointTemp > $temperature + $switchHysteresis / 2) {
  5669. $tempLevel = 2;
  5670. } elsif ($setpointTemp < $temperature - $switchHysteresis / 2) {
  5671. $tempLevel = 0;
  5672. } else {
  5673. $tempLevel = 1;
  5674. }
  5675. readingsBeginUpdate($hash);
  5676. readingsBulkUpdate($hash, "setpointTemp", sprintf("%.1f", $setpointTemp));
  5677. readingsBulkUpdate($hash, "temperature", sprintf("%.1f", $temperature));
  5678. readingsEndUpdate($hash, 1);
  5679. Log3 $name, 3, "EnOcean set $name $cmd $setpointTemp";
  5680. shift(@a);
  5681. } elsif ($cmd eq "humidityThreshold") {
  5682. $updateState = 0;
  5683. if (defined $a[1]) {
  5684. if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
  5685. $humidityCtrl = 1;
  5686. $humiThreshold = $a[1];
  5687. readingsSingleUpdate($hash, "humidityThreshold", $a[1], 1);
  5688. Log3 $name, 3, "EnOcean set $name humidityThreshold $a[1]";
  5689. shift(@a);
  5690. } elsif ($a[1] eq "auto") {
  5691. $humidityCtrl = 1;
  5692. $humiThreshold = 253;
  5693. readingsSingleUpdate($hash, "humidityThreshold", $a[1], 1);
  5694. Log3 $name, 3, "EnOcean set $name humidityThreshold $a[1]";
  5695. shift(@a);
  5696. } elsif ($a[1] eq "default") {
  5697. $humidityCtrl = 2;
  5698. $humiThreshold = 254;
  5699. readingsSingleUpdate($hash, "humidityThreshold", $a[1], 1);
  5700. Log3 $name, 3, "EnOcean set $name humidityThreshold $a[1]";
  5701. shift(@a);
  5702. } elsif ($a[1] eq "disabled") {
  5703. $humidityCtrl = 0;
  5704. $humiThreshold = 255;
  5705. readingsSingleUpdate($hash, "humidityThreshold", $a[1], 1);
  5706. Log3 $name, 3, "EnOcean set $name humidityThreshold $a[1]";
  5707. shift(@a);
  5708. } else {
  5709. return "Usage: $a[1] is wrong.";
  5710. }
  5711. } else {
  5712. return "Usage: set <name> humidityThreshold 0...100|auto|default|disabled";
  5713. }
  5714. shift(@a);
  5715. } elsif ($cmd eq "roomSize") {
  5716. $updateState = 0;
  5717. if (defined $a[1]) {
  5718. if ($a[1] =~ m/^\d+$/) {
  5719. readingsSingleUpdate($hash, "roomSize", $a[1], 1);
  5720. Log3 $name, 3, "EnOcean set $name roomSize $a[1]";
  5721. $roomSize = 14;
  5722. for (my $i = 0; $i < @roomSizeTbl; $i++) {
  5723. if ($a[1] <= $roomSizeTbl[$i]) {
  5724. $roomSize = $i;
  5725. last;
  5726. }
  5727. }
  5728. $roomSizeRef = 0;
  5729. shift(@a);
  5730. } elsif ($a[1] eq "not_used") {
  5731. $roomSizeRef = 1;
  5732. $roomSize = 15;
  5733. readingsSingleUpdate($hash, "roomSize", $a[1], 1);
  5734. Log3 $name, 3, "EnOcean set $name roomSize $a[1]";
  5735. shift(@a);
  5736. } elsif ($a[1] eq "default") {
  5737. $roomSizeRef = 2;
  5738. $roomSize = 15;
  5739. readingsSingleUpdate($hash, "roomSize", $a[1], 1);
  5740. Log3 $name, 3, "EnOcean set $name roomSize $a[1]";
  5741. shift(@a);
  5742. } elsif ($a[1] eq "max") {
  5743. $roomSizeRef = 0;
  5744. $roomSize = 14;
  5745. readingsSingleUpdate($hash, "roomSize", $a[1], 1);
  5746. Log3 $name, 3, "EnOcean set $name roomSize $a[1]";
  5747. shift(@a);
  5748. } else {
  5749. return "Usage: $a[1] is wrong.";
  5750. }
  5751. } else {
  5752. return "Usage: set <name> roomSize 0...350|max|not_used|default";
  5753. }
  5754. shift(@a);
  5755. } else {
  5756. $cmdList .= "off:noArg on:noArg desired-temp fanSpeed setpointTemp:slider,0,1,40 " .
  5757. "humidityThreshold roomSize";
  5758. return "Unknown argument $cmd, choose one of $cmdList";
  5759. }
  5760. $data = sprintf "%02X%02X%02X%02X", ($opMode << 4) | ($tempLevel << 1),
  5761. ($humidityCtrl << 6) | ($roomSizeRef << 4) | $roomSize,
  5762. $humiThreshold, $fanSpeed;
  5763. } elsif ($st eq "heatRecovery.00") {
  5764. # heat recovery ventilation
  5765. # (D2-50-00)
  5766. $rorg = "D2";
  5767. $updateState = 0;
  5768. my $airQuatityThreshold = ReadingsVal($name, "airQuatityThreshold", 'default');
  5769. $airQuatityThreshold = $airQuatityThreshold eq 'default' ? 127 : $airQuatityThreshold;
  5770. my $CO2Threshold = ReadingsVal($name, "CO2Threshold", 'default');
  5771. $CO2Threshold = $CO2Threshold eq 'default' ? 127 : $CO2Threshold;
  5772. my $heatExchangerBypass = 0;
  5773. my $humidityThreshold = ReadingsVal($name, "humidityThreshold", 'default');
  5774. $humidityThreshold = $humidityThreshold eq 'default' ? 127 : $humidityThreshold;
  5775. my $startTimerMode = 0;
  5776. my $tempThreshold = ReadingsVal($name, "roomTempSet", 'default');
  5777. $tempThreshold = $tempThreshold eq 'default' ? 0 : $tempThreshold;
  5778. my $ventilation = 15;
  5779. if ($cmd eq "ventilation") {
  5780. $ventilation = $a[1];
  5781. return "Usage: $cmd variable is missing, choose off|1...4|auto|demand|supplyAir|exhaustAir." if (!defined $ventilation);
  5782. shift(@a);
  5783. my %ventilation = (
  5784. "off" => 0,
  5785. 1 => 1,
  5786. 2 => 2,
  5787. 3 => 3,
  5788. 4 => 4,
  5789. "auto" => 11,
  5790. "demand" => 12,
  5791. "supplyAir" => 13,
  5792. "exhaustAir" => 14
  5793. );
  5794. if (exists $ventilation{$ventilation}) {
  5795. $ventilation = $ventilation{$ventilation};
  5796. } else {
  5797. return "Usage: $cmd variable wrong, choose off|1...4|auto|demand|supplyAir|exhaustAir.";
  5798. }
  5799. } elsif ($cmd eq "heatExchangerBypass") {
  5800. $heatExchangerBypass = $a[1];
  5801. return "Usage: $cmd variable is missing, choose opens|closes." if (!defined $heatExchangerBypass);
  5802. shift(@a);
  5803. my %heatExchangerBypass = (
  5804. "closes" => 1,
  5805. "opens" => 2
  5806. );
  5807. if (exists $heatExchangerBypass{$heatExchangerBypass}) {
  5808. $heatExchangerBypass = $heatExchangerBypass{$heatExchangerBypass};
  5809. } else {
  5810. return "Usage: $cmd variable wrong, choose opens|closes.";
  5811. }
  5812. } elsif ($cmd eq "startTimerMode") {
  5813. $startTimerMode = 1;
  5814. } elsif ($cmd eq "CO2Threshold") {
  5815. $CO2Threshold = $a[1];
  5816. if (defined $CO2Threshold) {
  5817. if ($CO2Threshold eq 'default') {
  5818. $CO2Threshold = 127;
  5819. readingsSingleUpdate($hash, "CO2Threshold", 'default', 1);
  5820. } elsif ($CO2Threshold =~ m/^\d+$/ && $CO2Threshold >= 0 && $CO2Threshold <= 100) {
  5821. readingsSingleUpdate($hash, "CO2Threshold", $CO2Threshold, 1);
  5822. } else {
  5823. return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
  5824. }
  5825. } else {
  5826. return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
  5827. }
  5828. shift(@a);
  5829. } elsif ($cmd eq "humidityThreshold") {
  5830. $humidityThreshold = $a[1];
  5831. if (defined $humidityThreshold) {
  5832. if ($humidityThreshold eq 'default') {
  5833. $humidityThreshold = 127;
  5834. readingsSingleUpdate($hash, "humidityThreshold", 'default', 1);
  5835. } elsif ($humidityThreshold =~ m/^\d+$/ && $humidityThreshold >= 0 && $humidityThreshold <= 100) {
  5836. readingsSingleUpdate($hash, "humidityThreshold", $humidityThreshold, 1);
  5837. } else {
  5838. return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
  5839. }
  5840. } else {
  5841. return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
  5842. }
  5843. shift(@a);
  5844. } elsif ($cmd eq "airQuatityThreshold") {
  5845. $airQuatityThreshold = $a[1];
  5846. if (defined $airQuatityThreshold) {
  5847. if ($airQuatityThreshold eq 'default') {
  5848. $airQuatityThreshold = 127;
  5849. readingsSingleUpdate($hash, "airQuatityThreshold", 'default', 1);
  5850. } elsif ($airQuatityThreshold =~ m/^\d+$/ && $airQuatityThreshold >= 0 && $airQuatityThreshold <= 100) {
  5851. readingsSingleUpdate($hash, "airQuatityThreshold", $airQuatityThreshold, 1);
  5852. } else {
  5853. return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
  5854. }
  5855. } else {
  5856. return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
  5857. }
  5858. shift(@a);
  5859. } elsif ($cmd eq "roomTemp") {
  5860. $tempThreshold = $a[1];
  5861. if (defined $tempThreshold) {
  5862. if ($tempThreshold eq 'default') {
  5863. $tempThreshold = 0;
  5864. readingsSingleUpdate($hash, "roomTempSet", 'default', 1);
  5865. } elsif ($tempThreshold =~ m/^[+-]?\d+$/ && $tempThreshold >= - 63 && $tempThreshold <= 63) {
  5866. readingsSingleUpdate($hash, "roomTempSet", $tempThreshold, 1);
  5867. $tempThreshold += 64;
  5868. #$tempThreshold = abs($tempThreshold) | 0x40 if ($tempThreshold < 0);
  5869. } else {
  5870. return "Usage: $cmd variable is wrong, choose default|-63 ... 63." ;
  5871. }
  5872. } else {
  5873. return "Usage: $cmd variable is wrong, choose default|-63 ... 63." ;
  5874. }
  5875. shift(@a);
  5876. } else {
  5877. $cmdList .= "ventilation:off,1,2,3,4,auto,demand,supplyAir,exhaustAir roomTemp heatExchangerBypass:closes,opens " .
  5878. "startTimerMode:noArg CO2Threshold humidityThreshold airQuatityThreshold";
  5879. return "Unknown argument $cmd, choose one of $cmdList";
  5880. }
  5881. $data = sprintf "%02X%02X%02X%02X%02X%02X", 32 | $ventilation, $heatExchangerBypass << 4, $startTimerMode << 7 | $CO2Threshold,
  5882. $humidityThreshold, $airQuatityThreshold, $tempThreshold;
  5883. Log3 $name, 3, "EnOcean set $name $cmd";
  5884. } elsif ($st eq "valveCtrl.00") {
  5885. # Valve Control
  5886. # (D2-A0-01)
  5887. if (AttrVal($name, "devMode", "master") eq "slave") {
  5888. # devNode slave
  5889. if ($cmd eq "closed") {
  5890. $rorg = "D2";
  5891. $data = "01";
  5892. } elsif ($cmd eq "open") {
  5893. $rorg = "D2";
  5894. $data = "02";
  5895. } elsif ($cmd eq "teachIn") {
  5896. $updateState = 0;
  5897. ($err, $rorg, $data) = EnOcean_sndUTE(undef, $hash, "biDir",
  5898. AttrVal($name, "uteResponseRequest", "yes"), "in", 0, "D2-A0-01");
  5899. } elsif ($cmd eq "teachOut") {
  5900. $updateState = 0;
  5901. ($err, $rorg, $data) = EnOcean_sndUTE(undef, $hash, "biDir",
  5902. AttrVal($name, "uteResponseRequest", "yes"), "out", 0, "D2-A0-01");
  5903. } else {
  5904. return "Unknown argument $cmd, choose one of " . $cmdList . "open:noArg closed:noArg teachIn:noArg teachOut:noArg";
  5905. }
  5906. } else {
  5907. # devMode master
  5908. if ($cmd eq "closes") {
  5909. $rorg = "D2";
  5910. $data = "01";
  5911. } elsif ($cmd eq "opens") {
  5912. $rorg = "D2";
  5913. $data = "02";
  5914. } else {
  5915. return "Unknown argument $cmd, choose one of " . $cmdList . "opens:noArg closes:noArg";
  5916. }
  5917. }
  5918. Log3 $name, 3, "EnOcean set $name $cmd";
  5919. } elsif ($st eq "contact") {
  5920. # 1BS Telegram
  5921. # Single Input Contact (EEP D5-00-01)
  5922. $rorg = "D5";
  5923. my $setCmd;
  5924. if ($cmd eq "teach") {
  5925. $attr{$name}{eep} = "D5-00-01";
  5926. CommandDeleteReading(undef, "$name .*");
  5927. readingsSingleUpdate($hash, "teach", "1BS teach-in sent", 1);
  5928. $setCmd = 0;
  5929. $updateState = 0;
  5930. } elsif ($cmd eq "closed") {
  5931. $setCmd = 9;
  5932. } elsif ($cmd eq "open") {
  5933. $setCmd = 8;
  5934. } else {
  5935. return "Unknown argument $cmd, choose one of " . $cmdList . "open:noArg closed:noArg teach:noArg";
  5936. }
  5937. $data = sprintf "%02X", $setCmd;
  5938. Log3 $name, 3, "EnOcean set $name $cmd";
  5939. } elsif ($st eq "genericProfile") {
  5940. # Generic Profile
  5941. my $channel = 0;
  5942. my $devMode = AttrVal($name, "devMode", "master");
  5943. my $header = 1;
  5944. my ($setChannel, $setChannelName) = split(/-|:/, $cmd, 2);
  5945. my ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax);
  5946. my ($readingFormat, $readingName, $readingType, $readingUnit, $readingValue);
  5947. my $gpDef = AttrVal($name, "gpDef", undef);
  5948. return "Usage: Channel definition is missing" if (!defined $gpDef);
  5949. my @gpDef = split("[ \t][ \t]*", $gpDef);
  5950. if ($cmd eq "channelName") {
  5951. # rename channel name
  5952. ($setChannel, $setChannelName) = split(/-|:/, $a[1], 2);
  5953. if (defined $gpDef[$setChannel]) {
  5954. ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
  5955. split(':', $gpDef[$setChannel]);
  5956. # remove spaces und tabs
  5957. #$channelName =~ tr/ \t//d;
  5958. if ($setChannelName eq "?") {
  5959. $channelName = "none";
  5960. if ($channelType == 1 && defined $EnO_gpValueData{$signalType}{name}) {
  5961. $channelName = $EnO_gpValueData{$signalType}{name};
  5962. } elsif ($channelType == 2 && defined $EnO_gpValueFlag{$signalType}{name}) {
  5963. $channelName = $EnO_gpValueFlag{$signalType}{name};
  5964. } elsif ($channelType == 3 && defined $EnO_gpValueEnum{$signalType}{name}) {
  5965. $channelName = $EnO_gpValueEnum{$signalType}{name};
  5966. }
  5967. } else {
  5968. $channelName = $setChannelName;
  5969. }
  5970. $resolution = '' if (!defined $resolution);
  5971. $engMin = '' if (!defined $engMin);
  5972. $scalingMin = '' if (!defined $scalingMin);
  5973. $engMax = '' if (!defined $engMax);
  5974. $scalingMax = '' if (!defined $scalingMax);
  5975. $gpDef[$setChannel] = $channelName . ':' . $channelDir . ':' . $channelType . ':' . $signalType . ':' . $valueType .
  5976. ':' . $resolution . ':' . $engMin . ':' . $scalingMin . ':' . $engMax . ':' . $scalingMax;
  5977. $attr{$name}{gpDef} = join(' ', @gpDef);
  5978. shift @a;
  5979. $updateState = 3;
  5980. $channelType = 255;
  5981. EnOcean_CommandSave(undef, undef);
  5982. } else {
  5983. return "Wrong parameter, channel $setChannel not defined.";
  5984. }
  5985. } elsif ($cmd eq "teachIn" && $devMode eq "slave") {
  5986. # teach-in generic profile
  5987. $rorg = "B0";
  5988. my ($gpDefO, $gpDefI, $formatPattern, $teachInInfo);
  5989. my $channelDirSeq = "--";
  5990. my $comMode = 0;
  5991. $attr{$name}{comMode} = "uniDir";
  5992. $attr{$name}{eep} = "B0-00-00";
  5993. # multicast teach-in
  5994. $destinationID = "FFFFFFFF";
  5995. my $productID = AttrVal($name, "productID", undef);
  5996. if (defined $productID) {
  5997. $productID = '0000000010' . EnOcean_convHexToBit($productID);
  5998. } else {
  5999. $productID = '';
  6000. }
  6001. while ($gpDef[$channel]) {
  6002. ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
  6003. split(':', $gpDef[$channel]);
  6004. if ($channelDir eq "O") {
  6005. #Log3 $name, 3, "EnOcean set $name channel: $channel channelDir: $channelDir seq: $channelDirSeq";
  6006. #Log3 $name, 3, "EnOcean set $name channel: $channel channelType: $channelType signalType: $signalType valueType: $valueType";
  6007. return "Usage: attr $name gpDef: O/I sequence error" if $channelDirSeq =~ m/.I$/;
  6008. $channelDirSeq = "O-";
  6009. # add channel-, signal- and valuetype
  6010. $gpDefO .= substr(unpack('B8', pack('C', $channelType)), 6) .
  6011. unpack('B8', pack('C', $signalType)) .
  6012. substr(unpack('B8', pack('C', $valueType)), 6);
  6013. if ($channelType == 1 || $channelType == 3) {
  6014. # data, enumeration: add resolution
  6015. $gpDefO .= substr(unpack('B8', pack('C', $resolution)), 4);
  6016. }
  6017. if ($channelType == 1) {
  6018. # data: add engineering and scaling
  6019. $gpDefO .= unpack('B8', pack('c', $engMin)) .
  6020. substr(unpack('B8', pack('C', $scalingMin)), 4) .
  6021. unpack('B8', pack('c', $engMax)) .
  6022. substr(unpack('B8', pack('C', $scalingMax)), 4);
  6023. }
  6024. } elsif ($channelDir eq "I") {
  6025. #Log3 $name, 3, "EnOcean set $name channel: $channel channelDir: $channelDir seq: $channelDirSeq";
  6026. return "Usage: attr $name gpDef: O/I sequence error" if $channelDirSeq !~ m/^O./;
  6027. $channelDirSeq = "OI";
  6028. $gpDefI .= substr(unpack('B8', pack('C', $channelType)), 6) .
  6029. unpack('B8', pack('C', $signalType)) .
  6030. substr(unpack('B8', pack('C', $valueType)), 6);
  6031. if ($channelType == 1 || $channelType == 3) {
  6032. # data, enumeration: add resolution
  6033. $gpDefI .= substr(unpack('B8', pack('C', $resolution)), 4);
  6034. }
  6035. if ($channelType == 1) {
  6036. # data: add engineering and scaling
  6037. $gpDefI .= unpack('B8', pack('c', $engMin)) .
  6038. substr(unpack('B8', pack('C', $scalingMin)), 4) .
  6039. unpack('B8', pack('c', $engMax)) .
  6040. substr(unpack('B8', pack('C', $scalingMax)), 4);
  6041. }
  6042. }
  6043. $channel ++;
  6044. }
  6045. if ($channelDirSeq eq "OI") {
  6046. # await teach-in response
  6047. $comMode = 1;
  6048. $attr{$name}{comMode} = "biDir";
  6049. # set flag for response request
  6050. $hash->{IODev}{helper}{gpRespWait}{AttrVal($name, "subDef", $hash->{DEF})}{teachInReq} = "in";
  6051. $hash->{IODev}{helper}{gpRespWait}{AttrVal($name, "subDef", $hash->{DEF})}{hash} = $hash;
  6052. if (!exists($hash->{IODev}{Teach})) {
  6053. # enable teach-in receiving for 3 sec
  6054. $hash->{IODev}{Teach} = 1;
  6055. #####
  6056. #my %timeoutHash = (hash => $IOHash, function => "gpRespTimeout", helper => "gpRespWait");
  6057. #RemoveInternalTimer(\%timeoutHash);
  6058. #InternalTimer(gettimeofday() + 3, "EnOcean_RespTimeout", \%timeoutHash, 0);
  6059. RemoveInternalTimer($hash->{helper}{timer}{gpRespTimeout}) if(exists $hash->{helper}{timer}{gpRespTimeout});
  6060. $hash->{helper}{timer}{gpRespTimeout} = {hash => $IOHash, function => "gpRespTimeout", helper => "gpRespWait"};
  6061. InternalTimer(gettimeofday() + 3, 'EnOcean_RespTimeout', $hash->{helper}{timer}{gpRespTimeout}, 0);
  6062. }
  6063. }
  6064. $header = (0x7FF << 1 | $comMode) << 4;
  6065. if ($channelDirSeq =~ m/.I$/) {
  6066. # create teach-in information
  6067. if (length($gpDefI) % 8) {
  6068. # fill with trailing zeroes to x bytes
  6069. $gpDefI .= 0 x (8 - length($gpDefI) % 8);
  6070. }
  6071. $teachInInfo = '0000000001' . unpack('B8', pack('C', length($gpDefI) / 4));
  6072. }
  6073. #Log3 $name, 3, "EnOcean set $name header: $header O: $gpDefO Info: $teachInInfo I: $gpDefI";
  6074. # DophinView GP profile error if Product ID sent
  6075. $data = $productID . $gpDefO . $teachInInfo . $gpDefI;
  6076. #$data = $gpDefO . $teachInInfo . $gpDefI;
  6077. if (length($data) % 8) {
  6078. # fill with trailing zeroes to x bytes
  6079. $data .= 0 x (8 - length($data) % 8);
  6080. }
  6081. $channelType = 0;
  6082. EnOcean_CommandSave(undef, undef);
  6083. $data = sprintf '%04X%s', $header, EnOcean_convBitToHex($data);
  6084. my $teachInState = $comMode == 1 ? "teach-in sent, response requested" : "teach-in sent";
  6085. readingsSingleUpdate($hash, "teach", "GP $teachInState", 1);
  6086. } elsif ($cmd eq "teachOut" && $devMode eq "slave") {
  6087. # teach out generic profile
  6088. $rorg = "B0";
  6089. $channelType = 0;
  6090. my $comMode = 0;
  6091. if (AttrVal($name, "comMode", "uniDir") eq "biDir") {
  6092. $comMode = 1;
  6093. $hash->{IODev}{helper}{gpRespWait}{AttrVal($name, "subDef", $hash->{DEF})}{teachInReq} = "out";
  6094. $hash->{IODev}{helper}{gpRespWait}{AttrVal($name, "subDef", $hash->{DEF})}{hash} = $hash;
  6095. if (!exists($hash->{IODev}{Teach})) {
  6096. # enable teach-in receiving for 3 sec
  6097. $hash->{IODev}{Teach} = 1;
  6098. #####
  6099. #my %timeoutHash = (hash => $IOHash, function => "gpRespTimeout", helper => "gpRespWait");
  6100. #RemoveInternalTimer(\%timeoutHash);
  6101. #InternalTimer(gettimeofday() + 3, "EnOcean_RespTimeout", \%timeoutHash, 0);
  6102. RemoveInternalTimer($hash->{helper}{timer}{gpRespTimeout}) if(exists $hash->{helper}{timer}{gpRespTimeout});
  6103. $hash->{helper}{timer}{gpRespTimeout} = {hash => $IOHash, function => "gpRespTimeout", helper => "gpRespWait"};
  6104. InternalTimer(gettimeofday() + 3, 'EnOcean_RespTimeout', $hash->{helper}{timer}{gpRespTimeout}, 0);
  6105. }
  6106. }
  6107. $data = sprintf '%04X', (0x7FF << 1 | $comMode) << 4 | 4;
  6108. my $teachInState = $comMode == 1 ? "teach-in deletion sent, response requested" : "teach-in deletion sent";
  6109. readingsSingleUpdate($hash, "teach", "GP $teachInState", 1);
  6110. } elsif ($setChannel =~ m/^\d+$/ && defined $gpDef[$setChannel]) {
  6111. # send selective data (GPSD)
  6112. $rorg = "B3";
  6113. # select channel
  6114. ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
  6115. split(':', $gpDef[$setChannel]);
  6116. if ($channelName eq $setChannelName && $channelDir eq "O") {
  6117. $channel = $setChannel;
  6118. } else {
  6119. return "Channel name wrong or no output channel";
  6120. }
  6121. } else {
  6122. # command error
  6123. my $channelCntr = 0;
  6124. my @cmdList;
  6125. while (defined $gpDef[$channelCntr]) {
  6126. ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
  6127. split(':', $gpDef[$channelCntr]);
  6128. if ($channelDir ne "O") {
  6129. $channelCntr ++;
  6130. next;
  6131. }
  6132. if ($channelType == 1) {
  6133. # data
  6134. push @cmdList, sprintf('%02d', $channelCntr) . "-" . $channelName;
  6135. } elsif ($channelType == 2) {
  6136. # flag
  6137. push @cmdList, sprintf('%02d', $channelCntr) . "-" . $channelName . ':' .
  6138. $EnO_gpValueFlag{$signalType}{flag}{0} . "," .
  6139. $EnO_gpValueFlag{$signalType}{flag}{1};
  6140. } elsif ($channelType == 3) {
  6141. # enumeration
  6142. my $cmdListEnum = "";
  6143. my $enum = 0;
  6144. while (defined $EnO_gpValueEnum{$signalType}{enum}{$enum}) {
  6145. $cmdListEnum .= $EnO_gpValueEnum{$signalType}{enum}{$enum} . ",";
  6146. $enum ++;
  6147. }
  6148. push @cmdList, sprintf('%02d', $channelCntr) . "-" . $channelName . ':' . substr($cmdListEnum, 0, -1);
  6149. }
  6150. $channelCntr ++;
  6151. }
  6152. if (defined $cmdList[0] && $devMode eq "slave") {
  6153. return "Unknown argument $cmd, choose one of " . $cmdList . 'channelName teachIn:noArg teachOut:noArg ' . join(" ", @cmdList);
  6154. } elsif (defined $cmdList[0] && $devMode eq "master") {
  6155. return "Unknown argument $cmd, choose one of " . $cmdList . 'channelName ' . join(" ", @cmdList);
  6156. } else {
  6157. return "Unknown argument $cmd, choose one of " . $cmdList . 'channelName';
  6158. }
  6159. }
  6160. #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel";
  6161. if ($channelType == 1) {
  6162. # data
  6163. if ($engMin * $EnO_scaling[$scalingMin] > $engMax * $EnO_scaling[$scalingMax]) {
  6164. return "Usage: numerical value is missing" if (!defined $a[1]);
  6165. if ($a[1] =~ m/^[-+]?\d*\.?\d+([eE][-+]?\d+)?$/ &&
  6166. $a[1] >= $engMax * $EnO_scaling[$scalingMax] &&
  6167. $a[1] <= $engMin * $EnO_scaling[$scalingMin]) {
  6168. $data = int(2**$EnO_resolution[$resolution] * ($a[1] - $engMin * $EnO_scaling[$scalingMin]) /
  6169. ($engMax * $EnO_scaling[$scalingMax] - $engMin * $EnO_scaling[$scalingMin]));
  6170. #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel resolution: " . $EnO_resolution[$resolution] . " data: $data";
  6171. if ($data >= 2**$EnO_resolution[$resolution]) {
  6172. $data = 2**$EnO_resolution[$resolution] - 1;
  6173. #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel resolution: " . $EnO_resolution[$resolution] . " data: $data";
  6174. }
  6175. shift @a;
  6176. } else {
  6177. return "Usage: $a[1] is not numeric or out of range";
  6178. }
  6179. } else {
  6180. return "Usage: numerical value is missing" if (!defined $a[1]);
  6181. if ($a[1] =~ m/^[-+]?\d*\.?\d+([eE][-+]?\d+)?$/ &&
  6182. $a[1] >= $engMin * $EnO_scaling[$scalingMin] &&
  6183. $a[1] <= $engMax * $EnO_scaling[$scalingMax]) {
  6184. $data = int(2**$EnO_resolution[$resolution] * ($a[1] - $engMin * $EnO_scaling[$scalingMin]) /
  6185. ($engMax * $EnO_scaling[$scalingMax] - $engMin * $EnO_scaling[$scalingMin]));
  6186. #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel resolution: " . $EnO_resolution[$resolution] . " data: $data";
  6187. if ($data >= 2**$EnO_resolution[$resolution]) {
  6188. $data = 2**$EnO_resolution[$resolution] - 1;
  6189. #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel resolution: " . $EnO_resolution[$resolution] . " data: $data";
  6190. }
  6191. shift @a;
  6192. } else {
  6193. return "Usage: $a[1] is not numeric or out of range";
  6194. }
  6195. }
  6196. ($err, $logLevel, $response, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType) =
  6197. EnOcean_gpConvDataToValue (undef, $hash, $channel, $data, $gpDef[$channel]);
  6198. readingsBeginUpdate($hash);
  6199. readingsBulkUpdate($hash, $readingName, sprintf("$readingFormat", $readingValue));
  6200. readingsBulkUpdate($hash, $readingName . "Unit", $readingUnit);
  6201. readingsBulkUpdate($hash, $readingName . "ValueType", $valueType);
  6202. readingsBulkUpdate($hash, $readingName . "ChannelType", $readingType);
  6203. readingsEndUpdate($hash, 1);
  6204. $data = EnOcean_gpConvSelDataToSndData($header, $channel, $EnO_resolution[$resolution], $data);
  6205. } elsif ($channelType == 2) {
  6206. # flag
  6207. if (defined $EnO_gpValueFlag{$signalType}{flagInv}{$a[1]}) {
  6208. $data = $EnO_gpValueFlag{$signalType}{flagInv}{$a[1]};
  6209. #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel data: " . $EnO_gpValueFlag{$signalType}{flagInv}{$a[1]};
  6210. shift @a;
  6211. } else {
  6212. return "Usage: $a[1] is unknown";
  6213. }
  6214. #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel data: $data";
  6215. ($err, $logLevel, $response, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType)=
  6216. EnOcean_gpConvDataToValue (undef, $hash, $channel, $data, $gpDef[$channel]);
  6217. readingsBeginUpdate($hash);
  6218. readingsBulkUpdate($hash, $readingName, sprintf("$readingFormat", $readingValue));
  6219. readingsBulkUpdate($hash, $readingName . "Unit", $readingUnit);
  6220. readingsBulkUpdate($hash, $readingName . "ValueType", $valueType);
  6221. readingsBulkUpdate($hash, $readingName . "ChannelType", $readingType);
  6222. readingsEndUpdate($hash, 1);
  6223. $data = sprintf '%04X', (($header << 6 | $channel) << 1 | $data) << 5;
  6224. } elsif ($channelType == 3) {
  6225. # enumeration
  6226. if (defined $EnO_gpValueEnum{$signalType}{enumInv}{$a[1]}) {
  6227. $data = $EnO_gpValueEnum{$signalType}{enumInv}{$a[1]};
  6228. #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel data: $data";
  6229. shift @a;
  6230. } else {
  6231. return "Usage: $a[1] is unknown";
  6232. }
  6233. ($err, $logLevel, $response, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType)=
  6234. EnOcean_gpConvDataToValue (undef, $hash, $channel, $data, $gpDef[$channel]);
  6235. readingsBeginUpdate($hash);
  6236. readingsBulkUpdate($hash, $readingName, sprintf("$readingFormat", $readingValue));
  6237. readingsBulkUpdate($hash, $readingName . "Unit", $readingUnit);
  6238. readingsBulkUpdate($hash, $readingName . "ValueType", $valueType);
  6239. readingsBulkUpdate($hash, $readingName . "ChannelType", $readingType);
  6240. readingsEndUpdate($hash, 1);
  6241. $data = EnOcean_gpConvSelDataToSndData($header, $channel, $EnO_resolution[$resolution], $data);
  6242. }
  6243. Log3 $name, 3, "EnOcean set $name $cmd";
  6244. } elsif ($st eq "raw") {
  6245. # sent raw data
  6246. if ($cmd eq "4BS"){
  6247. # 4BS Telegram
  6248. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{8}$/) {
  6249. $data = uc($a[1]);
  6250. $rorg = "A5";
  6251. } else {
  6252. return "Wrong parameter, choose 4BS <data 4 Byte hex> [status 1 Byte hex]";
  6253. }
  6254. } elsif ($cmd eq "1BS") {
  6255. # 1BS Telegram
  6256. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2}$/) {
  6257. $data = uc($a[1]);
  6258. $rorg = "D5";
  6259. } else {
  6260. return "Wrong parameter, choose 1BS <data 1 Byte hex> [status 1 Byte hex]";
  6261. }
  6262. } elsif ($cmd eq "RPS") {
  6263. # RPS Telegram
  6264. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2}$/) {
  6265. $data = uc($a[1]);
  6266. $rorg = "F6";
  6267. } else {
  6268. return "Wrong parameter, choose RPS <data 1 Byte hex> [status 1 Byte hex]";
  6269. }
  6270. } elsif ($cmd eq "MSC") {
  6271. # MSC Telegram
  6272. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) {
  6273. $data = uc($a[1]);
  6274. $rorg = "D1";
  6275. } else {
  6276. return "Wrong parameter, choose MSC <data 1 ... 14 Byte hex> [status 1 Byte hex]";
  6277. }
  6278. } elsif ($cmd eq "UTE") {
  6279. # UTE Telegram
  6280. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{14}$/) {
  6281. $data = uc($a[1]);
  6282. $rorg = "D4";
  6283. } else {
  6284. return "Wrong parameter, choose UTE <data 7 Byte hex> [status 1 Byte hex]";
  6285. }
  6286. } elsif ($cmd eq "VLD") {
  6287. # VLD Telegram
  6288. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) {
  6289. $data = uc($a[1]);
  6290. $rorg = "D2";
  6291. } else {
  6292. return "Wrong parameter, choose VLD <data 1 ... 14 Byte hex> [status 1 Byte hex]";
  6293. }
  6294. } elsif ($cmd eq "GPTI") {
  6295. # GP teach-in request telegram
  6296. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,1024}$/ && !(length($a[1]) % 2)) {
  6297. $data = uc($a[1]);
  6298. $rorg = "B0";
  6299. } else {
  6300. return "Wrong parameter, choose GPTI <data 1 ... 512 Byte hex> [status 1 Byte hex]";
  6301. }
  6302. } elsif ($cmd eq "GPTR") {
  6303. # GP teach-in response telegram
  6304. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,1024}$/ && !(length($a[1]) % 2)) {
  6305. $data = uc($a[1]);
  6306. $rorg = "B1";
  6307. } else {
  6308. return "Wrong parameter, choose GPTR <data 1 ... 512 Byte hex> [status 1 Byte hex]";
  6309. }
  6310. } elsif ($cmd eq "GPCD") {
  6311. # GP complete date telegram
  6312. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,1024}$/ && !(length($a[1]) % 2)) {
  6313. $data = uc($a[1]);
  6314. $rorg = "B2";
  6315. } else {
  6316. return "Wrong parameter, choose GPCD <data 1 ... 512 Byte hex> [status 1 Byte hex]";
  6317. }
  6318. } elsif ($cmd eq "GPSD") {
  6319. # GP selective data telegram
  6320. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,1024}$/ && !(length($a[1]) % 2)) {
  6321. $data = uc($a[1]);
  6322. $rorg = "B3";
  6323. } else {
  6324. return "Wrong parameter, choose GPSD <data 1 ... 512 Byte hex> [status 1 Byte hex]";
  6325. }
  6326. } elsif ($cmd eq "SEC") {
  6327. # secure telegram
  6328. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) {
  6329. $data = uc($a[1]);
  6330. $rorg = "30";
  6331. } else {
  6332. return "Wrong parameter, choose SEC <data 1 ... 14 Byte hex> [status 1 Byte hex]";
  6333. }
  6334. } elsif ($cmd eq "ENC") {
  6335. # secure telegram with encapsulation
  6336. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) {
  6337. $data = uc($a[1]);
  6338. $rorg = "31";
  6339. } else {
  6340. return "Wrong parameter, choose ENC <data 1 ... 14 Byte hex> [status 1 Byte hex]";
  6341. }
  6342. } elsif ($cmd eq "STE") {
  6343. # secure Teach-In
  6344. if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) {
  6345. $data = uc($a[1]);
  6346. $rorg = "35";
  6347. } else {
  6348. return "Wrong parameter, choose STE <data 1 ... 14 Byte hex> [status 1 Byte hex]";
  6349. }
  6350. } else {
  6351. return "Unknown argument $cmd, choose one of 1BS 4BS ENC GPCD GPSD GPTI GPTR MSC RPS SEC STE UTE VLD";
  6352. }
  6353. if ($a[2]) {
  6354. if ($a[2] !~ m/^[\dA-Fa-f]{2}$/) {
  6355. return "Wrong status parameter, choose $cmd $a[1] [status 1 Byte hex]";
  6356. }
  6357. $status = uc($a[2]);
  6358. splice(@a,2,1);
  6359. }
  6360. $updateState = 0;
  6361. readingsSingleUpdate($hash, "RORG", $cmd, 1);
  6362. readingsSingleUpdate($hash, "dataSent", $data, 1);
  6363. readingsSingleUpdate($hash, "statusSent", $status, 1);
  6364. Log3 $name, 3, "EnOcean set $name $cmd $data $status";
  6365. shift(@a);
  6366. } elsif ($st eq "remote") {
  6367. return "Unknown argument $cmd, choose one of $cmdList";
  6368. } else {
  6369. # subtype does not support set commands
  6370. $updateState = -1;
  6371. if (AttrVal($name, "remoteManagement", "off") eq "manager") {
  6372. return "Unknown argument $cmd, choose one of $cmdList";
  6373. } else {
  6374. return;
  6375. }
  6376. }
  6377. # set reading state if confirmation telegram is not expected
  6378. if($updateState == 1 && $updateStateAttr =~ m/^default|yes$/) {
  6379. readingsSingleUpdate($hash, "state", $cmd, 1);
  6380. } elsif ($updateState == 0 && $updateStateAttr eq "yes") {
  6381. readingsSingleUpdate($hash, "state", $cmd, 1);
  6382. #} elsif ($updateState == 3) {
  6383. #internal command
  6384. #} else {
  6385. # readingsSingleUpdate($hash, ".info", "await_confirm", 0);
  6386. }
  6387. # send commands
  6388. if($updateState >= -1 && $updateState <= 1) {
  6389. EnOcean_SndCdm(undef, $hash, $packetType, $rorg, $data, $subDef, $status, $destinationID);
  6390. EnOcean_observeInit(1, $hash, @cmdObserve);
  6391. if ($switchMode eq "pushbutton" && $cmd1 ne "released") {
  6392. my @timerCmd = ($name, "released");
  6393. my %par = (hash => $hash, timerCmd => \@timerCmd);
  6394. InternalTimer(gettimeofday() + 0.1, "EnOcean_TimerSet", \%par, 0);
  6395. }
  6396. }
  6397. #Log3 $name, 3, "EnOcean set $name remainder of CmdArg: " . join(' ' ,@a) if (@a > 0);
  6398. }
  6399. return undef;
  6400. }
  6401. # parse and display the incoming telegrams
  6402. sub EnOcean_Parse($$)
  6403. {
  6404. my ($iohash, $msg) = @_;
  6405. my $IODev = $iohash->{NAME};
  6406. my ($hash, $name, $filelogName, $rorgname);
  6407. my ($ctrl, $err, $logLevel, $response);
  6408. Log3 $IODev, 4, "EnOcean received via $IODev: $msg";
  6409. my @msg = split(':', $msg);
  6410. my ($rorg, $data, $senderID, $status, $odata, $subDef, $destinationID, $funcNumber, $manufID, $RSSI, $delay, $subTelNum);
  6411. my $packetType = hex($msg[1]);
  6412. my @event;
  6413. if ($packetType == 1) {
  6414. # packet type RADIO
  6415. #EnOcean:PacketType:RORG:MessageData:SourceID:Status:OptionalData
  6416. (undef, undef, $rorg, $data, $senderID, $status, $odata) = @msg;
  6417. $odata =~ m/^(..)(........)(..)(..)$/;
  6418. ($subTelNum, $destinationID, $RSSI) = (hex($1), $2, hex($3));
  6419. if (exists $modules{EnOcean}{defptr}{$senderID}) {
  6420. $hash = $modules{EnOcean}{defptr}{$senderID};
  6421. } elsif ($destinationID ne 'FFFFFFFF') {
  6422. $hash = $modules{EnOcean}{defptr}{$destinationID};
  6423. }
  6424. $rorgname = $EnO_rorgname{$rorg};
  6425. if (!$rorgname) {
  6426. if($hash) {
  6427. Log3 $hash->{NAME}, 2, "EnOcean $hash->{NAME} RORG $rorg unknown.";
  6428. } else {
  6429. Log3 undef, 2, "EnOcean RORG $rorg received from $senderID unknown.";
  6430. }
  6431. return "";
  6432. }
  6433. if ($rorg eq "40") {
  6434. # chained data message (CDM)
  6435. $data =~ m/^(..)(.*)$/;
  6436. # SEQ evaluation?
  6437. my ($seq, $idx) = (hex($1) & 0xC0, hex($1) & 0x3F);
  6438. $data = $2;
  6439. if ($idx == 0) {
  6440. # first message part
  6441. delete $iohash->{helper}{cdm};
  6442. $data =~ m/^(....)(..)(.*)$/;
  6443. $iohash->{helper}{cdm}{len} = hex($1);
  6444. $iohash->{helper}{cdm}{rorg} = $2;
  6445. $iohash->{helper}{cdm}{data}{$idx} = $3;
  6446. $iohash->{helper}{cdm}{lenCounter} = length($3) / 2;
  6447. #####
  6448. #my %functionHash = (hash => $iohash, function => "cdm");
  6449. #RemoveInternalTimer(\%functionHash);
  6450. #InternalTimer(gettimeofday() + 1, "EnOcean_helperClear", \%functionHash, 0);
  6451. RemoveInternalTimer($hash->{helper}{timer}{helperClear}) if(exists $hash->{helper}{timer}{helperClear});
  6452. $hash->{helper}{timer}{helperClear} = {hash => $iohash, function => "cdm"};
  6453. InternalTimer(gettimeofday() + 1, 'EnOcean_helperClear', $hash->{helper}{timer}{helperClear}, 0);
  6454. #Log3 $IODev, 3, "EnOcean $IODev CDM timer started";
  6455. } else {
  6456. $iohash->{helper}{cdm}{data}{$idx} = $data;
  6457. $iohash->{helper}{cdm}{lenCounter} += length($data) / 2;
  6458. }
  6459. if ($iohash->{helper}{cdm}{lenCounter} >= $iohash->{helper}{cdm}{len}) {
  6460. # data message complete
  6461. # reconstruct RORG, DATA
  6462. my ($idx, $dataPart, @data);
  6463. while (($idx, $dataPart) = each(%{$iohash->{helper}{cdm}{data}})) {
  6464. $data[$idx] = $iohash->{helper}{cdm}{data}{$idx};
  6465. }
  6466. $data = join('', @data);
  6467. $msg[3] = $data;
  6468. $rorg = $iohash->{helper}{cdm}{rorg};
  6469. $msg[2] = $rorg;
  6470. $msg = join(':', @msg);
  6471. $rorgname = $EnO_rorgname{$rorg};
  6472. if (!$rorgname) {
  6473. Log3 undef, 2, "EnOcean RORG $rorg received from $senderID unknown.";
  6474. return "";
  6475. }
  6476. delete $iohash->{helper}{cdm};
  6477. #####
  6478. #my %functionHash = (hash => $iohash, function => "cdm");
  6479. #RemoveInternalTimer(\%functionHash);
  6480. RemoveInternalTimer($hash->{helper}{timer}{helperClear}) if(exists $hash->{helper}{timer}{helperClear});
  6481. delete $hash->{helper}{timer}{helperClear} if (exists $hash->{helper}{timer}{helperClear});
  6482. #Log3 $IODev, 3, "EnOcean $IODev CDM concatenated DATA $data";
  6483. } else {
  6484. # wait for next data message part
  6485. return $IODev;
  6486. }
  6487. }
  6488. if($hash) {
  6489. $name = $hash->{NAME};
  6490. if ($rorg eq 'A5' && !(hex(substr($data, 6, 2)) & 8) &&
  6491. hex(substr($data, 0, 2)) >> 2 == 0x3F &&
  6492. (hex(substr($data, 0, 4)) >> 3 & 0x7F) == 0) {
  6493. # find Radio Link Test device
  6494. my $rltHash;
  6495. foreach my $dev (keys %defs) {
  6496. next if ($defs{$dev}{TYPE} ne 'EnOcean');
  6497. next if (!exists($attr{$dev}{subType}));
  6498. next if ($attr{$dev}{subType} ne 'radioLinkTest');
  6499. $rltHash = $defs{$dev};
  6500. last;
  6501. }
  6502. if (defined $rltHash) {
  6503. if ($rltHash != $hash) {
  6504. if (ReadingsVal($rltHash->{NAME}, 'state', '') =~ m/^standby$/) {
  6505. # device is temporarily subType radioLinkTest
  6506. @{$rltHash->{helper}{rlt}{oldDev}} = ($hash, $name, $hash->{DEF});
  6507. CommandModify(undef, "$name 00000000");
  6508. delete $modules{EnOcean}{defptr}{$hash->{DEF}};
  6509. $hash = $rltHash;
  6510. $name = $hash->{NAME};
  6511. CommandModify(undef, "$name $senderID");
  6512. $modules{EnOcean}{defptr}{$senderID} = $hash;
  6513. } else {
  6514. # Radio Link Test Devices not ready at the moment
  6515. return '';
  6516. }
  6517. }
  6518. Log3 $name, 4, "EnOcean received RLT Query messsage DATA: $data from DeviceID: $senderID";
  6519. } else {
  6520. Log3 $name, 4, "EnOcean received RLT Query messsage DATA: $data from DeviceID: $senderID";
  6521. }
  6522. } else {
  6523. Log3 $name, 4, "EnOcean $name received PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status";
  6524. }
  6525. $manufID = uc(AttrVal($name, "manufID", ""));
  6526. $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
  6527. $filelogName = "FileLog_$name";
  6528. #if ($IODev ne $hash->{IODev}{NAME}) {
  6529. # transceiver wrong
  6530. # Log3 $name, 4, "EnOcean $name locked telegram via $IODev PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status";
  6531. # return "";
  6532. #}
  6533. } else {
  6534. # SenderID unknown, created new device
  6535. Log3 undef, 5, "EnOcean received PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status";
  6536. my $learningMode = AttrVal($IODev, "learningMode", "demand");
  6537. my $ret = "UNDEFINED EnO_$senderID EnOcean $senderID $msg";
  6538. if ($rorgname =~ m/^GPCD|GPSD|SMLRNANS|SMREC|SIGNAL$/) {
  6539. Log3 undef, 4, "EnOcean Received $rorgname telegram to the unknown device with SenderID $senderID.";
  6540. return '';
  6541. } elsif ($rorg eq 'A5' &&
  6542. hex(substr($data, 0, 2)) >> 2 == 0x3F &&
  6543. (hex(substr($data, 0, 4)) >> 3 & 0x7F) == 0 &&
  6544. !(hex(substr($data, 6, 2)) & 8)) {
  6545. # find Radio Link Test device
  6546. my $rltHash;
  6547. foreach my $dev (keys %defs) {
  6548. next if ($defs{$dev}{TYPE} ne 'EnOcean');
  6549. next if (!exists($attr{$dev}{subType}));
  6550. next if ($attr{$dev}{subType} ne 'radioLinkTest');
  6551. $rltHash = $defs{$dev};
  6552. last;
  6553. }
  6554. if ($rltHash) {
  6555. return '' if (ReadingsVal($rltHash->{NAME}, 'state', '') !~ m/^standby$/);
  6556. $hash = $rltHash;
  6557. $name = $hash->{NAME};
  6558. CommandModify(undef, "$name $senderID");
  6559. $modules{EnOcean}{defptr}{$senderID} = $hash;
  6560. $filelogName = "FileLog_$name";
  6561. Log3 $name, 4, "EnOcean RLT Query messsage DATA: $data from SenderID: $senderID received";
  6562. $manufID = uc(AttrVal($name, "manufID", ""));
  6563. $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
  6564. } else {
  6565. Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and RLT Query message, please define it.";
  6566. return $ret;
  6567. }
  6568. } elsif (exists $iohash->{helper}{teachConfirmWaitHash}) {
  6569. # teach-in response with confirm telegram, assign remote device
  6570. $hash = $iohash->{helper}{teachConfirmWaitHash};
  6571. $name = $hash->{NAME};
  6572. # substitute subDef with DEF
  6573. delete $modules{EnOcean}{defptr}{$hash->{DEF}};
  6574. $modules{EnOcean}{defptr}{$senderID} = $hash;
  6575. $attr{$name}{subDef} = $hash->{DEF};
  6576. $subDef = $attr{$name}{subDef};
  6577. $hash->{DEF} = $senderID;
  6578. $attr{$name}{comMode} = "confirm";
  6579. $manufID = uc(AttrVal($name, "manufID", ""));
  6580. $filelogName = "FileLog_$name";
  6581. # clear teach-in request
  6582. delete $iohash->{helper}{teachConfirmWaitHash};
  6583. # store changes
  6584. EnOcean_CommandSave(undef, undef);
  6585. push @event, "3:teach:4BS teach-in accepted";
  6586. Log3 $name, 2, "EnOcean $name remote device with SenderID $senderID assigned";
  6587. } elsif ($learningMode eq "demand" && $iohash->{Teach}) {
  6588. Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
  6589. return $ret;
  6590. } elsif ($learningMode eq "nearfield" && $iohash->{Teach} && $RSSI <= 60) {
  6591. Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
  6592. return $ret;
  6593. } elsif ($learningMode eq "always") {
  6594. if ($rorgname =~ m/^UTE|GPTI|GPTR$/) {
  6595. if ($iohash->{Teach}) {
  6596. Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
  6597. return $ret;
  6598. } else {
  6599. Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, activate learning mode.";
  6600. return "";
  6601. }
  6602. } elsif ($rorgname =~ m/^SMLRNREQ$/) {
  6603. if ($iohash->{SmartAckLearn}) {
  6604. Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
  6605. return $ret;
  6606. } else {
  6607. Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, activate learning mode.";
  6608. return "";
  6609. }
  6610. } else {
  6611. Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
  6612. return $ret;
  6613. }
  6614. } else {
  6615. Log3 undef, 4, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, activate learning mode.";
  6616. return "";
  6617. }
  6618. }
  6619. } elsif ($packetType == 2) {
  6620. # packet type RESPONSE
  6621. #EnOcean:PacketType:ResposeCode:MessageData:OptionalData
  6622. (undef, undef, $funcNumber, $data, $odata) = @msg;
  6623. $data = defined($data) ? $data : '';
  6624. $odata = defined($odata) ? $odata : '';
  6625. my %codes = (
  6626. "00" => "OK",
  6627. "01" => "ERROR",
  6628. "02" => "NOT_SUPPORTED",
  6629. "03" => "WRONG_PARAM",
  6630. "04" => "OPERATION_DENIED",
  6631. "82" => "FLASH_HW_ERROR",
  6632. "90" => "BASEID_OUT_OF_RANGE",
  6633. "91" => "BASEID_MAX_REACHED",
  6634. );
  6635. my $rcTxt = $codes{$funcNumber} if($codes{$funcNumber});
  6636. if($hash) {
  6637. $name = $hash->{NAME};
  6638. $funcNumber = hex($funcNumber);
  6639. Log3 $name, $funcNumber == 0 ? 5 : 2, "EnOcean $name RESPONSE: $rcTxt DATA: $data ODATA: $odata";
  6640. return $name;
  6641. } else {
  6642. Log3 undef, $funcNumber == 0 ? 5 : 2, "EnOcean RESPONSE: $rcTxt DATA: $data ODATA: $odata";
  6643. return "";
  6644. }
  6645. } elsif ($packetType == 4) {
  6646. # packet type EVENT
  6647. #EnOcean:PacketType:EventCode:MessageData
  6648. (undef, undef, $funcNumber, $data) = @msg;
  6649. Log3 undef, 5, "EnOcean EventCode: $funcNumber DATA: $data";
  6650. $funcNumber = hex($funcNumber);
  6651. if ($funcNumber == 2) {
  6652. # smart Ack confirm learn
  6653. my ($priority, $rorg, $func, $type, $postmasterID, $hopCount);
  6654. my $responseTime = 150;
  6655. my $sendData = '';
  6656. my $sendHash = defined($hash) ? $hash : $iohash;
  6657. my $sendName = $sendHash->{NAME};
  6658. $data =~ m/^(..)(....)(..)(..)(..)(..)(........)(........)(..)$/;
  6659. ($priority, $manufID, $rorg, $func, $type, $RSSI, $postmasterID, $senderID, $hopCount) = (hex($1), $2, $3, $4, $5, hex($6), $7, $8, $9);
  6660. #Log3 undef, 2, "EnOcean IOHASH: $iohash PRIORITY: $priority SmartAckLearn: " . (exists($iohash->{SmartAckLearn}) ? 1 : 0) .
  6661. # " SmartAckLearnWait: " . (exists($iohash->{helper}{smartAckLearnWait}) ? $iohash->{helper}{smartAckLearnWait} : '');
  6662. if ($iohash->{SmartAckLearn} ||
  6663. exists($iohash->{helper}{smartAckLearnWait}) && $iohash->{helper}{smartAckLearnWait} eq $sendName) {
  6664. my $subType = "$rorg.$func.$type";
  6665. if (exists $EnO_eepConfig{$subType}) {
  6666. # EEP supported
  6667. if (exists $modules{EnOcean}{defptr}{$senderID}) {
  6668. $hash = $modules{EnOcean}{defptr}{$senderID};
  6669. }
  6670. $rorgname = $EnO_rorgname{$rorg};
  6671. if($hash) {
  6672. delete $iohash->{helper}{smartAckLearnWait};
  6673. $name = $hash->{NAME};
  6674. $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
  6675. if (($priority & 15) == 15) {
  6676. # device exists, learn OUT
  6677. # send response, to delete mailbox
  6678. $rorg = substr(AttrVal($name, "eep", ' '), 0, 2);
  6679. $sendData = sprintf "00%04X20", $responseTime;
  6680. EnOcean_SndRadio(undef, $hash, 2, $rorg, $sendData, $subDef, '00', $hash->{DEF});
  6681. Log3 $name, 2, "EnOcean $name Smart Ack teach-out send";
  6682. Log3 $name, 2, "EnOcean $name device $name deleted";
  6683. CommandDelete(undef, $name);
  6684. EnOcean_CommandSave(undef, undef);
  6685. return '';
  6686. } else {
  6687. # config device
  6688. Log3 $name, 5, "EnOcean $name received PacketType: $packetType EventCode: $funcNumber DATA: $data";
  6689. $attr{$name}{subType} = $EnO_eepConfig{$subType}{attr}{subType};
  6690. $attr{$name}{eep} = "$rorg-$func-$type";
  6691. $manufID = sprintf "%03X", hex($manufID) & 0x7FF;
  6692. $attr{$name}{manufID} = $manufID;
  6693. $manufID = $EnO_manuf{$manufID} if(exists $EnO_manuf{$manufID});
  6694. $attr{$name}{postmasterID} = $postmasterID;
  6695. $hash->{SmartAckRSSI} = $RSSI;
  6696. $attr{$name}{teachMethod} = 'smartAck';
  6697. foreach my $attrCntr (keys %{$EnO_eepConfig{$subType}{attr}}) {
  6698. if ($attrCntr ne "subDef") {
  6699. $attr{$name}{$attrCntr} = $EnO_eepConfig{$subType}{attr}{$attrCntr};
  6700. }
  6701. }
  6702. #if (defined AttrVal($name, 'subDef', undef)) {
  6703. # $subDef = $attr{$name}{subDef};
  6704. #} else {
  6705. #$subDef = $postmasterID;
  6706. #$attr{$name}{subDef} = $postmasterID;
  6707. #}
  6708. $subDef = '0' x 8;
  6709. $attr{$name}{subDef} = '0' x 8;
  6710. # sent response to create mailbox
  6711. $sendData = sprintf "00%04X00", $responseTime;
  6712. EnOcean_SndRadio(undef, $hash, 2, $rorg, $sendData, $subDef, '00', $hash->{DEF});
  6713. readingsSingleUpdate($hash, "teach", "Smart Ack teach-in accepted EEP $rorg-$func-$type Manufacturer: $manufID", 1);
  6714. Log3 $name, 2, "EnOcean $name Smart Ack teach-in accepted EEP $rorg-$func-$type Manufacturer: $manufID";
  6715. EnOcean_CommandSave(undef, undef);
  6716. return $name;
  6717. }
  6718. } else {
  6719. # device unknown
  6720. if (($priority & 5) == 5) {
  6721. # Smart Ack priority ok (place for mailbox, good RSSI, Local)
  6722. Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and Smart Ack learn In message, please define it.";
  6723. return "UNDEFINED EnO_$senderID EnOcean $senderID $msg";
  6724. } elsif (($priority & 5) == 1) {
  6725. # Smart Ack no place for mailbox
  6726. $sendData = sprintf "00%04X12", $responseTime;
  6727. EnOcean_SndRadio(undef, $sendHash, 2, $rorg, $sendData, $postmasterID, '00', $senderID);
  6728. Log3 $sendName, 2, "EnOcean $sendName Smart Ack learn in from SenderID $senderID Discard learn in, postmaster has no place for further mailbox";
  6729. return '';
  6730. } else {
  6731. # Smart Ack priority to low
  6732. $sendData = sprintf "00%04X13", $responseTime;
  6733. EnOcean_SndRadio(undef, $sendHash, 2, $rorg, $sendData, $postmasterID, '00', $senderID);
  6734. Log3 $sendName, 2, "EnOcean $sendName Smart Ack learn in from SenderID $senderID Discard learn in, priority to low";
  6735. return '';
  6736. }
  6737. }
  6738. } else {
  6739. # EEP not supported
  6740. # sent response
  6741. $sendData = sprintf "00%04X11", $responseTime;
  6742. EnOcean_SndRadio(undef, $sendHash, 2, $rorg, $sendData, $postmasterID, '00', $senderID);
  6743. Log3 $sendName, 2, "EnOcean $sendName Smart Ack learn in from SenderID $senderID with EEP $rorg-$func-$type not supported";
  6744. return '';
  6745. }
  6746. } else {
  6747. # smart ack learn not activated
  6748. $sendData = sprintf "00%04XFF", $responseTime;
  6749. EnOcean_SndRadio(undef, $sendHash, 2, $rorg, $sendData, $postmasterID, '00', $senderID);
  6750. Log3 $sendName, 2, "EnOcean $sendName Smart Ack learn in from SenderID $senderID received, activate learning";
  6751. return '';
  6752. }
  6753. }
  6754. } elsif ($packetType == 6) {
  6755. # packet type SMART ACK
  6756. #EnOcean:PacketType:SmartAckCode:MessageData
  6757. (undef, undef, $funcNumber, $data) = @msg;
  6758. if($hash) {
  6759. $name = $hash->{NAME};
  6760. $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
  6761. Log3 $name, 2, "EnOcean $name received PacketType: $packetType Function Number: $funcNumber DATA: $data";
  6762. } else {
  6763. Log3 undef, 2, "EnOcean received PacketType: $packetType Function Number: $funcNumber DATA: $data";
  6764. return "";
  6765. }
  6766. $funcNumber = hex($funcNumber);
  6767. } elsif ($packetType == 7) {
  6768. # packet type REMOTE_MAN_COMMAND
  6769. #EnOcean:PacketType:RORG:MessageData:SourceID:DestinationID:FunctionNumber:ManufacturerID:RSSI:Delay
  6770. (undef, undef, $rorg, $data, $senderID, $destinationID, $funcNumber, $manufID, $RSSI, $delay) = @msg;
  6771. if (exists $modules{EnOcean}{defptr}{$senderID}) {
  6772. $hash = $modules{EnOcean}{defptr}{$senderID};
  6773. $name = $hash->{NAME};
  6774. if (!exists $attr{$name}{remoteID}) {
  6775. $attr{$name}{remoteID} = $senderID;
  6776. Log3 $name, 2, "EnOcean $name remoteID $senderID assigned";
  6777. if (exists($iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash}) &&
  6778. $iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash} == $hash) {
  6779. delete $iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash};
  6780. }
  6781. }
  6782. } elsif (exists($iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash})) {
  6783. # the remoteID is assigned to the requesting device
  6784. $hash = $iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash};
  6785. $name = $hash->{NAME};
  6786. $subDef = '0' x 8;
  6787. $attr{$name}{remoteID} = $senderID;
  6788. $modules{EnOcean}{defptr}{$senderID} = $hash;
  6789. delete $iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash};
  6790. Log3 $name, 2, "EnOcean $name remoteID $senderID assigned";
  6791. #EnOcean_CommandSave(undef, undef);
  6792. } elsif ($destinationID ne 'FFFFFFFF') {
  6793. $hash = $modules{EnOcean}{defptr}{$destinationID};
  6794. }
  6795. #$funcNumber = substr($funcNumber, 1);
  6796. if($hash) {
  6797. $name = $hash->{NAME};
  6798. $manufID = substr($manufID, 1);
  6799. $rorgname = $EnO_rorgname{$rorg};
  6800. $subDef = '0' x 8;
  6801. Log3 $name, 4, "EnOcean $name received PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID
  6802. DestinationID $destinationID Function Number: " . substr($funcNumber, 1) . " ManufacturerID: $manufID";
  6803. $delay = hex($delay);
  6804. $funcNumber = hex($funcNumber);
  6805. $RSSI = hex($RSSI);
  6806. } else {
  6807. if (hex($funcNumber) == 4) {
  6808. #Log3 undef, 1, "EnOcean received remote management query ID from unknown SenderID $senderID, please define device.";
  6809. #return "UNDEFINED EnO_$senderID EnOcean $senderID $msg";
  6810. return '';
  6811. } else {
  6812. return '';
  6813. }
  6814. }
  6815. }
  6816. my $eep = AttrVal($name, "eep", undef);
  6817. my $smartAckLearn = $hash->{IODev}{SmartAckLearn};
  6818. my $teach = $hash->{IODev}{Teach};
  6819. my ($deleteDevice, $oldDevice);
  6820. if (AttrVal($name, "secLevel", "off") =~ m/^encapsulation|encryption$/ &&
  6821. AttrVal($name, "secMode", "") =~ m/^rcv|biDir$/) {
  6822. if ($rorg eq "30" || $rorg eq "31") {
  6823. Log3 $name, 5, "EnOcean $name secure data RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status";
  6824. ($err, $rorg, $data) = EnOcean_sec_convertToNonsecure($hash, $rorg, $data);
  6825. if (defined $err) {
  6826. Log3 $name, 2, "EnOcean $name security ERROR: $err";
  6827. return "";
  6828. }
  6829. } elsif ($rorg eq "35") {
  6830. # pass second teach-in telegram
  6831. } else {
  6832. Log3 $name, 2, "EnOcean $name unsecure telegram locked";
  6833. return "";
  6834. }
  6835. if ($rorg eq "32") {
  6836. if (defined $eep) {
  6837. # reconstruct RORG
  6838. $rorg = substr($eep, 0, 2);
  6839. Log3 $name, 5, "EnOcean $name decrypted data RORG: 32 >> $rorg DATA: $data SenderID: $senderID STATUS: $status";
  6840. } else {
  6841. # Teach-In telegram expected
  6842. #####
  6843. # telegram analyse needed >> 1BS, 4BS, UTE
  6844. if (length($data) == 14) {
  6845. # UTE
  6846. $rorg = "D4";
  6847. } elsif (length($data) == 8) {
  6848. # 4BS
  6849. $rorg = "A5";
  6850. } elsif (length($data) == 2) {
  6851. # 1BS
  6852. $rorg = "D5";
  6853. } else {
  6854. Log3 $name, 2, "EnOcean $name security teach-in failed, UTE, 4BS or 1BS teach-in message is missing";
  6855. return "";
  6856. }
  6857. }
  6858. }
  6859. }
  6860. if ($rorg eq "A6") {
  6861. # addressing destination telegram (ADT)
  6862. # reconstruct RORG, DATA
  6863. $data =~ m/^(..)(.*)(........)$/;
  6864. ($rorg, $data) = ($1, $2);
  6865. $rorgname = $EnO_rorgname{$rorg};
  6866. if (!$rorgname) {
  6867. Log3 undef, 1, "EnOcean RORG $rorg received from $senderID unknown.";
  6868. return "";
  6869. }
  6870. if ($destinationID ne $3) {
  6871. Log3 $name, 1, "EnOcean $name ADT DestinationID wrong.";
  6872. return "";
  6873. }
  6874. Log3 $name, 1, "EnOcean $name ADT decapsulation RORG: $rorg DATA: $data DestinationID: $3";
  6875. }
  6876. # compute data
  6877. # extract data bytes $db[x] ... $db[0]
  6878. my @db;
  6879. my $dbCntr = 0;
  6880. for (my $strCntr = length($data) / 2 - 1; $strCntr >= 0; $strCntr --) {
  6881. $db[$dbCntr] = hex substr($data, $strCntr * 2, 2);
  6882. $dbCntr ++;
  6883. }
  6884. my $model = AttrVal($name, "model", "");
  6885. my $st = AttrVal($name, "subType", "");
  6886. my $subtypeReading = AttrVal($name, "subTypeReading", undef);
  6887. $st = $subtypeReading if (defined $subtypeReading);
  6888. if ($rorg eq "F6") {
  6889. # RPS Telegram
  6890. my $event = "state";
  6891. my $nu = (hex($status) & 0x10) >> 4;
  6892. # unused flags (AFAIK)
  6893. #push @event, "1:T21:".((hex($status) & 0x20) >> 5);
  6894. #push @event, "1:NU:$nu";
  6895. if ($st eq "FRW" || $st eq "smokeDetector.02") {
  6896. # smoke detector
  6897. if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} != $db[0] || ReadingsVal($name, 'alarm', '') eq 'dead_sensor') {
  6898. if ($db[0] == 0x30) {
  6899. push @event, "3:alarm:off";
  6900. push @event, "3:battery:low";
  6901. $msg = ReadingsVal($name, 'state', 'off');
  6902. } elsif ($db[0] == 0x10) {
  6903. push @event, "3:battery:ok";
  6904. push @event, "3:alarm:smoke-alarm";
  6905. $msg = "smoke-alarm";
  6906. } elsif ($db[0] == 0) {
  6907. push @event, "3:alarm:off";
  6908. push @event, "3:battery:ok";
  6909. $msg = "off";
  6910. }
  6911. push @event, "3:$event:$msg";
  6912. $hash->{helper}{lastEvent} = $db[0];
  6913. }
  6914. RemoveInternalTimer($hash->{helper}{timer}{alarm}) if(exists $hash->{helper}{timer}{alarm});
  6915. @{$hash->{helper}{timer}{alarm}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
  6916. InternalTimer(gettimeofday() + 1440, 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0);
  6917. } elsif ($st eq "windSpeed.00") {
  6918. # wind speed threshold detector
  6919. if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} != $db[0] || ReadingsVal($name, 'alarm', '') eq 'dead_sensor') {
  6920. push @event, "3:alarm:off";
  6921. if ($db[0] == 0x30) {
  6922. push @event, "3:battery:low";
  6923. $msg = ReadingsVal($name, 'state', 'off');
  6924. } elsif ($db[0] == 0x10) {
  6925. push @event, "3:windSpeed:on";
  6926. push @event, "3:battery:ok";
  6927. $msg = "on";
  6928. } elsif ($db[0] == 0) {
  6929. push @event, "3:windSpeed:off";
  6930. push @event, "3:battery:ok";
  6931. $msg = "off";
  6932. }
  6933. push @event, "3:$event:$msg";
  6934. $hash->{helper}{lastEvent} = $db[0];
  6935. }
  6936. RemoveInternalTimer($hash->{helper}{timer}{alarm}) if(exists $hash->{helper}{timer}{alarm});
  6937. @{$hash->{helper}{timer}{alarm}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
  6938. InternalTimer(gettimeofday() + 1320, 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0);
  6939. } elsif ($model =~ m/FAE14|FHK14|FHK61$/) {
  6940. # heating/cooling relay FAE14, FHK14, untested
  6941. $event = "controllerMode";
  6942. if ($db[0] == 0x30) {
  6943. # night reduction 2 K
  6944. push @event, "3:energyHoldOff:holdoff";
  6945. $msg = "auto";
  6946. } elsif ($db[0] == 0x10) {
  6947. # off
  6948. push @event, "3:energyHoldOff:normal";
  6949. $msg = "off";
  6950. } elsif ($db[0] == 0x70) {
  6951. # on
  6952. push @event, "3:energyHoldOff:normal";
  6953. $msg = "auto";
  6954. } elsif ($db[0] == 0x50) {
  6955. # night reduction 4 K
  6956. push @event, "3:energyHoldOff:holdoff";
  6957. $msg = "auto";
  6958. }
  6959. push @event, "3:$event:$msg";
  6960. } elsif ($st eq "gateway") {
  6961. # Eltako switching, dimming
  6962. if ($db[0] == 0x70) {
  6963. $msg = "on";
  6964. } elsif ($db[0] == 0x50) {
  6965. $msg = "off";
  6966. } elsif ($db[0] == 0x30) {
  6967. $event = 'alert';
  6968. $msg = "on";
  6969. } elsif ($db[0] == 0x10) {
  6970. $event = 'alert';
  6971. $msg = "off";
  6972. }
  6973. push @event, "3:$event:$msg";
  6974. } elsif ($st eq "manufProfile" && $manufID eq "00D") {
  6975. # Eltako shutter
  6976. if ($db[0] == 0x70) {
  6977. # open
  6978. if ($model eq 'Eltako_FSB_ACK') {
  6979. push @event, "3:position:0";
  6980. push @event, "3:anglePos:" . AttrVal($name, "angleMin", -90);
  6981. }
  6982. push @event, "3:endPosition:open_ack";
  6983. $msg = "open_ack";
  6984. } elsif ($db[0] == 0x50) {
  6985. # closed
  6986. push @event, "3:position:100";
  6987. push @event, "3:anglePos:" . AttrVal($name, "angleMax", 90);
  6988. push @event, "3:endPosition:closed";
  6989. $msg = "closed";
  6990. } elsif ($db[0] == 0) {
  6991. # not reached or not available
  6992. push @event, "3:endPosition:not_reached";
  6993. $msg = "not_reached";
  6994. } elsif ($db[0] == 1) {
  6995. # up
  6996. push @event, "3:endPosition:not_reached";
  6997. $msg = "up";
  6998. } elsif ($db[0] == 2) {
  6999. # down
  7000. push @event, "3:endPosition:not_reached";
  7001. $msg = "down";
  7002. }
  7003. push @event, "3:$event:$msg";
  7004. } elsif ($st eq "occupSensor.01" && $model eq "tracker") {
  7005. # tracker
  7006. push @event, "3:button:" . ($db[0] & 0x70 ? "pressed" : "released");
  7007. } elsif ($st eq "switch.7F" && $manufID eq "00D") {
  7008. $msg = $EnO_ptm200btn[($db[0] & 0xE0) >> 5];
  7009. $msg .= "," . $EnO_ptm200btn[($db[0] & 0x0E) >> 1] if ($db[0] & 1);
  7010. $msg .= " released" if (!($db[0] & 0x10));
  7011. push @event, "3:buttons:" . ($db[0] & 0x10 ? "pressed" : "released");
  7012. if ($msg =~ m/A0/) {push @event, "3:channelA:A0";}
  7013. if ($msg =~ m/AI/) {push @event, "3:channelA:AI";}
  7014. if ($msg =~ m/B0/) {push @event, "3:channelB:B0";}
  7015. if ($msg =~ m/BI/) {push @event, "3:channelB:BI";}
  7016. if ($msg =~ m/C0/) {push @event, "3:channelC:C0";}
  7017. if ($msg =~ m/CI/) {push @event, "3:channelC:CI";}
  7018. if ($msg =~ m/D0/) {push @event, "3:channelD:D0";}
  7019. if ($msg =~ m/DI/) {push @event, "3:channelD:DI";}
  7020. push @event, "3:$event:$msg";
  7021. } else {
  7022. if ($nu) {
  7023. if ($st eq "keycard") {
  7024. # Key Card, not tested
  7025. $msg = "keycard_inserted" if ($db[0] == 112);
  7026. } elsif ($st eq "liquidLeakage") {
  7027. # liquid leakage sensor, not tested
  7028. $msg = "wet" if ($db[0] == 0x11);
  7029. } else {
  7030. # Theoretically there can be a released event with some of the A0, BI
  7031. # pins set, but with the plastic cover on this wont happen.
  7032. $msg = $EnO_ptm200btn[($db[0] & 0xE0) >> 5];
  7033. $msg .= "," . $EnO_ptm200btn[($db[0] & 0x0E) >> 1] if ($db[0] & 1);
  7034. $msg .= " released" if (!($db[0] & 0x10));
  7035. push @event, "3:buttons:" . ($db[0] & 0x10 ? "pressed" : "released");
  7036. if ($msg =~ m/A0/) {push @event, "3:channelA:A0";}
  7037. if ($msg =~ m/AI/) {push @event, "3:channelA:AI";}
  7038. if ($msg =~ m/B0/) {push @event, "3:channelB:B0";}
  7039. if ($msg =~ m/BI/) {push @event, "3:channelB:BI";}
  7040. if ($msg =~ m/C0/) {push @event, "3:channelC:C0";}
  7041. if ($msg =~ m/CI/) {push @event, "3:channelC:CI";}
  7042. if ($msg =~ m/D0/) {push @event, "3:channelD:D0";}
  7043. if ($msg =~ m/DI/) {push @event, "3:channelD:DI";}
  7044. }
  7045. } else {
  7046. if ($db[0] & 0xC0) {
  7047. # Only a Mechanical Handle is setting these bits when NU = 0
  7048. $msg = "closed" if ($db[0] == 0xF0);
  7049. $msg = "open" if ($db[0] == 0xE0);
  7050. $msg = "tilted" if ($db[0] == 0xD0);
  7051. $msg = "open_from_tilted" if ($db[0] == 0xC0);
  7052. } elsif ($st eq "keycard") {
  7053. $msg = "keycard_removed";
  7054. } elsif ($st eq "liquidLeakage") {
  7055. $msg = "dry";
  7056. } else {
  7057. $msg = (($db[0] & 0x10) ? "pressed" : "released");
  7058. push @event, "3:buttons:" . ($db[0] & 0x10 ? "pressed" : "released");
  7059. }
  7060. }
  7061. # released events are disturbing when using a remote, since it overwrites
  7062. # the "real" state immediately. In the case of an Eltako FSB14, FSB61 ...
  7063. # the state should remain released.
  7064. if ($msg =~ m/released$/ &&
  7065. AttrVal($name, "sensorMode", "switch") ne "pushbutton" &&
  7066. $model !~ m/(FT55|FSB.*|FSM12|FSM61|FTS12)$/) {
  7067. $event = "buttons";
  7068. $msg = "released";
  7069. } else {
  7070. push @event, "3:$event:$msg";
  7071. }
  7072. }
  7073. } elsif ($rorg eq "D5") {
  7074. # 1BS telegram
  7075. if ($st eq "radioLinkTest") {
  7076. # Radio Link Test (EEP A5-3F-00)
  7077. @{$hash->{helper}{rlt}{param}} = ('parse', $hash, $data, $subDef, 'master', $RSSI);
  7078. EnOcean_RLT($hash->{helper}{rlt}{param});
  7079. } else {
  7080. # Single Input Contact (EEP D5-00-01)
  7081. # [EnOcean EMCS, Eltako FTK, STM-250]
  7082. if (!($db[0] & 8)) {
  7083. # teach-in
  7084. $attr{$name}{eep} = "D5-00-01";
  7085. $attr{$name}{manufID} = "7FF";
  7086. $attr{$name}{subType} = "contact";
  7087. push @event, "3:teach:1BS teach-in accepted EEP D5-00-01 Manufacturer: no ID";
  7088. Log3 $name, 2, "EnOcean $name teach-in EEP D5-00-01 Manufacturer: no ID";
  7089. # store attr subType, manufID ...
  7090. EnOcean_CommandSave(undef, undef);
  7091. }
  7092. push @event, "3:state:" . ($db[0] & 1 ? "closed" : "open");
  7093. CommandDeleteReading(undef, "$name alarm");
  7094. if (AttrVal($name, "signOfLife", 'off') eq 'on') {
  7095. RemoveInternalTimer($hash->{helper}{timer}{alarm}) if(exists $hash->{helper}{timer}{alarm});
  7096. @{$hash->{helper}{timer}{alarm}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
  7097. InternalTimer(gettimeofday() + AttrVal($name, "signOfLifeInterval", 1980), 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0);
  7098. }
  7099. }
  7100. } elsif ($rorg eq "A5") {
  7101. # 4BS telegram
  7102. if (($db[0] & 8) == 0) {
  7103. # Teach-In telegram
  7104. if ($teach || AttrVal($hash->{IODev}{NAME}, "learningMode", "demand") eq "always" || $st eq "radioLinkTest") {
  7105. if ($db[0] & 0x80) {
  7106. # 4BS Teach-In telegram with EEP and manufacturer ID
  7107. my $func = sprintf "%02X", ($db[3] >> 2);
  7108. my $type = sprintf "%02X", ((($db[3] & 3) << 5) | ($db[2] >> 3));
  7109. my $mid = sprintf "%03X", ((($db[2] & 7) << 8) | $db[1]);
  7110. # manufID to account for vendor-specific features
  7111. $attr{$name}{manufID} = $mid;
  7112. $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
  7113. my $st = "A5.$func.$type";
  7114. $attr{$name}{eep} = "A5-$func-$type";
  7115. if ($db[0] & 0x10) {
  7116. # 4BS teach-in bidirectional response received
  7117. Log3 $name, 5, "EnOcean $name 4BS teach-in response message from $senderID received";
  7118. if ($teach && exists($hash->{IODev}{helper}{"4BSRespWait"}{$destinationID})) {
  7119. if ($db[0] & 0x40) {
  7120. # EEP supported
  7121. if ($db[0] & 0x20) {
  7122. # SenderID stored
  7123. Log3 $name, 2, "EnOcean $name 4BS teach-in accepted by $senderID";
  7124. push @event, "3:teach:4BS teach-in accepted EEP $rorg-$func-$type Manufacturer: $mid";
  7125. $attr{$name}{comMode} = "biDir";
  7126. $attr{$name}{destinationID} = "unicast";
  7127. # substitute subDef with DEF
  7128. $attr{$name}{subDef} = $hash->{DEF};
  7129. $hash->{DEF} = $senderID;
  7130. $modules{EnOcean}{defptr}{$senderID} = $hash;
  7131. delete $modules{EnOcean}{defptr}{$destinationID};
  7132. # clear teach-in request
  7133. delete $hash->{IODev}{helper}{"4BSRespWait"}{$destinationID};
  7134. # store attr subType, manufID ...
  7135. EnOcean_CommandSave(undef, undef);
  7136. } else {
  7137. # SenderID not stored / deleted
  7138. Log3 $name, 2, "EnOcean $name 4BS request not accepted by $senderID";
  7139. push @event, "3:teach:4BS request not accepted EEP $rorg-$func-$type Manufacturer: $mid";
  7140. }
  7141. } else {
  7142. # EEP not suppported
  7143. Log3 $name, 2, "EnOcean $name 4BS EEP not supported by $senderID";
  7144. push @event, "3:teach:4BS EEP not supported EEP $rorg-$func-$type Manufacturer: $mid";
  7145. }
  7146. } else {
  7147. Log3 $name, 2, "EnOcean $name 4BS teach-in response message from $senderID ignored";
  7148. }
  7149. } else {
  7150. # 4BS teach-in query
  7151. $attr{$name}{teachMethod} = '4BS';
  7152. if(exists $EnO_eepConfig{$st}{attr}) {
  7153. push @event, "3:teach:4BS teach-in accepted EEP A5-$func-$type Manufacturer: $mid";
  7154. Log3 $name, 2, "EnOcean $name 4BS teach-in accepted EEP A5-$func-$type Manufacturer: $mid";
  7155. foreach my $attrCntr (keys %{$EnO_eepConfig{$st}{attr}}) {
  7156. if ($attrCntr eq "subDef") {
  7157. if (!defined AttrVal($name, $attrCntr, undef)) {
  7158. $attr{$name}{$attrCntr} = EnOcean_CheckSenderID($EnO_eepConfig{$st}{attr}{$attrCntr}, $hash->{IODev}{NAME}, "00000000");
  7159. }
  7160. } else {
  7161. $attr{$name}{$attrCntr} = $EnO_eepConfig{$st}{attr}{$attrCntr};
  7162. }
  7163. }
  7164. if (exists($hash->{helper}{teachInWait}) && $hash->{helper}{teachInWait} =~ m/^4BS|STE$/) {
  7165. $attr{$filelogName}{logtype} = $EnO_eepConfig{$st}{GPLOT} . 'text'
  7166. if (exists $attr{$filelogName}{logtype});
  7167. EnOcean_CreateSVG(undef, $hash, undef);
  7168. delete $hash->{helper}{teachInWait};
  7169. }
  7170. $st = $EnO_eepConfig{$st}{attr}{subType};
  7171. } else {
  7172. push @event, "3:teach:4BS EEP not supported EEP A5-$func-$type Manufacturer: $mid";
  7173. Log3 $name, 2, "EnOcean $name 4BS EEP not supported EEP A5-$func-$type Manufacturer: $mid";
  7174. $attr{$name}{subType} = "raw";
  7175. $st = "raw";
  7176. }
  7177. if ($teach || $st eq "radioLinkTest") {
  7178. # bidirectional 4BS teach-in
  7179. if ($st eq "hvac.01" || $st eq "MD15") {
  7180. # EEP A5-20-01
  7181. $attr{$name}{comMode} = "biDir";
  7182. $attr{$name}{destinationID} = "unicast";
  7183. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
  7184. # teach-in response
  7185. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, "800FFFF0", $subDef, "00", $hash->{DEF});
  7186. Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
  7187. readingsSingleUpdate($hash, 'operationMode', 'setpointTemp', 0);
  7188. #####
  7189. #EnOcean_hvac_01Cmd($hash, $packetType, 128); # 128 == 20 degree C
  7190. } elsif ($st eq "hvac.02") {
  7191. # EEP A5-20-02 not supported
  7192. # teach-in response
  7193. $data = sprintf "%06X90", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
  7194. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, "00000000", "00", $hash->{DEF});
  7195. Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
  7196. } elsif ($st eq "hvac.03") {
  7197. # EEP A5-20-03 not supported
  7198. # teach-in response
  7199. $data = sprintf "%06X90", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
  7200. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, "00000000", "00", $hash->{DEF});
  7201. Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
  7202. } elsif ($st eq "hvac.04") {
  7203. # heating radiator valve actuating drive (EEP A5-20-04)
  7204. $attr{$name}{comMode} = "biDir";
  7205. $attr{$name}{destinationID} = "unicast";
  7206. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
  7207. # teach-in response
  7208. $data = sprintf "%06XF0", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
  7209. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, "00", $hash->{DEF});
  7210. Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
  7211. readingsSingleUpdate($hash, 'operationMode', 'setpointTemp', 0);
  7212. } elsif ($st eq "radioLinkTest") {
  7213. # Radio Link Test (EEP A5-3F-00)
  7214. if (ReadingsVal($name, "state", 'standby') eq 'standby') {
  7215. $attr{$name}{comMode} = "biDir";
  7216. $attr{$name}{destinationID} = "unicast";
  7217. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
  7218. # teach-in response, SenderID not stored
  7219. $data = sprintf "%06XD0", (hex($func) << 7 | hex($type)) << 11 | hex($attr{$name}{manufID});
  7220. #$data = sprintf "%06XD0", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
  7221. #$data = sprintf "%06XF0", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
  7222. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, "00", $hash->{DEF});
  7223. Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
  7224. @{$hash->{helper}{rlt}{param}} = ('start', $hash, undef, $subDef, 'master', $RSSI);
  7225. EnOcean_RLT($hash->{helper}{rlt}{param});
  7226. }
  7227. #} elsif ($st =~ m/^hvac\.1[0-1]$/) {
  7228. # EEP A5-20-10, A5-20-11
  7229. # teach-in response
  7230. # $data = sprintf "%06XF0", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
  7231. # EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, "00000000", "00", $hash->{DEF});
  7232. # Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
  7233. } elsif (AttrVal($name, "comMode", "") =~ m/^confirm|biDir$/) {
  7234. # confirm telegram requested, teach-in response sent
  7235. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
  7236. $data = sprintf "%06XF0", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
  7237. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, "00", $hash->{DEF});
  7238. Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
  7239. #} else {
  7240. # EEP not supported
  7241. # teach-in response
  7242. # $data = sprintf "%06X90", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
  7243. # EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, "00000000", "00", $hash->{DEF});
  7244. # Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
  7245. }
  7246. }
  7247. }
  7248. # store attr subType, manufID ...
  7249. EnOcean_CommandSave(undef, undef);
  7250. # delete standard readings
  7251. CommandDeleteReading(undef, "$name sensor[0-9]");
  7252. CommandDeleteReading(undef, "$name D[0-9]");
  7253. } else {
  7254. # 4BS Teach-In without EEP and manufacturer ID
  7255. push @event, "3:teach:4BS teach-in accepted: No EEP profile identifier and no Manufacturer ID";
  7256. Log3 $name, 2, "EnOcean $name 4BS teach-in accepted. No EEP profile identifier and no Manufacturer ID";
  7257. $attr{$name}{subType} = "raw";
  7258. $st = "raw";
  7259. }
  7260. } else {
  7261. Log3 $name, 4, "EnOcean $name teach-in with subType $st locked, set transceiver in teach mode.";
  7262. return "";
  7263. }
  7264. } elsif ($st eq "hvac.01" || $st eq "MD15") {
  7265. # Battery Powered Actuator (EEP A5-20-01)
  7266. # [Kieback&Peter MD15-FTL-xx]
  7267. push @event, "3:energyInput:" . (($db[2] & 0x40) ? "enabled" : "disabled");
  7268. my $battery = ($db[2] & 0x10) ? "ok" : "low";
  7269. my $energyStorage;
  7270. if ($db[2] & 0x20) {
  7271. $energyStorage = 'charged';
  7272. $battery = 'ok';
  7273. } else {
  7274. $energyStorage = 'empty';
  7275. }
  7276. push @event, "3:battery:$battery";
  7277. push @event, "3:energyStorage:$energyStorage";
  7278. my $roomTemp = ReadingsVal($name, "roomTemp", 20);
  7279. if ($db[2] & 4) {
  7280. CommandDeleteReading(undef, "$name roomTemp");
  7281. } else {
  7282. $roomTemp = $db[1] * 40 / 255;
  7283. push @event, "3:roomTemp:" . sprintf "%0.1f", $roomTemp;
  7284. }
  7285. my $setpoint = $db[3];
  7286. push @event, "3:setpoint:$setpoint";
  7287. my $maintenanceMode = ReadingsVal($name, "maintenanceMode", ($db[2] & 0x80) ? 'on' : 'off');
  7288. # if ($db[2] & 0x80) {
  7289. # $maintenanceMode = 'on' if (!defined($maintenanceMode));
  7290. # } else {
  7291. # $maintenanceMode = 'off';
  7292. # }
  7293. push @event, "3:cover:" . (($db[2] & 8) ? "open" : "closed");
  7294. push @event, "3:window:" . (($db[2] & 2) ? "open" : "closed");
  7295. push @event, "3:actuatorState:". (($db[2] & 1) ? "obstructed" : "ok");
  7296. push @event, "3:selfCtrl:" . (($db[0] & 4) ? "on" : "off");
  7297. my $functionSelect = 0;
  7298. my $setpointSelect = 0;
  7299. my $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
  7300. my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
  7301. my $setpointTempSet = ReadingsVal($name, "setpointTempSet", $setpointTemp);
  7302. my $temperature = ReadingsVal($name, 'temperature', $roomTemp);
  7303. if (!defined(AttrVal($name, "temperatureRefDev", undef))) {
  7304. if ($db[2] & 4) {
  7305. CommandDeleteReading(undef, "$name temperature");
  7306. } else {
  7307. $temperature = $roomTemp;
  7308. readingsSingleUpdate($hash, 'temperature', sprintf("%0.1f", $temperature), 1);
  7309. }
  7310. }
  7311. Log3 $name, 5, "EnOcean $name EnOcean_parse SPT: $setpointTemp SPTS: $setpointTempSet";
  7312. my $operationMode = ReadingsVal($name, "operationMode", 'setpointTemp');
  7313. my $setpointSummerMode = AttrVal($name, "setpointSummerMode", 0);
  7314. my $summerMode = AttrVal($name, "summerMode", "off");
  7315. my $timeDiff = EnOcean_TimeDiff(ReadingsTimestamp($name, 'wakeUpCycle', undef));
  7316. my $waitingCmds = ReadingsVal($name, "waitingCmds", "no_change");
  7317. my $wakeUpCycle = 600;
  7318. # calc wakeup cycle
  7319. if ($summerMode eq 'off') {
  7320. $summerMode = 0;
  7321. if ($timeDiff == 0) {
  7322. $wakeUpCycle = 1200;
  7323. } elsif ($timeDiff < 120) {
  7324. $wakeUpCycle = 120;
  7325. } elsif ($timeDiff > 1200) {
  7326. $wakeUpCycle = 1200;
  7327. } else {
  7328. $wakeUpCycle = int($timeDiff);
  7329. }
  7330. } else {
  7331. $summerMode = 8;
  7332. # ignore all commands
  7333. if ($waitingCmds ne "summerMode") {
  7334. $waitingCmds = "no_change";
  7335. CommandDeleteReading(undef, "$name waitingCmds");
  7336. }
  7337. if ($manufID eq '049') {
  7338. $wakeUpCycle = 28800;
  7339. } else {
  7340. $wakeUpCycle = 3600;
  7341. }
  7342. }
  7343. readingsSingleUpdate($hash, 'wakeUpCycle', $wakeUpCycle, 1);
  7344. # set alarm timer
  7345. CommandDeleteReading(undef, "$name alarm");
  7346. RemoveInternalTimer($hash->{helper}{timer}{alarm}) if(exists $hash->{helper}{timer}{alarm});
  7347. @{$hash->{helper}{timer}{alarm}} = ($hash, 'alarm', 'no_response_from_actuator', 1, 3);
  7348. InternalTimer(gettimeofday() + $wakeUpCycle * 1.1, "EnOcean_readingsSingleUpdate", $hash->{helper}{timer}{alarm}, 0);
  7349. my $actionCmd = AttrVal($name, "rcvRespAction", undef);
  7350. if (defined $actionCmd) {
  7351. my %specials = ("%ACTUATORSTATE" => (($db[2] & 1) ? "obstructed" : "ok"),
  7352. "%BATTERY" => $battery,
  7353. "%COVER" => (($db[2] & 8) ? "open" : "closed"),
  7354. "%ENERGYINPUT" => (($db[2] & 0x40) ? "enabled" : "disabled"),
  7355. "%ENERGYSTORAGE" => $energyStorage,
  7356. "%MAINTENANCEMODE" => $maintenanceMode,
  7357. "%NAME" => $name,
  7358. "%OPERATIONMODE" => $operationMode,
  7359. "%ROOMTEMP" => $roomTemp,
  7360. "%SELFCTRL" => (($db[0] & 4) ? "on" : "off"),
  7361. "%SETPOINT" => $setpoint,
  7362. "%SETPOINTTEMP" => $setpointTemp,
  7363. "%SUMMERMODE" => $summerMode,
  7364. "%TEMPERATURE" => $temperature,
  7365. "%WINDOW" => (($db[2] & 2) ? "open" : "closed"),
  7366. );
  7367. # action exec
  7368. $actionCmd = EvalSpecials($actionCmd, %specials);
  7369. my $ret = AnalyzeCommandChain(undef, $actionCmd);
  7370. Log3 $name, 2, "Encean $name rcvRespAction ERROR: $ret" if($ret);
  7371. $maintenanceMode = ReadingsVal($name, "maintenanceMode", ($db[2] & 0x80) ? 'on' : 'off');
  7372. $operationMode = ReadingsVal($name, "operationMode", 'setpointTemp');
  7373. $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
  7374. $setpointTempSet = ReadingsVal($name, "setpointTempSet", $setpointTemp);
  7375. $temperature = ReadingsVal($name, 'temperature', $roomTemp);
  7376. $waitingCmds = ReadingsVal($name, "waitingCmds", "no_change");
  7377. }
  7378. if ($waitingCmds eq "valveOpens") {
  7379. # deactivate PID regulator
  7380. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7381. $setpointSet = 100;
  7382. $db[2] = 0x20;
  7383. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7384. push @event, "3:maintenanceMode:valveOpend:runInit";
  7385. push @event, "3:operationMode:off";
  7386. CommandDeleteReading(undef, "$name setpointSet");
  7387. CommandDeleteReading(undef, "$name setpointTemp");
  7388. CommandDeleteReading(undef, "$name setpointTempSet");
  7389. CommandDeleteReading(undef, "$name waitingCmds");
  7390. $functionSelect = 1;
  7391. $waitingCmds = 0x20;
  7392. } elsif ($waitingCmds eq "valveCloses") {
  7393. if ($maintenanceMode eq "valveOpend:runInit") {
  7394. $setpointSet = 100;
  7395. $db[2] = 0x20;
  7396. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7397. push @event, "3:maintenanceMode:runInit";
  7398. push @event, "3:operationMode:off";
  7399. $functionSelect = 1;
  7400. $waitingCmds = 0x80;
  7401. } else {
  7402. $setpointSet = 0;
  7403. $db[2] = 0x20;
  7404. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7405. push @event, "3:maintenanceMode:valveClosed";
  7406. push @event, "3:operationMode:off";
  7407. CommandDeleteReading(undef, "$name waitingCmds");
  7408. $functionSelect = 1;
  7409. $waitingCmds = 0x10;
  7410. }
  7411. # stop PID regulator
  7412. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7413. CommandDeleteReading(undef, "$name setpointSet");
  7414. CommandDeleteReading(undef, "$name setpointTemp");
  7415. CommandDeleteReading(undef, "$name setpointTempSet");
  7416. } elsif ($waitingCmds eq "runInit") {
  7417. # deactivate PID regulator
  7418. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7419. $setpointSet = 0;
  7420. $db[2] = 0x20;
  7421. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7422. push @event, "3:maintenanceMode:runInit";
  7423. push @event, "3:operationMode:off";
  7424. push @event, "3:waitingCmds:$operationMode";
  7425. #CommandDeleteReading(undef, "$name setpointSet");
  7426. #CommandDeleteReading(undef, "$name setpointTemp");
  7427. #CommandDeleteReading(undef, "$name setpointTempSet");
  7428. #CommandDeleteReading(undef, "$name waitingCmds");
  7429. $functionSelect = 1;
  7430. $waitingCmds = 0x80;
  7431. } elsif ($waitingCmds eq "liftSet") {
  7432. # deactivate PID regulator
  7433. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7434. $setpointSet = 0;
  7435. $db[2] = 0x20;
  7436. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7437. push @event, "3:maintenanceMode:listSet";
  7438. push @event, "3:operationMode:off";
  7439. push @event, "3:waitingCmds:$operationMode";
  7440. #CommandDeleteReading(undef, "$name setpointSet");
  7441. #CommandDeleteReading(undef, "$name setpointTemp");
  7442. #CommandDeleteReading(undef, "$name setpointTempSet");
  7443. #CommandDeleteReading(undef, "$name waitingCmds");
  7444. $functionSelect = 1;
  7445. $waitingCmds = 0x40;
  7446. } elsif ($waitingCmds eq "setpoint") {
  7447. # deactivate PID regulator
  7448. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7449. if ($maintenanceMode eq "valveOpend:runInit") {
  7450. $setpointSet = 100;
  7451. $db[2] = 0x20;
  7452. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7453. push @event, "3:maintenanceMode:runInit";
  7454. push @event, "3:operationMode:off";
  7455. $functionSelect = 1;
  7456. $waitingCmds = 0x80;
  7457. } else {
  7458. $db[2] = (40 - $temperature) * 255 / 40;
  7459. push @event, "3:maintenanceMode:off";
  7460. push @event, "3:operationMode:setpoint";
  7461. CommandDeleteReading(undef, "$name setpointTemp");
  7462. CommandDeleteReading(undef, "$name setpointTempSet");
  7463. CommandDeleteReading(undef, "$name waitingCmds");
  7464. $waitingCmds = 0;
  7465. }
  7466. } elsif ($waitingCmds eq "setpointTemp") {
  7467. if ($maintenanceMode eq "valveOpend:runInit") {
  7468. # deactivate PID regulator
  7469. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7470. $setpointSet = 100;
  7471. $db[2] = 0x20;
  7472. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7473. push @event, "3:maintenanceMode:runInit";
  7474. push @event, "3:operationMode:off";
  7475. $functionSelect = 1;
  7476. $waitingCmds = 0x80;
  7477. } else {
  7478. if (AttrVal($name, "pidCtrl", 'on') eq 'on') {
  7479. # activate PID regulator
  7480. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'actuator', '');
  7481. $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
  7482. } else {
  7483. # deactivate PID regulator
  7484. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7485. # setpoint temperature
  7486. $setpointSet = $setpointTempSet * 255 / 40;
  7487. $setpointSelect = 4;
  7488. }
  7489. $setpointTemp = $setpointTempSet;
  7490. $db[2] = (40 - $temperature) * 255 / 40;
  7491. push @event, "3:setpointTemp:" . sprintf("%0.1f", $setpointTemp);
  7492. push @event, "3:maintenanceMode:off";
  7493. push @event, "3:operationMode:setpointTemp";
  7494. CommandDeleteReading(undef, "$name waitingCmds");
  7495. $waitingCmds = 0;
  7496. }
  7497. } elsif ($waitingCmds eq "summerMode") {
  7498. # deactivate PID regulator
  7499. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7500. $setpointSet = $setpointSummerMode;
  7501. $db[2] = (40 - $temperature) * 255 / 40;
  7502. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7503. push @event, "3:maintenanceMode:off";
  7504. push @event, "3:operationMode:summerMode";
  7505. #CommandDeleteReading(undef, "$name setpointSet");
  7506. CommandDeleteReading(undef, "$name setpointTemp");
  7507. CommandDeleteReading(undef, "$name setpointTempSet");
  7508. CommandDeleteReading(undef, "$name waitingCmds");
  7509. $waitingCmds = 0;
  7510. } elsif ($operationMode eq "setpoint") {
  7511. # deactivate PID regulator
  7512. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7513. if ($maintenanceMode eq "valveOpend:runInit") {
  7514. $setpointSet = 100;
  7515. $db[2] = 0x20;
  7516. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7517. push @event, "3:maintenanceMode:off";
  7518. push @event, "3:operationMode:setpoint";
  7519. $functionSelect = 1;
  7520. $waitingCmds = 0x80;
  7521. } else {
  7522. $db[2] = (40 - $temperature) * 255 / 40;
  7523. push @event, "3:maintenanceMode:off";
  7524. push @event, "3:operationMode:setpoint";
  7525. $waitingCmds = 0;
  7526. }
  7527. } elsif ($operationMode eq "setpointTemp") {
  7528. if ($maintenanceMode eq "valveOpend:runInit") {
  7529. # deactivate PID regulator
  7530. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7531. $setpointSet = 100;
  7532. $db[2] = 0x20;
  7533. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7534. push @event, "3:maintenanceMode:off";
  7535. push @event, "3:operationMode:setpointTemp";
  7536. $functionSelect = 1;
  7537. $waitingCmds = 0x80;
  7538. } else {
  7539. if (AttrVal($name, "pidCtrl", 'on') eq 'on') {
  7540. # activate PID regulator
  7541. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'actuator', '');
  7542. $setpointSet = ReadingsVal($name, "setpointSet", $setpointSet);
  7543. } else {
  7544. # deactivate PID regulator
  7545. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7546. # setpoint temperature
  7547. $setpointSet = $setpointTempSet * 255 / 40;
  7548. $setpointSelect = 4;
  7549. }
  7550. $db[2] = (40 - $temperature) * 255 / 40;
  7551. $setpointTemp = $setpointTempSet;
  7552. push @event, "3:setpointTemp:" . sprintf("%.1f", $setpointTemp);
  7553. push @event, "3:maintenanceMode:off";
  7554. push @event, "3:operationMode:setpointTemp";
  7555. $waitingCmds = 0;
  7556. }
  7557. } elsif ($operationMode eq "summerMode") {
  7558. # deactivate PID regulator
  7559. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7560. $setpointSet = $setpointSummerMode;
  7561. $db[2] = (40 - $temperature) * 255 / 40;
  7562. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7563. push @event, "3:maintenanceMode:off";
  7564. push @event, "3:operationMode:summerMode";
  7565. $waitingCmds = 0;
  7566. } elsif ($maintenanceMode eq "valveOpend:runInit") {
  7567. # deactivate PID regulator
  7568. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7569. $setpointSet = 100;
  7570. $db[2] = 0x20;
  7571. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7572. push @event, "3:maintenanceMode:valveOpend:runInit";
  7573. push @event, "3:operationMode:off";
  7574. #CommandDeleteReading(undef, "$name setpointSet");
  7575. #CommandDeleteReading(undef, "$name setpointTemp");
  7576. #CommandDeleteReading(undef, "$name setpointTempSet");
  7577. #CommandDeleteReading(undef, "$name waitingCmds");
  7578. $functionSelect = 1;
  7579. $waitingCmds = 0x20;
  7580. } elsif ($maintenanceMode eq "valveClosed") {
  7581. # stop PID regulator
  7582. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7583. $setpointSet = 0;
  7584. $db[2] = 0x20;
  7585. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7586. push @event, "3:maintenanceMode:valveClosed";
  7587. push @event, "3:operationMode:off";
  7588. #CommandDeleteReading(undef, "$name setpointSet");
  7589. #CommandDeleteReading(undef, "$name setpointTemp");
  7590. #CommandDeleteReading(undef, "$name setpointTempSet");
  7591. #CommandDeleteReading(undef, "$name waitingCmds");
  7592. $functionSelect = 1;
  7593. $waitingCmds = 0x10;
  7594. } else {
  7595. $db[2] = (40 - $temperature) * 255 / 40;
  7596. $waitingCmds = 0;
  7597. }
  7598. push @event, "3:state:T: " . sprintf("%0.1f", $temperature) . " SPT: " . sprintf("%.1f", $setpointTemp) . " SP: $setpoint";
  7599. # sent message to the actuator
  7600. $data = sprintf "%02X%02X%02X08", $setpointSet, $db[2], $waitingCmds | $summerMode | $setpointSelect | $functionSelect;
  7601. EnOcean_SndRadio(undef, $hash, $packetType, "A5", $data, $subDef, "00", $hash->{DEF});
  7602. } elsif ($st eq "hvac.04") {
  7603. # heating radiator valve actuating drive EEP A5-20-04)
  7604. my %failureCode = (
  7605. 17 => "measurement_error",
  7606. 18 => "battery_empty",
  7607. 20 => "frost_protection",
  7608. 33 => "blocked_valve",
  7609. 36 => "end_point_detection_error",
  7610. 40 => "no_valve",
  7611. 49 => "not_taught_in",
  7612. 53 => "no_response_from_controller",
  7613. 54 => "teach-in_error"
  7614. );
  7615. my $battery = "ok";
  7616. my %displayOrientation = (0 => 0, 90 => 1, 180 => 2, 270 => 3);
  7617. my $feedTemp = ReadingsVal($name, "feedTemp", 20);
  7618. my $roomTemp = ReadingsVal($name, "roomTemp", 20);
  7619. my $setpoint = $db[3];
  7620. push @event, "3:setpoint:$setpoint";
  7621. my $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
  7622. my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
  7623. my $setpointTempSet = ReadingsVal($name, "setpointTempSet", $setpointTemp);
  7624. my $temperature = ReadingsVal($name, "temperature", $roomTemp);
  7625. if ($db[0] & 2) {
  7626. if ($setpointTemp == $setpointTempSet) {
  7627. $setpointTemp = sprintf "%0.1f", ($db[2] * 20 / 255 + 10);
  7628. if ($setpointTemp != $setpointTempSet) {
  7629. # setpointTempSet has been changed by actuator
  7630. $setpointTempSet = $setpointTemp;
  7631. readingsSingleUpdate($hash, 'setpointTempSet', $setpointTempSet, 1);
  7632. }
  7633. } else {
  7634. # setpointTempSet has been changed by Fhem
  7635. $setpointTemp = sprintf "%0.1f", ($db[2] * 20 / 255 + 10);
  7636. }
  7637. push @event, "3:setpointTemp:$setpointTemp";
  7638. } else {
  7639. if ($db[0] & 0x80) {
  7640. # temperature measurement inactive
  7641. CommandDeleteReading(undef, "$name feedTemp");
  7642. } else {
  7643. $feedTemp = $db[2] * 60 / 255 + 20;
  7644. push @event, "3:feedTemp:" . sprintf("%0.1f", $feedTemp);
  7645. }
  7646. }
  7647. if ($db[0] & 1) {
  7648. # failure code
  7649. if (exists $failureCode{$db[1]}) {
  7650. push @event, "3:alarm:" . $failureCode{$db[1]};
  7651. $battery = "empty" if ($db[1] == 18);
  7652. } else {
  7653. CommandDeleteReading(undef, "$name alarm");
  7654. }
  7655. } else {
  7656. if ($db[0] & 0x80) {
  7657. # temperature measurement inactive
  7658. CommandDeleteReading(undef, "$name roomTemp");
  7659. } else {
  7660. # room temperature
  7661. $roomTemp = sprintf("%0.1f", ($db[1] * 20 / 255 + 10));
  7662. push @event, "3:roomTemp:$roomTemp";
  7663. CommandDeleteReading(undef, "$name alarm");
  7664. }
  7665. }
  7666. if (!defined(AttrVal($name, "temperatureRefDev", undef))) {
  7667. if ($db[0] & 0x80) {
  7668. # temperature measurement needed, activate temperature measurement
  7669. $attr{$name}{measurementCtrl} = 'enable';
  7670. EnOcean_CommandSave(undef, undef);
  7671. } else {
  7672. $temperature = $roomTemp;
  7673. readingsSingleUpdate($hash, 'temperature', $temperature, 1);
  7674. #push @event, "3:temperature:$temperature";
  7675. }
  7676. }
  7677. push @event, "3:measurementState:" . ($db[0] & 0x80 ? "inactive" : "active");
  7678. push @event, "3:blockKey:" . ($db[0] & 4 ? "yes" : "no");
  7679. push @event, "3:battery:$battery";
  7680. #push @event, "3:state:T: $temperature SPT: $setpointTemp SP: $setpoint";
  7681. if ($db[0] & 0x40) {
  7682. # status request
  7683. # action needed?
  7684. }
  7685. Log3 $name, 5, "EnOcean $name EnOcean_parse SPT: $setpointTemp SPTS: $setpointTempSet";
  7686. my $activatePID = AttrVal($name, 'pidCtrl', 'on') eq 'on' ? 'actuator' : 'stop';
  7687. my $blockKey = ((AttrVal($name, "blockKey", 'no') eq 'yes') ? 1 : 0) << 2;
  7688. my $displayOrientation = $displayOrientation{AttrVal($name, "displayOrientation", 0)} << 4;
  7689. my $maintenanceMode = ReadingsVal($name, "maintenanceMode", "off");
  7690. my $measurementCtrl = (AttrVal($name, 'measurementCtrl', 'enable') eq 'enable') ? 0 : 0x40;
  7691. #my $operationMode = ReadingsVal($name, "operationMode", "off");
  7692. my $operationMode = ReadingsVal($name, "operationMode", ((AttrVal($name, 'pidCtrl', 'on') eq 'on') ? 'setpointTemp' : 'setpoint'));
  7693. my $summerMode = AttrVal($name, "summerMode", "off");
  7694. my $waitingCmds = ReadingsVal($name, "waitingCmds", "no_change");
  7695. my $wakeUpCycle = $wakeUpCycle{AttrVal($name, "wakeUpCycle", 300)};
  7696. if ($summerMode eq 'off' && $wakeUpCycle >= 50) {
  7697. # set default Wake-up Cycle (300 s)
  7698. $wakeUpCycle = 9;
  7699. } elsif ($summerMode eq 'on') {
  7700. if ($waitingCmds ne "summerMode") {
  7701. $waitingCmds = "no_change";
  7702. CommandDeleteReading(undef, "$name waitingCmds");
  7703. }
  7704. $setpointSet = 100;
  7705. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7706. $wakeUpCycle = 50 if ($wakeUpCycle < 50);
  7707. }
  7708. my $actionCmd = AttrVal($name, "rcvRespAction", undef);
  7709. if (defined $actionCmd) {
  7710. my %specials = ("%BATTERY" => $battery,
  7711. "%FEEDTEMP" => $feedTemp,
  7712. "%MAINTENANCEMODE" => $maintenanceMode,
  7713. "%NAME" => $name,
  7714. "%OPERATIONMODE" => $operationMode,
  7715. "%ROOMTEMP" => $roomTemp,
  7716. "%SETPOINT" => $setpoint,
  7717. "%SETPOINTTEMP" => $setpointTemp,
  7718. "%SUMMERMODE" => $summerMode,
  7719. "%TEMPERATURE" => $temperature,
  7720. );
  7721. # action exec
  7722. $actionCmd = EvalSpecials($actionCmd, %specials);
  7723. my $ret = AnalyzeCommandChain(undef, $actionCmd);
  7724. Log3 $name, 2, "EnOcean $name rcvRespAction ERROR: $ret" if($ret);
  7725. $maintenanceMode = ReadingsVal($name, "maintenanceMode", 'off');
  7726. $operationMode = ReadingsVal($name, "operationMode", ((AttrVal($name, 'pidCtrl', 'on') eq 'on') ? 'setpointTemp' : 'setpoint'));
  7727. $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
  7728. $setpointTempSet = ReadingsVal($name, "setpointTempSet", $setpointTemp);
  7729. $temperature = ReadingsVal($name, 'temperature', $roomTemp);
  7730. $waitingCmds = ReadingsVal($name, "waitingCmds", "no_change");
  7731. }
  7732. if ($waitingCmds eq "valveOpens") {
  7733. # deactivate PID regulator
  7734. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7735. $setpointSet = 100;
  7736. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7737. push @event, "3:maintenanceMode:valveOpend:runInit";
  7738. push @event, "3:operationMode:off";
  7739. CommandDeleteReading(undef, "$name setpointSet");
  7740. CommandDeleteReading(undef, "$name setpointTemp");
  7741. CommandDeleteReading(undef, "$name setpointTempSet");
  7742. CommandDeleteReading(undef, "$name waitingCmds");
  7743. $waitingCmds = 1;
  7744. } elsif ($waitingCmds eq "valveCloses") {
  7745. if ($maintenanceMode eq "valveOpend:runInit") {
  7746. $setpointSet = 100;
  7747. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7748. push @event, "3:maintenanceMode:runInit";
  7749. push @event, "3:operationMode:off";
  7750. $waitingCmds = 2;
  7751. } else {
  7752. $setpointSet = 0;
  7753. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7754. push @event, "3:maintenanceMode:valveClosed";
  7755. push @event, "3:operationMode:off";
  7756. CommandDeleteReading(undef, "$name waitingCmds");
  7757. $waitingCmds = 3;
  7758. }
  7759. # stop PID regulator
  7760. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7761. CommandDeleteReading(undef, "$name setpointSet");
  7762. CommandDeleteReading(undef, "$name setpointTemp");
  7763. CommandDeleteReading(undef, "$name setpointTempSet");
  7764. } elsif ($waitingCmds eq "runInit") {
  7765. # deactivate PID regulator
  7766. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7767. $setpointSet = 100;
  7768. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7769. push @event, "3:maintenanceMode:runInit";
  7770. push @event, "3:operationMode:off";
  7771. CommandDeleteReading(undef, "$name setpointSet");
  7772. CommandDeleteReading(undef, "$name setpointTemp");
  7773. CommandDeleteReading(undef, "$name setpointTempSet");
  7774. CommandDeleteReading(undef, "$name waitingCmds");
  7775. $waitingCmds = 2;
  7776. } elsif ($waitingCmds eq "setpoint") {
  7777. # deactivate PID regulator
  7778. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7779. if ($maintenanceMode eq "valveOpend:runInit") {
  7780. $setpointSet = 100;
  7781. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7782. push @event, "3:maintenanceMode:runInit";
  7783. push @event, "3:operationMode:off";
  7784. $waitingCmds = 2;
  7785. } else {
  7786. push @event, "3:maintenanceMode:off";
  7787. push @event, "3:operationMode:setpoint";
  7788. #CommandDeleteReading(undef, "$name setpointSet");
  7789. CommandDeleteReading(undef, "$name setpointTemp");
  7790. CommandDeleteReading(undef, "$name setpointTempSet");
  7791. CommandDeleteReading(undef, "$name waitingCmds");
  7792. $waitingCmds = 0;
  7793. }
  7794. } elsif ($waitingCmds eq "setpointTemp") {
  7795. if ($maintenanceMode eq "valveOpend:runInit") {
  7796. # deactivate PID regulator
  7797. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7798. $setpointSet = 100;
  7799. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7800. push @event, "3:maintenanceMode:runInit";
  7801. push @event, "3:operationMode:off";
  7802. $waitingCmds = 2;
  7803. } else {
  7804. # activate PID regulator
  7805. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, $activatePID, '');
  7806. $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
  7807. $setpointTemp = $setpointTempSet;
  7808. push @event, "3:setpointTemp:$setpointTemp";
  7809. push @event, "3:maintenanceMode:off";
  7810. push @event, "3:operationMode:setpointTemp";
  7811. #CommandDeleteReading(undef, "$name setpointSet");
  7812. #CommandDeleteReading(undef, "$name setpointTempSet");
  7813. CommandDeleteReading(undef, "$name waitingCmds");
  7814. $waitingCmds = 0;
  7815. }
  7816. } elsif ($waitingCmds eq "summerMode") {
  7817. # deactivate PID regulator
  7818. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7819. $setpointSet = 100;
  7820. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7821. push @event, "3:maintenanceMode:off";
  7822. push @event, "3:operationMode:summerMode";
  7823. #CommandDeleteReading(undef, "$name setpointSet");
  7824. CommandDeleteReading(undef, "$name setpointTemp");
  7825. CommandDeleteReading(undef, "$name setpointTempSet");
  7826. CommandDeleteReading(undef, "$name waitingCmds");
  7827. $waitingCmds = 0;
  7828. } elsif ($operationMode eq "setpoint") {
  7829. # deactivate PID regulator
  7830. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7831. if ($maintenanceMode eq "valveOpend:runInit") {
  7832. $setpointSet = 100;
  7833. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7834. push @event, "3:maintenanceMode:off";
  7835. push @event, "3:operationMode:setpoint";
  7836. $waitingCmds = 2;
  7837. } else {
  7838. push @event, "3:maintenanceMode:off";
  7839. push @event, "3:operationMode:setpoint";
  7840. #CommandDeleteReading(undef, "$name setpointSet");
  7841. #CommandDeleteReading(undef, "$name setpointTemp");
  7842. #CommandDeleteReading(undef, "$name setpointTempSet");
  7843. #CommandDeleteReading(undef, "$name waitingCmds");
  7844. $waitingCmds = 0;
  7845. }
  7846. } elsif ($operationMode eq "setpointTemp") {
  7847. if ($maintenanceMode eq "valveOpend:runInit") {
  7848. # deactivate PID regulator
  7849. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7850. $setpointSet = 100;
  7851. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7852. push @event, "3:maintenanceMode:off";
  7853. push @event, "3:operationMode:setpointTemp";
  7854. $waitingCmds = 2;
  7855. } else {
  7856. # activate PID regulator
  7857. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, $activatePID, '');
  7858. $setpointSet = ReadingsVal($name, "setpointSet", $setpointSet);
  7859. push @event, "3:setpointTemp:$setpointTemp";
  7860. push @event, "3:maintenanceMode:off";
  7861. push @event, "3:operationMode:setpointTemp";
  7862. #CommandDeleteReading(undef, "$name setpointSet");
  7863. #CommandDeleteReading(undef, "$name setpointTempSet");
  7864. #CommandDeleteReading(undef, "$name waitingCmds");
  7865. $waitingCmds = 0;
  7866. }
  7867. } elsif ($operationMode eq "summerMode") {
  7868. # deactivate PID regulator
  7869. ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
  7870. $setpointSet = 100;
  7871. readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
  7872. push @event, "3:maintenanceMode:off";
  7873. push @event, "3:operationMode:summerMode";
  7874. #CommandDeleteReading(undef, "$name setpointSet");
  7875. #CommandDeleteReading(undef, "$name setpointTemp");
  7876. #CommandDeleteReading(undef, "$name setpointTempSet");
  7877. #CommandDeleteReading(undef, "$name waitingCmds");
  7878. $waitingCmds = 0;
  7879. } else {
  7880. $waitingCmds = 0;
  7881. }
  7882. push @event, "3:state:T: $temperature SPT: $setpointTemp SP: $setpoint";
  7883. # sent message to the actuator
  7884. $data = sprintf "%02X%02X%02X%02X", $setpointSet,
  7885. ($setpointTempSet - 10) / 20 * 255,
  7886. (AttrVal($name, 'pidCtrl', 'on') eq 'on' ? 0 : 0x80) | $measurementCtrl | $wakeUpCycle,
  7887. $displayOrientation | 8 | $blockKey | $waitingCmds;
  7888. EnOcean_SndRadio(undef, $hash, $packetType, "A5", $data, $subDef, "00", $hash->{DEF});
  7889. } elsif ($st eq "hvac.10") {
  7890. # Generic HVAC Interface (EEP A5-20-10)
  7891. my %mode = (
  7892. 0 => "auto",
  7893. 1 => "heat",
  7894. 2 => "morning_warmup",
  7895. 3 => "cool",
  7896. 4 => "night_purge",
  7897. 5 => "precool",
  7898. 6 => "off",
  7899. 7 => "test",
  7900. 8 => "emergency_heat",
  7901. 9 => "fan_only",
  7902. 10 => "free_cool",
  7903. 11 => "ice",
  7904. 12 => "max_heat",
  7905. 13 => "eco",
  7906. 14 => "dehumidification",
  7907. 15 => "calibration",
  7908. 16 => "emergency_cool",
  7909. 17 => "emergency_stream",
  7910. 18 => "max_cool",
  7911. 19 => "hvc_load",
  7912. 20 => "no_load",
  7913. 31 => "auto_heat",
  7914. 32 => "auto_cool",
  7915. );
  7916. if (exists $mode{$db[3]}) {
  7917. push @event, "3:mode:$mode{$db[3]}";
  7918. } else {
  7919. push @event, "3:mode:unknown";
  7920. }
  7921. my %vanePosition = (
  7922. 0 => "auto",
  7923. 1 => "horizontal",
  7924. 2 => "position_2",
  7925. 3 => "position_3",
  7926. 4 => "position_4",
  7927. 5 => "vertical",
  7928. 6 => "swing",
  7929. 11 => "vertical_swing",
  7930. 12 => "horizontal_swing",
  7931. 13 => "hor_vert_swing",
  7932. 14 => "stop_swing",
  7933. );
  7934. my $vanePosition = $db[2] >> 4;
  7935. if (exists $vanePosition{$vanePosition}) {
  7936. push @event, "3:vanePosition:$vanePosition{$vanePosition}";
  7937. } else {
  7938. push @event, "3:vanePosition:unknown";
  7939. }
  7940. my $fanSpeed = $db[2] & 0x0F;
  7941. if ($fanSpeed == 0) {
  7942. push @event, "3:fanSpeed:auto";
  7943. } elsif ($fanSpeed > 0 && $fanSpeed < 15) {
  7944. push @event, "3:fanSpeed:" . $fanSpeed;
  7945. } else {
  7946. push @event, "3:fanSpeed:unknown";
  7947. }
  7948. if ($db[1] == 255) {
  7949. push @event, "3:ctrl:auto";
  7950. } elsif ($db[1] >= 0 && $db[1] <= 100) {
  7951. push @event, "3:ctrl:" . $db[1];
  7952. } else {
  7953. push @event, "3:ctrl:unknown";
  7954. }
  7955. my $occupancy = ($db[0] & 6) >> 1;
  7956. if ($occupancy == 0) {
  7957. push @event, "3:occupancy:occupied";
  7958. } elsif ($occupancy == 1) {
  7959. push @event, "3:occupancy:standby";
  7960. } elsif ($occupancy == 2) {
  7961. push @event, "3:occupancy:unoccupied";
  7962. } else {
  7963. push @event, "3:occupancy:off";
  7964. }
  7965. push @event, "3:powerSwitch:" . ($db[0] & 1 ? "on" : "off");
  7966. push @event, "3:state:" . ($db[0] & 1 ? "on" : "off");
  7967. } elsif ($st eq "hvac.11") {
  7968. # Generic HVAC Interface - Error Control (EEP A5-20-11)
  7969. push @event, "3:errorCode:" . hex(substr($data, 0, 4));
  7970. push @event, "3:otherDisable:" . ($db[1] & 8 ? "disabled" : "enabled");
  7971. push @event, "3:windowDisable:" . ($db[1] & 4 ? "disabled" : "enabled");
  7972. push @event, "3:keyCardDisable:" . ($db[1] & 2 ? "disabled" : "enabled");
  7973. push @event, "3:externalDisable:" . ($db[1] & 1 ? "disabled" : "enabled");
  7974. push @event, "3:remoteCtrl:" . ($db[0] & 4 ? "disabled" : "enabled");
  7975. push @event, "3:window:" . ($db[0] & 2 ? "closed" : "opened");
  7976. push @event, "3:alarm:" . ($db[0] & 1 ? "error" : "ok");
  7977. push @event, "3:state:" . ($db[0] & 1 ? "error" : "ok");
  7978. } elsif ($st =~ m/^tempSensor/) {
  7979. # Temperature Sensor with with different ranges (EEP A5-02-01 ... A5-02-1B)
  7980. # $db[1] is the temperature where 0x00 = max °C ... 0xFF = min °C
  7981. my $temp;
  7982. $temp = sprintf "%0.1f", 0 - $db[1] / 6.375 if ($st eq "tempSensor.01");
  7983. $temp = sprintf "%0.1f", 10 - $db[1] / 6.375 if ($st eq "tempSensor.02");
  7984. $temp = sprintf "%0.1f", 20 - $db[1] / 6.375 if ($st eq "tempSensor.03");
  7985. $temp = sprintf "%0.1f", 30 - $db[1] / 6.375 if ($st eq "tempSensor.04");
  7986. $temp = sprintf "%0.1f", 40 - $db[1] / 6.375 if ($st eq "tempSensor.05");
  7987. $temp = sprintf "%0.1f", 50 - $db[1] / 6.375 if ($st eq "tempSensor.06");
  7988. $temp = sprintf "%0.1f", 60 - $db[1] / 6.375 if ($st eq "tempSensor.07");
  7989. $temp = sprintf "%0.1f", 70 - $db[1] / 6.375 if ($st eq "tempSensor.08");
  7990. $temp = sprintf "%0.1f", 80 - $db[1] / 6.375 if ($st eq "tempSensor.09");
  7991. $temp = sprintf "%0.1f", 90 - $db[1] / 6.375 if ($st eq "tempSensor.0A");
  7992. $temp = sprintf "%0.1f", 100 - $db[1] / 6.375 if ($st eq "tempSensor.0B");
  7993. $temp = sprintf "%0.1f", 20 - $db[1] / 3.1875 if ($st eq "tempSensor.10");
  7994. $temp = sprintf "%0.1f", 30 - $db[1] / 3.1875 if ($st eq "tempSensor.11");
  7995. $temp = sprintf "%0.1f", 40 - $db[1] / 3.1875 if ($st eq "tempSensor.12");
  7996. $temp = sprintf "%0.1f", 50 - $db[1] / 3.1875 if ($st eq "tempSensor.13");
  7997. $temp = sprintf "%0.1f", 60 - $db[1] / 3.1875 if ($st eq "tempSensor.14");
  7998. $temp = sprintf "%0.1f", 70 - $db[1] / 3.1875 if ($st eq "tempSensor.15");
  7999. $temp = sprintf "%0.1f", 80 - $db[1] / 3.1875 if ($st eq "tempSensor.16");
  8000. $temp = sprintf "%0.1f", 90 - $db[1] / 3.1875 if ($st eq "tempSensor.17");
  8001. $temp = sprintf "%0.1f", 100 - $db[1] / 3.1875 if ($st eq "tempSensor.18");
  8002. $temp = sprintf "%0.1f", 110 - $db[1] / 3.1875 if ($st eq "tempSensor.19");
  8003. $temp = sprintf "%0.1f", 120 - $db[1] / 3.1875 if ($st eq "tempSensor.1A");
  8004. $temp = sprintf "%0.1f", 130 - $db[1] / 3.1875 if ($st eq "tempSensor.1B");
  8005. $temp = sprintf "%0.2f", 41.2 - (($db[2] << 8) | $db[1]) / 20 if ($st eq "tempSensor.20");
  8006. $temp = sprintf "%0.1f", 62.3 - (($db[2] << 8) | $db[1]) / 10 if ($st eq "tempSensor.30");
  8007. push @event, "3:temperature:$temp";
  8008. push @event, "3:state:$temp";
  8009. } elsif ($st eq "COSensor.01") {
  8010. # Gas Sensor, CO Sensor (EEP A5-09-01)
  8011. # [untested]
  8012. # $db[3] is the CO concentration where 0x00 = 0 ppm ... 0xFF = 255 ppm
  8013. # $db[1] is the temperature where 0x00 = 0 °C ... 0xFF = 255 °C
  8014. # $db[0] bit D1 temperature sensor available 0 = no, 1 = yes
  8015. my $coChannel1 = $db[3];
  8016. push @event, "3:CO:$coChannel1";
  8017. if ($db[0] & 2) {
  8018. my $temp = $db[1];
  8019. push @event, "3:temperature:$temp";
  8020. }
  8021. push @event, "3:state:$coChannel1";
  8022. } elsif ($st eq "COSensor.02") {
  8023. # Gas Sensor, CO Sensor (EEP A5-09-02)
  8024. # [untested]
  8025. # $db[3] is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
  8026. # $db[2] is the CO concentration where 0x00 = 0 ppm ... 0xFF = 1020 ppm
  8027. # $db[1] is the temperature where 0x00 = 0 °C ... 0xFF = 51 °C
  8028. # $db[0]_bit_1 temperature sensor available 0 = no, 1 = yes
  8029. my $coChannel1 = $db[2] << 2;
  8030. my $voltage = sprintf "%0.1f", $db[3] * 0.02;
  8031. push @event, "3:CO:$coChannel1";
  8032. if ($db[0] & 2) {
  8033. my $temp = sprintf "%0.1f", $db[1] * 0.2;
  8034. push @event, "3:temperature:$temp";
  8035. }
  8036. push @event, "3:voltage:$voltage";
  8037. push @event, "3:state:$coChannel1";
  8038. } elsif ($st eq "tempHumiCO2Sensor.01") {
  8039. # Gas Sensor, CO2 Sensor (EEP A5-09-04)
  8040. # [Thermokon SR04 CO2 *, Eltako FCOTF63, untested]
  8041. # $db[3] is the humidity where 0x00 = 0 %rH ... 0xC8 = 100 %rH
  8042. # $db[2] is the CO2 concentration where 0x00 = 0 ppm ... 0xFF = 2500 ppm
  8043. # $db[1] is the temperature where 0x00 = 0°C ... 0xFF = +51 °C
  8044. # $db[0] bit D2 humidity sensor available 0 = no, 1 = yes
  8045. # $db[0] bit D1 temperature sensor available 0 = no, 1 = yes
  8046. my $humi = "-";
  8047. my $temp = "-";
  8048. my $airQuality;
  8049. if ($db[0] & 4) {
  8050. $humi = $db[3] >> 1;
  8051. push @event, "3:humidity:$humi";
  8052. }
  8053. my $co2 = sprintf "%d", $db[2] * 10;
  8054. push @event, "3:CO2:$co2";
  8055. if ($db[0] & 2) {
  8056. $temp = sprintf "%0.1f", $db[1] * 51 / 255 ;
  8057. push @event, "3:temperature:$temp";
  8058. }
  8059. if ($co2 <= 400) {
  8060. $airQuality = "high";
  8061. } elsif ($co2 <= 600) {
  8062. $airQuality = "mean";
  8063. } elsif ($co2 <= 1000) {
  8064. $airQuality = "moderate";
  8065. } else {
  8066. $airQuality = "low";
  8067. }
  8068. push @event, "3:airQuality:$airQuality";
  8069. push @event, "3:state:T: $temp H: $humi CO2: $co2 AQ: $airQuality";
  8070. } elsif ($st eq "radonSensor.01") {
  8071. # Gas Sensor, Radon Sensor (EEP A5-09-06)
  8072. # [untested]
  8073. # $db[3]_bit_7 ... $db[2]_bit_6 is the radon activity where 0 = 0 Bq/m3 ... 1023 = 1023 Bq/m3
  8074. my $rn = $db[3] << 2 | $db[2] >> 6;
  8075. push @event, "3:Rn:$rn";
  8076. push @event, "3:state:$rn";
  8077. } elsif ($st eq "vocSensor.01") {
  8078. # Gas Sensor, VOC Sensor (EEP A5-09-05, A5-09-0C)
  8079. # [untested]
  8080. # $db[3]_bit_7 ... $db[2]_bit_0 is the VOC concentration where 0 = 0 ppb ... 65535 = 65535 ppb
  8081. # $db[1] is the VOC identification
  8082. # $db[0]_bit_1 ... $db[0]_bit_0 is the scale multiplier
  8083. my $vocSCM = $db[0] & 3;
  8084. if ($vocSCM == 3) {
  8085. $vocSCM = 10;
  8086. } elsif ($vocSCM == 2) {
  8087. $vocSCM = 1;
  8088. } elsif ($vocSCM == 1) {
  8089. $vocSCM = 0.1;
  8090. } else {
  8091. $vocSCM = 0.01;
  8092. }
  8093. my $vocConc = sprintf "%f", ($db[3] << 8 | $db[2]) * $vocSCM;
  8094. my %vocID = (
  8095. 0 => "VOCT",
  8096. 1 => "Formaldehyde",
  8097. 2 => "Benzene",
  8098. 3 => "Styrene",
  8099. 4 => "Toluene",
  8100. 5 => "Tetrachloroethylene",
  8101. 6 => "Xylene",
  8102. 7 => "n-Hexane",
  8103. 8 => "n-Octane",
  8104. 9 => "Cyclopentane",
  8105. 10 => "Methanol",
  8106. 11 => "Ethanol",
  8107. 12 => "1-Pentanol",
  8108. 13 => "Acetone",
  8109. 14 => "Ethylene Oxide",
  8110. 15 => "Acetaldehyde ue",
  8111. 16 => "Acetic Acid",
  8112. 17 => "Propionice Acid",
  8113. 18 => "Valeric Acid",
  8114. 19 => "Butyric Acid",
  8115. 20 => "Ammoniac",
  8116. 22 => "Hydrogen Sulfide",
  8117. 23 => "Dimethylsulfide",
  8118. 24 => "2-Butanol",
  8119. 25 => "2-Methylpropanol",
  8120. 26 => "Diethyl Ether",
  8121. 27 => "Naphthalene",
  8122. 28 => "4-Phenylcyclohexene",
  8123. 29 => "Limonene",
  8124. 30 => "Tricloroethylene",
  8125. 31 => "Isovaleric Acid",
  8126. 32 => "Indole",
  8127. 33 => "Cadaverine",
  8128. 34 => "Putrescine",
  8129. 35 => "Caproic Acid",
  8130. 255 => "Ozone",
  8131. );
  8132. if (exists $vocID{$db[1]}) {
  8133. push @event, "3:vocName:$vocID{$db[1]}";
  8134. } else {
  8135. push @event, "3:vocName:unknown";
  8136. }
  8137. push @event, "3:concentration:$vocConc";
  8138. push @event, "3:concentrationUnit:" . $db[0] & 4 ? 'ug/m3' : 'ppb';
  8139. push @event, "3:state:$vocConc";
  8140. } elsif ($st eq "particlesSensor.01") {
  8141. # Gas Sensor, Particles Sensor (EEP A5-09-07)
  8142. # [untested]
  8143. # $db[3]_bit_7 ... $db[2]_bit_7 is the particle concentration < 10 µm
  8144. # where 0 = 0 µg/m3 ... 511 = 511 µg/m3
  8145. # $db[2]_bit_6 ... $db[1]_bit_6 is the particle concentration < 2.5 µm
  8146. # where 0 = 0 µg/m3 ... 511 = 511 µg/m3
  8147. # $db[1]_bit_5 ... $db[0]_bit_5 is the particle concentration < 1 µm
  8148. # where 0 = 0 µg/m3 ... 511 = 511 µg/m3
  8149. # $db[0]_bit_2 = 1 = Sensor PM10 active
  8150. # $db[0]_bit_1 = 1 = Sensor PM2_5 active
  8151. # $db[0]_bit_0 = 1 = Sensor PM1 active
  8152. my $pm_10 = "inactive";
  8153. my $pm_2_5 = "inactive";
  8154. my $pm_1 = "inactive";
  8155. if ($db[0] & 4) {$pm_10 = $db[3] << 1 | $db[2] >> 7;}
  8156. if ($db[0] & 2) {$pm_2_5 = ($db[2] & 0x7F) << 1 | $db[1] >> 7;}
  8157. if ($db[0] & 1) {$pm_1 = ($db[1] & 0x3F) << 3 | $db[0] >> 5;}
  8158. push @event, "3:particles_10:$pm_10";
  8159. push @event, "3:particles_2_5:$pm_2_5";
  8160. push @event, "3:particles_1:$pm_1";
  8161. push @event, "3:state:PM10: $pm_10 PM2_5: $pm_2_5 PM1: $pm_1";
  8162. } elsif ($st eq "CO2Sensor.01") {
  8163. # CO2 Sensor (EEP A5-09-08, A5-09-09)
  8164. # [untested]
  8165. # $db[1] is the CO2 concentration where 0x00 = 0 ppm ... 0xFF = 2000 ppm
  8166. # $db[0]_bit_2 is power failure detection
  8167. my $co2 = $db[1] / 255 * 2000;
  8168. push @event, "3:powerFailureDetection:" . ($db[0] & 4 ? "detected" : "not_detected");
  8169. push @event, "3:CO2:$co2";
  8170. push @event, "3:state:$co2";
  8171. } elsif ($st eq "HSensor.01") {
  8172. # H Sensor (EEP A5-09-0A)
  8173. # [untested]
  8174. # $db[3]_$db[2] is the H concentration where 0x00 = 0 ppm ... 0xFFFF = 2000 ppm
  8175. my $hydro = ($db[3] << 8 | $db[2]) / 65535 * 2000;
  8176. push @event, "3:voltage:" . sprintf("%0.1f", (($db[0] & 0xF0) >> 4) / 15 * 3 + 2) if ($db[0] & 1);
  8177. push @event, "3:temperature:" . sprintf("%0.1f", $db[1] / 255 * 80 - 20) if ($db[0] & 2);
  8178. push @event, "3:H:" . sprintf "%0.2f", $hydro;
  8179. push @event, "3:state:" . sprintf "%0.2f", $hydro;
  8180. } elsif ($st eq "radiationSensor.01") {
  8181. # Radiation Sensor (EEP A5-09-0B)
  8182. # [untested]
  8183. # $db[2]_$db[1] is the radioactivity where 0x00 = 0 ... 0xFFFF = 65535
  8184. my %scaleMulti = (
  8185. 0 => 0.001,
  8186. 1 => 0.01,
  8187. 2 => 0.1,
  8188. 3 => 1,
  8189. 4 => 10,
  8190. 5 => 100,
  8191. 6 => 1000,
  8192. 7 => 10000,
  8193. 8 => 100000
  8194. );
  8195. my $scaleMulti = ($db[0] & 0xF0) >> 4;
  8196. my $scaleDecimals;
  8197. if ($scaleMulti <= 2) {
  8198. $scaleDecimals = "%0." . (3 - $scaleMulti) . "f";
  8199. } else {
  8200. $scaleDecimals = "%d"
  8201. }
  8202. $scaleMulti = $scaleMulti{$scaleMulti} if (exists $scaleMulti{$scaleMulti});
  8203. my %unit = (
  8204. 0 => "uSv/h",
  8205. 1 => "cpm",
  8206. 2 => "Bq/L",
  8207. 3 => "Bq/kg"
  8208. );
  8209. my $unit = ($db[0] & 6) >> 1;
  8210. $unit = $unit{$unit};
  8211. my $radioactivity = $db[2] << 8 | $db[1];
  8212. push @event, "3:radioactivity:" . sprintf "$scaleDecimals", $radioactivity * $scaleMulti;
  8213. push @event, "3:radioactivityUnit:$unit";
  8214. push @event, "3:voltage:" . sprintf("%0.1f", (($db[3] & 0xF0) >> 4) / 15 * 3 + 2) if ($db[0] & 1);
  8215. push @event, "3:state:" . sprintf "$scaleDecimals", $radioactivity * $scaleMulti;
  8216. } elsif ($st eq "roomSensorControl.05") {
  8217. # Room Sensor and Control Unit (EEP A5-10-01 ... A5-10-0D)
  8218. # [Eltako FTR55D, FTR55H, Thermokon SR04 *, Thanos SR *, untested]
  8219. # $db[3] is the fan speed or night reduction for Eltako
  8220. # $db[2] is the setpoint where 0x00 = min ... 0xFF = max or
  8221. # reference temperature for Eltako where 0x00 = 0°C ... 0xFF = 40°C
  8222. # $db[1] is the temperature where 0x00 = +40°C ... 0xFF = 0°C
  8223. # $db[0]_bit_0 is the occupy button, pushbutton or slide switch
  8224. my $temp = sprintf "%0.1f", 40 - $db[1] / 6.375;
  8225. if ($manufID eq "00D") {
  8226. my $nightReduction = 0;
  8227. $nightReduction = 1 if ($db[3] == 0x06);
  8228. $nightReduction = 2 if ($db[3] == 0x0C);
  8229. $nightReduction = 3 if ($db[3] == 0x13);
  8230. $nightReduction = 4 if ($db[3] == 0x19);
  8231. $nightReduction = 5 if ($db[3] == 0x1F);
  8232. my $setpointTemp = sprintf "%0.1f", $db[2] / 6.375;
  8233. push @event, "3:state:T: $temp SPT: $setpointTemp NR: $nightReduction";
  8234. push @event, "3:nightReduction:$nightReduction";
  8235. push @event, "3:setpointTemp:$setpointTemp";
  8236. } else {
  8237. my $fspeed = 3;
  8238. $fspeed = 2 if ($db[3] >= 145);
  8239. $fspeed = 1 if ($db[3] >= 165);
  8240. $fspeed = 0 if ($db[3] >= 190);
  8241. $fspeed = "auto" if ($db[3] >= 210);
  8242. my $switch = $db[0] & 1 ? "on" : "off";
  8243. push @event, "3:state:T: $temp SP: $db[2] F: $fspeed SW: $switch";
  8244. push @event, "3:fanStage:$fspeed";
  8245. push @event, "3:switch:$switch";
  8246. push @event, "3:setpoint:$db[2]";
  8247. my $setpointScaled = EnOcean_ReadingScaled($hash, $db[2], 0, 255);
  8248. if (defined $setpointScaled) {
  8249. push @event, "3:setpointScaled:" . $setpointScaled;
  8250. }
  8251. }
  8252. push @event, "3:temperature:$temp";
  8253. } elsif ($st eq "roomSensorControl.01") {
  8254. # Room Sensor and Control Unit (EEP A5-04-01, A5-10-10 ... A5-10-14)
  8255. # [Thermokon SR04 * rH, Thanus SR *, untested]
  8256. # $db[3] is the setpoint where 0x00 = min ... 0xFF = max
  8257. # $db[2] is the humidity where 0x00 = 0%rH ... 0xFA = 100%rH
  8258. # $db[1] is the temperature where 0x00 = 0°C ... 0xFA = +40°C
  8259. # $db[0] bit D0 is the occupy button, pushbutton or slide switch
  8260. my $temp = sprintf "%0.1f", $db[1] * 40 / 250;
  8261. my $humi = sprintf "%d", $db[2] / 2.5;
  8262. my $switch = $db[0] & 1;
  8263. push @event, "3:humidity:$humi";
  8264. push @event, "3:temperature:$temp";
  8265. if ($manufID eq "039") {
  8266. my $brightness = sprintf "%d", $db[3] * 117;
  8267. push @event, "3:brightness:$brightness";
  8268. push @event, "3:state:T: $temp H: $humi B: $brightness";
  8269. } else {
  8270. push @event, "3:setpoint:$db[3]";
  8271. push @event, "3:state:T: $temp H: $humi SP: $db[3] SW: $switch";
  8272. push @event, "3:switch:$switch";
  8273. my $setpointScaled = EnOcean_ReadingScaled($hash, $db[3], 0, 255);
  8274. if (defined $setpointScaled) {
  8275. push @event, "3:setpointScaled:" . $setpointScaled;
  8276. }
  8277. }
  8278. } elsif ($st eq "roomSensorControl.02") {
  8279. # Room Sensor and Control Unit (A5-10-15 ... A5-10-17)
  8280. # [untested]
  8281. # $db[2] bit D7 ... D2 is the setpoint where 0 = min ... 63 = max
  8282. # $db[2] bit D1 ... $db[1] bit D0 is the temperature where 0 = -10°C ... 1023 = +41.2°C
  8283. # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
  8284. my $temp = sprintf "%0.2f", -10 + ((($db[2] & 3) << 8) | $db[1]) / 19.98;
  8285. my $setpoint = ($db[2] & 0xFC) >> 2;
  8286. my $presence = $db[0] & 1 ? "absent" : "present";
  8287. push @event, "3:state:T: $temp SP: $setpoint P: $presence";
  8288. push @event, "3:presence:$presence";
  8289. push @event, "3:setpoint:$setpoint";
  8290. push @event, "3:temperature:$temp";
  8291. my $setpointScaled = EnOcean_ReadingScaled($hash, $db[2], 0, 255);
  8292. if (defined $setpointScaled) {
  8293. push @event, "3:setpointScaled:" . $setpointScaled;
  8294. }
  8295. } elsif ($st eq "roomSensorControl.18") {
  8296. # Room Sensor and Control Unit (A5-10-18)
  8297. # [untested]
  8298. # $db[3] is the illuminance where min 0x00 = 0 lx, max 0xFA = 1000 lx
  8299. # $db[2] is the setpoint where 250 = 0 °C ... 0 = 40 °C
  8300. # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
  8301. # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
  8302. # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
  8303. # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
  8304. my $lux = $db[3] << 2;
  8305. if ($db[3] == 251) {$lux = "over range";}
  8306. my $setpoint = sprintf "%0.1f", 40 - $db[2] * 40 / 250;
  8307. my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
  8308. my $fanSpeed;
  8309. if ((($db[0] & 0x70) >> 4) == 0) {
  8310. $fanSpeed = "auto";
  8311. } elsif ((($db[0] & 0x70) >> 4) == 7) {
  8312. $fanSpeed = "off";
  8313. } else {
  8314. $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
  8315. }
  8316. my $presence;
  8317. if ($db[0] & 2) {
  8318. $presence = "disabled";
  8319. } else {
  8320. $presence = $db[0] & 1 ? "absent" : "present";
  8321. }
  8322. push @event, "3:brightness:$lux";
  8323. push @event, "3:fan:$fanSpeed";
  8324. push @event, "3:presence:$presence";
  8325. push @event, "3:setpoint:$setpoint";
  8326. push @event, "3:temperature:$temp";
  8327. push @event, "3:state:T: $temp B: $lux F: $fanSpeed SP: $setpoint P: $presence";
  8328. } elsif ($st eq "roomSensorControl.19") {
  8329. # Room Sensor and Control Unit (A5-10-19)
  8330. # [untested]
  8331. # $db[3] is the humidity where min 0x00 = 0 %rH, max 0xFA = 10 %rH
  8332. # $db[2] is the setpoint where 250 = 0 °C ... 0 = 40 °C
  8333. # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
  8334. # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
  8335. # $db[0]_bit_1 is Occupany Button where 0 = pressed, 1 = released
  8336. # $db[0]_bit_0 is Occupany enable where 0 = enabled, 1 = disabled
  8337. my $humi = $db[3] / 2.5;
  8338. my $setpoint = sprintf "%0.1f", 40 - $db[2] * 40 / 250;
  8339. my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
  8340. my $fanSpeed;
  8341. if ((($db[0] & 0x70) >> 4) == 0) {
  8342. $fanSpeed = "auto";
  8343. } elsif ((($db[0] & 0x70) >> 4) == 7) {
  8344. $fanSpeed = "off";
  8345. } else {
  8346. $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
  8347. }
  8348. my $presence;
  8349. if ($db[0] & 1) {
  8350. $presence = "disabled";
  8351. } else {
  8352. $presence = $db[0] & 2 ? "absent" : "present";
  8353. }
  8354. push @event, "3:fan:$fanSpeed";
  8355. push @event, "3:humidity:$humi";
  8356. push @event, "3:presence:$presence";
  8357. push @event, "3:setpoint:$setpoint";
  8358. push @event, "3:temperature:$temp";
  8359. push @event, "3:state:T: $temp H: $humi F: $fanSpeed SP: $setpoint P: $presence";
  8360. } elsif ($st eq "roomSensorControl.1A") {
  8361. # Room Sensor and Control Unit (A5-10-1A)
  8362. # [untested]
  8363. # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
  8364. # $db[3] > 0xFA is error code
  8365. # $db[2] is the setpoint where 250 = 0 °C ... 0 = 40 °C
  8366. # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
  8367. # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
  8368. # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
  8369. # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
  8370. my $voltage = sprintf "%0.1f", $db[3] * 0.02;
  8371. if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
  8372. my $setpoint = sprintf "%0.1f", 40 - $db[2] * 40 / 250;
  8373. my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
  8374. my $fanSpeed;
  8375. if ((($db[0] & 0x70) >> 4) == 0) {
  8376. $fanSpeed = "auto";
  8377. } elsif ((($db[0] & 0x70) >> 4) == 7) {
  8378. $fanSpeed = "off";
  8379. } else {
  8380. $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
  8381. }
  8382. my $presence;
  8383. if ($db[0] & 2) {
  8384. $presence = "disabled";
  8385. } else {
  8386. $presence = $db[0] & 1 ? "absent" : "present";
  8387. }
  8388. push @event, "3:fan:$fanSpeed";
  8389. push @event, "3:presence:$presence";
  8390. push @event, "3:setpoint:$setpoint";
  8391. push @event, "3:temperature:$temp";
  8392. push @event, "3:voltage:$voltage";
  8393. push @event, "3:state:T: $temp F: $fanSpeed SP: $setpoint P: $presence U: $voltage";
  8394. } elsif ($st eq "roomSensorControl.1B") {
  8395. # Room Sensor and Control Unit (A5-10-1B)
  8396. # [untested]
  8397. # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
  8398. # $db[3] > 0xFA is error code
  8399. # $db[2] is the illuminance where min 0x00 = 0 lx, max 0xFA = 1000 lx
  8400. # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
  8401. # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
  8402. # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
  8403. # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
  8404. my $voltage = sprintf "%0.1f", $db[3] * 0.02;
  8405. if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
  8406. my $lux = $db[2] << 2;
  8407. if ($db[2] == 251) {$lux = "over range";}
  8408. my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
  8409. my $fanSpeed;
  8410. if ((($db[0] & 0x70) >> 4) == 0) {
  8411. $fanSpeed = "auto";
  8412. } elsif ((($db[0] & 0x70) >> 4) == 7) {
  8413. $fanSpeed = "off";
  8414. } else {
  8415. $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
  8416. }
  8417. my $presence;
  8418. if ($db[0] & 2) {
  8419. $presence = "disabled";
  8420. } else {
  8421. $presence = $db[0] & 1 ? "absent" : "present";
  8422. }
  8423. push @event, "3:brightness:$lux";
  8424. push @event, "3:fan:$fanSpeed";
  8425. push @event, "3:presence:$presence";
  8426. push @event, "3:temperature:$temp";
  8427. push @event, "3:voltage:$voltage";
  8428. push @event, "3:state:T: $temp B: $lux F: $fanSpeed P: $presence U: $voltage";
  8429. } elsif ($st eq "roomSensorControl.1C") {
  8430. # Room Sensor and Control Unit (A5-10-1C)
  8431. # [untested]
  8432. # $db[3] is the illuminance where min 0x00 = 0 lx, max 0xFA = 1000 lx
  8433. # $db[2] is the illuminance setpoint where min 0x00 = 0 lx, max 0xFA = 1000 lx
  8434. # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
  8435. # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
  8436. # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
  8437. # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
  8438. my $lux = $db[3] << 2;
  8439. if ($db[3] == 251) {$lux = "over range";}
  8440. my $setpoint = $db[2] << 2;
  8441. my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
  8442. my $fanSpeed;
  8443. if ((($db[0] & 0x70) >> 4) == 0) {
  8444. $fanSpeed = "auto";
  8445. } elsif ((($db[0] & 0x70) >> 4) == 7) {
  8446. $fanSpeed = "off";
  8447. } else {
  8448. $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
  8449. }
  8450. my $presence;
  8451. if ($db[0] & 2) {
  8452. $presence = "disabled";
  8453. } else {
  8454. $presence = $db[0] & 1 ? "absent" : "present";
  8455. }
  8456. push @event, "3:brightness:$lux";
  8457. push @event, "3:fan:$fanSpeed";
  8458. push @event, "3:presence:$presence";
  8459. push @event, "3:setpoint:$setpoint";
  8460. push @event, "3:temperature:$temp";
  8461. push @event, "3:state:T: $temp B: $lux F: $fanSpeed SP: $setpoint P: $presence";
  8462. } elsif ($st eq "roomSensorControl.1D") {
  8463. # Room Sensor and Control Unit (A5-10-1D)
  8464. # [untested]
  8465. # $db[3] is the humidity where min 0x00 = 0 %rH, max 0xFA = 10 %rH
  8466. # $db[2] is the humidity setpoint where min 0x00 = 0 %rH, max 0xFA = 10 %rH
  8467. # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
  8468. # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
  8469. # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
  8470. # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
  8471. my $humi = sprintf "%d", $db[3] / 2.5;
  8472. my $setpoint = $db[2] / 2.5;
  8473. my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
  8474. my $fanSpeed;
  8475. if ((($db[0] & 0x70) >> 4) == 0) {
  8476. $fanSpeed = "auto";
  8477. } elsif ((($db[0] & 0x70) >> 4) == 7) {
  8478. $fanSpeed = "off";
  8479. } else {
  8480. $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
  8481. }
  8482. my $presence;
  8483. if ($db[0] & 2) {
  8484. $presence = "disabled";
  8485. } else {
  8486. $presence = $db[0] & 1 ? "absent" : "present";
  8487. }
  8488. push @event, "3:fan:$fanSpeed";
  8489. push @event, "3:humidity:$humi";
  8490. push @event, "3:presence:$presence";
  8491. push @event, "3:setpoint:$setpoint";
  8492. push @event, "3:temperature:$temp";
  8493. push @event, "3:state:T: $temp H: $humi F: $fanSpeed SP: $setpoint P: $presence";
  8494. } elsif ($st eq "roomSensorControl.1F") {
  8495. # Room Sensor and Control Unit (A5-10-1F)
  8496. # [untested]
  8497. # $db[3] is the fan speed
  8498. # $db[2] is the setpoint where 0 = 0 ... 255 = 255
  8499. # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
  8500. # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
  8501. # $db[0]_bit_6 ... $db[0]_bit_4 are flags
  8502. # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
  8503. # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
  8504. my $fanSpeed = "unknown";
  8505. if ($db[0] & 0x10) {
  8506. $fanSpeed = 3;
  8507. $fanSpeed = 2 if ($db[3] >= 145);
  8508. $fanSpeed = 1 if ($db[3] >= 165);
  8509. $fanSpeed = 0 if ($db[3] >= 190);
  8510. $fanSpeed = "auto" if ($db[3] >= 210);
  8511. }
  8512. my $setpoint = "unknown";
  8513. $setpoint = $db[2] if ($db[0] & 0x20);
  8514. my $temp = "unknown";
  8515. $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250 if ($db[0] & 0x40);
  8516. my $presence = "unknown";
  8517. $presence = "absent" if (!($db[0] & 2));
  8518. $presence = "present" if (!($db[0] & 1));
  8519. push @event, "3:fan:$fanSpeed";
  8520. push @event, "3:presence:$presence";
  8521. push @event, "3:setpoint:$setpoint";
  8522. push @event, "3:temperature:$temp";
  8523. push @event, "3:state:T: $temp F: $fanSpeed SP: $setpoint P: $presence";
  8524. my $setpointScaled = EnOcean_ReadingScaled($hash, $db[2], 0, 255);
  8525. if (defined $setpointScaled) {
  8526. push @event, "3:setpointScaled:" . $setpointScaled;
  8527. }
  8528. } elsif ($st eq "roomSensorControl.20") {
  8529. # Room Operation Panel (A5-10-20, A5-10-21)
  8530. # [untested]
  8531. # $db[3] is the setpoint where 0 = 0 ... 255 = 255
  8532. # $db[2] is the humidity setpoint where min 0x00 = 0 %rH, max 0xFA = 100 %rH
  8533. # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
  8534. # $db[0]_bit_6 ... $db[0]_bit_5 is setpoint mode
  8535. # $db[0]_bit_4 is battery state 0 = ok, 1 = low
  8536. # $db[0]_bit_0 is user activity where 0 = no, 1 = yes
  8537. my $humi = sprintf "%d", $db[2] / 2.5;
  8538. my $setpoint = $db[3];
  8539. my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
  8540. my $setpointMode;
  8541. if ((($db[0] & 0x60) >> 5) == 3) {
  8542. $setpointMode = "reserved";
  8543. } elsif ((($db[0] & 0x60) >> 5) == 2) {
  8544. $setpointMode = "auto";
  8545. } elsif ((($db[0] & 0x60) >> 1) == 1){
  8546. $setpointMode = "frostProtection";
  8547. } else {
  8548. $setpointMode = "setpoint";
  8549. }
  8550. my $battery = ($db[0] & 0x10) ? "low" : "ok";
  8551. push @event, "3:activity:" . ($db[0] & 1 ? "yes" : "no");
  8552. push @event, "3:battery:$battery";
  8553. push @event, "3:humidity:$humi";
  8554. push @event, "3:setpoint:$setpoint";
  8555. push @event, "3:setpointMode:$setpointMode";
  8556. push @event, "3:temperature:$temp";
  8557. push @event, "3:state:T: $temp H: $humi SP: $setpoint B: $battery";
  8558. my $setpointScaled = EnOcean_ReadingScaled($hash, $db[3], 0, 255);
  8559. if (defined $setpointScaled) {
  8560. push @event, "3:setpointScaled:" . $setpointScaled;
  8561. }
  8562. } elsif ($st eq "roomSensorControl.22") {
  8563. # Room Operation Panel (A5-10-22, A5-10-23)
  8564. my $setpoint = $db[3];
  8565. my $humi = sprintf "%d", $db[2] / 2.5;
  8566. my $temp = sprintf "%0.1f", $db[1] * 40 / 250;
  8567. my $fanSpeed;
  8568. if ((($db[0] & 0xE0) >> 5) == 4) {
  8569. $fanSpeed = 3;
  8570. } elsif ((($db[0] & 0xE0) >> 5) == 3) {
  8571. $fanSpeed = 2;
  8572. } elsif ((($db[0] & 0xE0) >> 5) == 2) {
  8573. $fanSpeed = 1;
  8574. } elsif ((($db[0] & 0xE0) >> 5) == 1){
  8575. $fanSpeed = "off";
  8576. } else {
  8577. $fanSpeed = "auto";
  8578. }
  8579. my $occupancy = ($db[0] & 1) ? "occupied" : "unoccupied";
  8580. push @event, "3:occupancy:$occupancy";
  8581. push @event, "3:humidity:$humi";
  8582. push @event, "3:setpoint:$setpoint";
  8583. push @event, "3:fanSpeed:$fanSpeed";
  8584. push @event, "3:temperature:$temp";
  8585. push @event, "3:state:T: $temp H: $humi SP: $setpoint F: $fanSpeed O: $occupancy";
  8586. my $setpointScaled = EnOcean_ReadingScaled($hash, $db[3], 0, 255);
  8587. if (defined $setpointScaled) {
  8588. push @event, "3:setpointScaled:" . $setpointScaled;
  8589. }
  8590. } elsif ($st eq "tempHumiSensor.02") {
  8591. # Temperatur and Humidity Sensor(EEP A5-04-02)
  8592. # [Eltako FAFT60, FIFT63AP]
  8593. # $db[3] is the voltage where 0x59 = 2.5V ... 0x9B = 4V, only at Eltako
  8594. # $db[2] is the humidity where 0x00 = 0%rH ... 0xFA = 100%rH
  8595. # $db[1] is the temperature where 0x00 = -20°C ... 0xFA = +60°C
  8596. my $humi = sprintf "%d", $db[2] / 2.5;
  8597. my $temp = sprintf "%0.1f", -20 + $db[1] * 80 / 250;
  8598. my $battery = "unknown";
  8599. if ($manufID eq "00D") {
  8600. # Eltako sensor
  8601. my $voltage = sprintf "%0.1f", $db[3] * 6.58 / 255;
  8602. my $energyStorage = "unknown";
  8603. if ($db[3] <= 0x58) {
  8604. $energyStorage = "empty";
  8605. $battery = "low";
  8606. }
  8607. elsif ($db[3] <= 0xDC) {
  8608. $energyStorage = "charged";
  8609. $battery = "ok";
  8610. }
  8611. else {
  8612. $energyStorage = "full";
  8613. $battery = "ok";
  8614. }
  8615. push @event, "3:battery:$battery";
  8616. push @event, "3:energyStorage:$energyStorage";
  8617. push @event, "3:voltage:$voltage";
  8618. }
  8619. push @event, "3:state:T: $temp H: $humi B: $battery";
  8620. push @event, "3:humidity:$humi";
  8621. push @event, "3:temperature:$temp";
  8622. } elsif ($st eq "tempHumiSensor.03") {
  8623. # Temperatur and Humidity Sensor(EEP A5-04-03)
  8624. # [untested]
  8625. # $db[3] is the humidity where 0x00 = 0%rH ... 0xFF = 100%rH
  8626. # $db[2] .. $db[1] is the temperature where 0x00 = -20°C ... 0x3FF = +60°C
  8627. my $humi = sprintf "%d", $db[3] / 2.55;
  8628. my $temp = sprintf "%0.1f", -20 + ($db[2] << 8 | $db[1]) * 80 / 1023;
  8629. push @event, "3:state:T: $temp H: $humi";
  8630. push @event, "3:humidity:$humi";
  8631. push @event, "3:temperature:$temp";
  8632. push @event, "3:telegramType:" . ($db[0] & 1 ? "event" : "heartbeat");
  8633. CommandDeleteReading(undef, "$name alarm");
  8634. if (AttrVal($name, "signOfLife", 'off') eq 'on') {
  8635. RemoveInternalTimer($hash->{helper}{timer}{alarm}) if(exists $hash->{helper}{timer}{alarm});
  8636. @{$hash->{helper}{timer}{alarm}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
  8637. InternalTimer(gettimeofday() + AttrVal($name, "signOfLifeInterval", 1540), 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0);
  8638. }
  8639. } elsif ($st eq "baroSensor.01") {
  8640. # Barometric Sensor(EEP A5-04-03)
  8641. # [untested]
  8642. # $db[3] .. $db[2] is the barometric where 0x00 = 500 hPa ... 0x3FF = 1150 hPa
  8643. my $baro = sprintf "%d", 500 + ($db[2] << 8 | $db[1]) * 650 / 1023;
  8644. push @event, "3:state:$baro";
  8645. push @event, "3:airPressure:$baro";
  8646. push @event, "3:telegramType:" . ($db[0] & 1 ? "event" : "heartbeat");
  8647. } elsif ($st eq "lightSensor.01") {
  8648. # Light Sensor (EEP A5-06-01)
  8649. # [Eltako FAH60, FAH63, FIH63, Thermokon SR65 LI, untested]
  8650. # $db[3] is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
  8651. # $db[3] is the low illuminance for Eltako devices where
  8652. # min 0x00 = 0 lx, max 0xFF = 100 lx, if $db[2] = 0
  8653. # $db[2] is the illuminance (ILL2) where min 0x00 = 300 lx, max 0xFF = 30000 lx
  8654. # $db[1] is the illuminance (ILL1) where min 0x00 = 600 lx, max 0xFF = 60000 lx
  8655. # $db[0]_bit_0 is Range select where 0 = ILL1, 1 = ILL2
  8656. my $lux;
  8657. my $voltage = "unknown";
  8658. if ($manufID eq "00D") {
  8659. if($db[2] == 0) {
  8660. $lux = sprintf "%d", $db[3] * 100 / 255;
  8661. } else {
  8662. $lux = sprintf "%d", $db[2] * 116.48 + 300;
  8663. }
  8664. } else {
  8665. $voltage = sprintf "%0.1f", $db[3] * 0.02;
  8666. if($db[0] & 1) {
  8667. $lux = sprintf "%d", $db[2] * 116.48 + 300;
  8668. } else {
  8669. $lux = sprintf "%d", $db[1] * 232.94 + 600;
  8670. }
  8671. push @event, "3:voltage:$voltage";
  8672. }
  8673. push @event, "3:brightness:$lux";
  8674. push @event, "3:state:$lux";
  8675. } elsif ($st eq "lightSensor.02") {
  8676. # Light Sensor (EEP A5-06-02)
  8677. # $db[3] is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
  8678. # $db[2] is the illuminance (ILL2) where min 0x00 = 0 lx, max 0xFF = 510 lx
  8679. # $db[1] is the illuminance (ILL1) where min 0x00 = 0 lx, max 0xFF = 1020 lx
  8680. # $db[0]_bit_0 is Range select where 0 = ILL1, 1 = ILL2
  8681. my $lux;
  8682. my $voltage = sprintf "%0.1f", $db[3] * 0.02;
  8683. if($db[0] & 1) {
  8684. $lux = $db[2] << 1;
  8685. } else {
  8686. $lux = $db[1] << 2;
  8687. }
  8688. push @event, "3:voltage:$voltage";
  8689. push @event, "3:brightness:$lux";
  8690. push @event, "3:state:$lux";
  8691. } elsif ($st eq "lightSensor.03") {
  8692. # Light Sensor (EEP A5-06-03)
  8693. # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
  8694. # $db[3] > 0xFA is error code
  8695. # $db[2]_bit_7 ... $db[1]_bit_6 is the illuminance where min 0x000 = 0 lx, max 0x3E8 = 1000 lx
  8696. my $lux = $db[2] << 2 | $db[1] >> 6;
  8697. if ($lux == 1001) {$lux = "over range";}
  8698. my $voltage = sprintf "%0.1f", $db[3] * 0.02;
  8699. if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
  8700. push @event, "3:voltage:$voltage";
  8701. push @event, "3:brightness:$lux";
  8702. push @event, "3:state:$lux";
  8703. } elsif ($st eq "lightSensor.04") {
  8704. # Light Sensor (EEP A5-06-04)
  8705. my $temperature;
  8706. if ($db[0] & 2) {
  8707. $temperature = sprintf "%0.1f", $db[3] * 80 / 255 - 20;
  8708. push @event, "3:temperature:$temperature";
  8709. } else {
  8710. $temperature = '-';
  8711. CommandDeleteReading(undef, "$name temperature");
  8712. }
  8713. my $brightness;
  8714. if ($db[0] & 1) {
  8715. $brightness = $db[2] << 8 | $db[1];
  8716. push @event, "3:brightness:$brightness";
  8717. } else {
  8718. $brightness = '-';
  8719. CommandDeleteReading(undef, "$name brightness");
  8720. }
  8721. my $energyStorage = sprintf "%d", ($db[0] >> 4) * 100 / 15;
  8722. my $battery;
  8723. if ($energyStorage <= 6) {
  8724. $battery = 'low';
  8725. push @event, "3:battery:low";
  8726. push @event, "3:energyStorage:$energyStorage";
  8727. } else {
  8728. $battery = 'ok';
  8729. push @event, "3:battery:ok";
  8730. push @event, "3:energyStorage:$energyStorage";
  8731. }
  8732. push @event, "3:state:T: $temperature E: $brightness B: $battery";
  8733. } elsif ($st eq "lightSensor.05") {
  8734. # Light Sensor (EEP A5-06-05)
  8735. # $db[3] is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
  8736. # $db[2] is the illuminance (ILL2) where min 0x00 = 0 lx, max 0xFF = 5100 lx
  8737. # $db[1] is the illuminance (ILL1) where min 0x00 = 0 lx, max 0xFF = 1020000 lx
  8738. # $db[0]_bit_0 is Range select where 0 = ILL1, 1 = ILL2
  8739. my $lux;
  8740. if($db[0] & 1) {
  8741. $lux = sprintf "%d", $db[2] * 20;
  8742. } else {
  8743. $lux = sprintf "%d", $db[1] * 40;
  8744. }
  8745. push @event, "3:voltage:" . sprintf "%0.1f", $db[3] * 0.02;
  8746. push @event, "3:brightness:$lux";
  8747. push @event, "3:state:$lux";
  8748. } elsif ($st eq "occupSensor.01") {
  8749. # Occupancy Sensor (EEP A5-07-01)
  8750. # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
  8751. # $db[3] > 0xFA is error code
  8752. # $db[2] is solar panel current where =0 uA ... 0xFF = 127 uA
  8753. # $db[1] is PIR Status (motion) where 0 ... 127 = off, 128 ... 255 = on
  8754. my $motion = "off";
  8755. if ($db[1] >= 128) {$motion = "on";}
  8756. if ($db[0] & 1) {push @event, "3:voltage:" . sprintf "%0.1f", $db[3] * 0.02;}
  8757. if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
  8758. if ($manufID eq "00B") {
  8759. push @event, "3:current:" . sprintf "%0.1f", $db[2] / 2;
  8760. if ($db[0] & 2) {
  8761. push @event, "3:sensorType:ceiling";
  8762. } else {
  8763. push @event, "3:sensorType:wall";
  8764. }
  8765. }
  8766. if ($model eq "tracker") {
  8767. RemoveInternalTimer($hash->{helper}{timer}{motion}) if(exists $hash->{helper}{timer}{motion});
  8768. RemoveInternalTimer($hash->{helper}{timer}{state}) if(exists $hash->{helper}{timer}{state});
  8769. @{$hash->{helper}{timer}{motion}} = ($hash, 'motion', 'off', 1, 5);
  8770. @{$hash->{helper}{timer}{state}} = ($hash, 'state', 'off', 1, 5);
  8771. InternalTimer(gettimeofday() + AttrVal($name, 'trackerWakeUpCycle', 30) * 1.1, 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{motion}, 0);
  8772. InternalTimer(gettimeofday() + AttrVal($name, 'trackerWakeUpCycle', 30) * 1.1, 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{state}, 0);
  8773. }
  8774. if (!exists($hash->{helper}{lastVoltage}) || $hash->{helper}{lastVoltage} != $db[3]) {
  8775. push @event, "3:battery:" . ($db[3] * 0.02 > 2.8 ? "ok" : "low");
  8776. $hash->{helper}{lastVoltage} = $db[3];
  8777. }
  8778. push @event, "3:button:" . ($db[0] & 4 ? "released" : "pressed") if ($manufID eq "7FF");
  8779. push @event, "3:motion:$motion";
  8780. push @event, "3:state:$motion";
  8781. } elsif ($st eq "occupSensor.02") {
  8782. # Occupancy Sensor (EEP A5-07-02)
  8783. # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
  8784. # $db[3] > 0xFA is error code
  8785. # $db[0]_bit_7 is PIR Status (motion) where 0 = off, 1 = on
  8786. my $motion = $db[0] >> 7 ? "on" : "off";
  8787. if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
  8788. push @event, "3:battery:" . ($db[3] * 0.02 > 2.9 ? "ok" : "low");
  8789. push @event, "3:motion:$motion";
  8790. push @event, "3:voltage:" . sprintf "%0.1f", $db[3] * 0.02;
  8791. push @event, "3:state:$motion";
  8792. } elsif ($st eq "occupSensor.03") {
  8793. # Occupancy Sensor (EEP A5-07-03)
  8794. # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
  8795. # $db[3] > 0xFA is error code
  8796. # $db[2]_bit_7 ... $db[1]_bit_6 is the illuminance where min 0x000 = 0 lx, max 0x3E8 = 1000 lx
  8797. # $db[0]_bit_7 is PIR Status (motion) where 0 = off, 1 = on
  8798. my $motion = $db[0] >> 7 ? "on" : "off";
  8799. my $lux = $db[2] << 2 | $db[1] >> 6;
  8800. if ($lux == 1001) {$lux = "over range";}
  8801. my $voltage = sprintf "%0.1f", $db[3] * 0.02;
  8802. if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
  8803. push @event, "3:battery:" . ($db[3] * 0.02 > 2.9 ? "ok" : "low");
  8804. push @event, "3:brightness:$lux";
  8805. push @event, "3:motion:$motion";
  8806. push @event, "3:voltage:$voltage";
  8807. push @event, "3:state:M: $motion E: $lux U: $voltage";
  8808. } elsif ($st =~ m/^lightTempOccupSensor/) {
  8809. # Light, Temperatur and Occupancy Sensor (EEP A5-08-01 ... A5-08-03)
  8810. # $db[3] is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
  8811. # $db[2] is the illuminance where min 0x00 = 0 lx, max 0xFF = 510 lx, 1020 lx, (2048 lx)
  8812. # $db[1] is the temperature whrere 0x00 = 0 °C ... 0xFF = 51 °C or -30 °C ... 50°C
  8813. # $db[0]_bit_1 is PIR Status (motion) where 0 = on, 1 = off
  8814. # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
  8815. my $lux;
  8816. my $temp;
  8817. my $voltage = sprintf "%0.1f", $db[3] * 0.02;
  8818. my $motion = $db[0] & 2 ? "off" : "on";
  8819. my $presence = $db[0] & 1 ? "absent" : "present";
  8820. if ($st eq "lightTempOccupSensor.01") {
  8821. # Light, Temperatur and Occupancy Sensor (EEP A5-08-01)
  8822. # [Eltako FABH63, FBH55, FBH63, FIBH63]
  8823. if ($manufID eq "00D") {
  8824. $lux = sprintf "%d", $db[2] * 2048 / 255;
  8825. push @event, "3:state:M: $motion E: $lux";
  8826. } else {
  8827. $lux = $db[2] << 1;
  8828. $temp = sprintf "%0.1f", $db[1] * 0.2;
  8829. push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage";
  8830. push @event, "3:presence:$presence";
  8831. push @event, "3:temperature:$temp";
  8832. push @event, "3:voltage:$voltage";
  8833. }
  8834. } elsif ($st eq "lightTempOccupSensor.02") {
  8835. # Light, Temperatur and Occupancy Sensor (EEP A5-08-02)
  8836. $lux = $db[2] << 2;
  8837. $temp = sprintf "%0.1f", $db[1] * 0.2;
  8838. push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage";
  8839. push @event, "3:presence:$presence";
  8840. push @event, "3:temperature:$temp";
  8841. push @event, "3:voltage:$voltage";
  8842. } elsif ($st eq "lightTempOccupSensor.03") {
  8843. # Light, Temperatur and Occupancy Sensor (EEP A5-08-03)
  8844. $lux = $db[2] * 6;
  8845. $temp = sprintf "%0.1f", -30 + $db[1] * 80 / 255;
  8846. push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage";
  8847. push @event, "3:presence:$presence";
  8848. push @event, "3:temperature:$temp";
  8849. push @event, "3:voltage:$voltage";
  8850. }
  8851. push @event, "3:brightness:$lux";
  8852. push @event, "3:motion:$motion";
  8853. } elsif ($st eq "lightCtrlState.01") {
  8854. # Lighting Controller State (EEP A5-11-01)
  8855. # $db[3] is the illumination where 0x00 = 0 lx ... 0xFF = 510 lx
  8856. # $db[2] is the illumination Setpoint where 0x00 = 0 ... 0xFF = 255
  8857. # $db[1] is the Dimming Output Level where 0x00 = 0 ... 0xFF = 255
  8858. # $db[0]_bit_7 is the Repeater state where 0 = disabled, 1 = enabled
  8859. # $db[0]_bit_6 is the Power Relay Timer state where 0 = disabled, 1 = enabled
  8860. # $db[0]_bit_5 is the Daylight Harvesting state where 0 = disabled, 1 = enabled
  8861. # $db[0]_bit_4 is the Dimming mode where 0 = switching, 1 = dimming
  8862. # $db[0]_bit_2 is the Magnet Contact state where 0 = open, 1 = closed
  8863. # $db[0]_bit_1 is the Occupancy (prensence) state where 0 = absent, 1 = present
  8864. # $db[0]_bit_0 is the Power Relay state where 0 = off, 1 = on
  8865. push @event, "3:brightness:" . ($db[3] << 1);
  8866. push @event, "3:illum:$db[2]";
  8867. push @event, "3:dim:$db[1]";
  8868. push @event, "3:powerRelayTimer:" . ($db[0] & 0x80 ? "enabled" : "disabled");
  8869. push @event, "3:repeater:" . ($db[0] & 0x40 ? "enabled" : "disabled");
  8870. push @event, "3:daylightHarvesting:" . ($db[0] & 0x20 ? "enabled" : "disabled");
  8871. push @event, "3:mode:" . ($db[0] & 0x10 ? "dimming" : "switching");
  8872. push @event, "3:contact:" . ($db[0] & 4 ? "closed" : "open");
  8873. push @event, "3:presence:" . ($db[0] & 2 ? "present" : "absent");
  8874. push @event, "3:powerSwitch:" . ($db[0] & 1 ? "on" : "off");
  8875. push @event, "3:state:" . ($db[0] & 1 ? "on" : "off");
  8876. } elsif ($st eq "tempCtrlState.01") {
  8877. # Temperature Controller Output (EEP A5-11-02)
  8878. # $db[3] is the Control Variable where 0x00 = 0 % ... 0xFF = 100 %
  8879. # $db[2] is the Fan Stage
  8880. # $db[1] is the Actual Setpoint where 0x00 = 0 °C ... 0xFF = 51.2 °C
  8881. # $db[0]_bit_7 is the Alarm state where 0 = no, 1 = yes
  8882. # $db[0]_bit_6 ... $db[0]_bit_5 is the Controller Mode
  8883. # $db[0]_bit_4 is the Controller State where 0 = auto, 1 = override
  8884. # $db[0]_bit_2 is the Energy hold-off where 0 = normal, 1 = hold-off
  8885. # $db[0]_bit_1 ... $db[0]_bit_0is the Occupancy (prensence) state where 0 = present
  8886. # 1 = absent, 3 = standby, 4 = frost
  8887. push @event, "3:controlVar:" . sprintf "%d", $db[3] * 100 / 255;
  8888. if (($db[2] & 3) == 0) {
  8889. push @event, "3:fan:0";
  8890. } elsif (($db[2] & 3) == 1){
  8891. push @event, "3:fan:1";
  8892. } elsif (($db[2] & 3) == 2){
  8893. push @event, "3:fan:2";
  8894. } elsif (($db[2] & 3) == 3){
  8895. push @event, "3:fan:3";
  8896. } elsif ($db[2] == 255){
  8897. push @event, "3:fan:unknown";
  8898. }
  8899. push @event, "3:fanMode:" . ($db[2] & 0x10 ? "auto" : "manual");
  8900. my $setpointTemp = sprintf "%0.1f", $db[1] * 0.2;
  8901. push @event, "3:setpointTemp:$setpointTemp";
  8902. push @event, "3:alarm:" . ($db[0] & 1 ? "on" : "off");
  8903. my $controllerMode = ($db[0] & 0x60) >> 5;
  8904. if ($controllerMode == 0) {
  8905. push @event, "3:controllerMode:auto";
  8906. } elsif ($controllerMode == 1) {
  8907. push @event, "3:controllerMode:heating";
  8908. } elsif ($controllerMode == 2) {
  8909. push @event, "3:controllerMode:cooling";
  8910. } elsif ($controllerMode == 3) {
  8911. push @event, "3:controllerMode:off";
  8912. }
  8913. push @event, "3:controllerState:" . ($db[0] & 0x10 ? "override" : "auto");
  8914. push @event, "3:energyHoldOff:" . ($db[0] & 4 ? "holdoff" : "normal");
  8915. if (($db[0] & 3) == 0) {
  8916. push @event, "3:presence:present";
  8917. } elsif (($db[0] & 3) == 1){
  8918. push @event, "3:presence:absent";
  8919. } elsif (($db[0] & 3) == 2){
  8920. push @event, "3:presence:standby";
  8921. } elsif (($db[0] & 3) == 3){
  8922. push @event, "3:presence:frost";
  8923. }
  8924. push @event, "3:state:$setpointTemp";
  8925. } elsif ($st eq "shutterCtrlState.01") {
  8926. # Blind Status (EEP A5-11-03)
  8927. # $db[3] is the Shutter Position where 0 = 0 % ... 100 = 100 %
  8928. # $db[2]_bit_7 is the Angle sign where 0 = positive, 1 = negative
  8929. # $db[2]_bit_6 ... $db[2]_bit_0 where 0 = 0° ... 90 = 180°
  8930. # $db[1]_bit_7 is the Position Value Flag where 0 = no available, 1 = available
  8931. # $db[1]_bit_6 is the Angle Value Flag where 0 = no available, 1 = available
  8932. # $db[1]_bit_5 ... $db[1]_bit_4 is the Error State (alarm)
  8933. # $db[1]_bit_3 ... $db[1]_bit_2 is the End-position State
  8934. # $db[1]_bit_1 ... $db[1]_bit_0 is the Shutter State
  8935. # $db[0]_bit_7 is the Service Mode where 0 = no, 1 = yes
  8936. # $db[0]_bit_6 is the Position Mode where 0 = normal, 1 = inverse
  8937. if ($db[1] & 0x80) {
  8938. push @event, "3:position:" . $db[3];
  8939. }
  8940. my $anglePos = ($db[2] & 0x7F) << 1;
  8941. if ($db[2] & 0x80) {$anglePos *= -1;}
  8942. if ($db[1] & 0x40) {
  8943. push @event, "3:anglePos:" . $anglePos;
  8944. }
  8945. my $alarm = ($db[1] & 0x30) >> 4;
  8946. if ($alarm == 0) {
  8947. push @event, "3:alarm:off";
  8948. } elsif ($alarm == 1){
  8949. push @event, "3:alarm:no_endpoints_defined";
  8950. } elsif ($alarm == 2){
  8951. push @event, "3:alarm:on";
  8952. } elsif ($alarm == 3){
  8953. push @event, "3:alarm:not_used";
  8954. }
  8955. my $endPosition = ($db[1] & 0x0C) >> 2;
  8956. if ($endPosition == 0) {
  8957. push @event, "3:endPosition:not_available";
  8958. push @event, "3:state:not_available";
  8959. } elsif ($endPosition == 1) {
  8960. push @event, "3:endPosition:not_reached";
  8961. push @event, "3:state:not_reached";
  8962. } elsif ($endPosition == 2) {
  8963. push @event, "3:endPosition:open";
  8964. push @event, "3:state:open";
  8965. } elsif ($endPosition == 3){
  8966. push @event, "3:endPosition:closed";
  8967. push @event, "3:state:closed";
  8968. }
  8969. my $shutterState = $db[1] & 3;
  8970. if (($db[1] & 3) == 0) {
  8971. push @event, "3:shutterState:not_available";
  8972. } elsif (($db[1] & 3) == 1) {
  8973. push @event, "3:shutterState:stopped";
  8974. } elsif (($db[1] & 3) == 2){
  8975. push @event, "3:shutterState:opens";
  8976. } elsif (($db[1] & 3) == 3){
  8977. push @event, "3:shutterState:closes";
  8978. }
  8979. push @event, "3:serviceOn:" . ($db[0] & 0x80 ? "yes" : "no");
  8980. push @event, "3:positionMode:" . ($db[0] & 0x40 ? "inverse" : "normal");
  8981. } elsif ($st eq "lightCtrlState.02") {
  8982. # Extended Lighting Status (EEP A5-11-04)
  8983. # $db[3] the contents of the variable depends on the parameter mode
  8984. # $db[2] the contents of the variable depends on the parameter mode
  8985. # $db[1] the contents of the variable depends on the parameter mode
  8986. # $db[0]_bit_7 is the Service Mode where 0 = no, 1 = yes
  8987. # $db[0]_bit_6 is the operating hours flag where 0 = not_available, 1 = available
  8988. # $db[0]_bit_5 ... $db[0]_bit_4 is the Error State (alarm)
  8989. # $db[0]_bit_2 ... $db[0]_bit_1 is the parameter mode
  8990. # $db[0]_bit_0 is the lighting status where 0 = off, 1 = on
  8991. push @event, "3:serviceOn:" . ($db[1] & 0x80 ? "yes" : "no");
  8992. my $alarm = ($db[0] & 0x30) >> 4;
  8993. if ($alarm == 0) {
  8994. push @event, "3:alarm:off";
  8995. } elsif ($alarm == 1){
  8996. push @event, "3:alarm:lamp_failure";
  8997. } elsif ($alarm == 2){
  8998. push @event, "3:alarm:internal_failure";
  8999. } elsif ($alarm == 3){
  9000. push @event, "3:alarm:external_periphery_failure";
  9001. }
  9002. my $mode = ($db[0] & 6) >> 1;
  9003. if ($mode == 0) {
  9004. # dimmer value and lamp operating hours
  9005. push @event, "3:dim:$db[3]";
  9006. if ($db[0] & 40) {
  9007. push @event, "3:lampOpHours:" . ($db[2] << 8 | $db[1]);
  9008. } else {
  9009. push @event, "3:lampOpHours:unknown";
  9010. }
  9011. } elsif ($mode == 1){
  9012. # RGB value
  9013. push @event, "3:red:$db[3]";
  9014. push @event, "3:green:$db[2]";
  9015. push @event, "3:blue:$db[1]";
  9016. push @event, "3:rgb:" . substr($data, 0, 6);
  9017. } elsif ($mode == 2){
  9018. # energy metering value
  9019. my @measureUnit = ("mW", "W", "kW", "MW", "Wh", "kWh", "MWh", "GWh",
  9020. "mA", "A", "mV", "V");
  9021. if ($db[1] < 4) {
  9022. push @event, "3:power:" . ($db[3] << 8 | $db[2]);
  9023. push @event, "3:powerUnit:" . $measureUnit[$db[1]];
  9024. } elsif ($db[1] < 8) {
  9025. push @event, "3:energy:" . ($db[3] << 8 | $db[2]);
  9026. push @event, "3:energyUnit:" . $measureUnit[$db[1]];
  9027. } elsif ($db[1] == 8) {
  9028. push @event, "3:current:" . ($db[3] << 8 | $db[2]);
  9029. push @event, "3:currentUnit:" . $measureUnit[$db[1]];
  9030. } elsif ($db[1] == 9) {
  9031. push @event, "3:current:" . sprintf "%0.1f", ($db[3] << 8 | $db[2]) / 10;
  9032. push @event, "3:currentUnit:" . $measureUnit[$db[1]];
  9033. } elsif ($db[1] == 10) {
  9034. push @event, "3:voltage:" . ($db[3] << 8 | $db[2]);
  9035. push @event, "3:voltageUnit:" . $measureUnit[$db[1]];
  9036. } elsif ($db[1] == 11) {
  9037. push @event, "3:voltage:" . sprintf "%0.1f", ($db[3] << 8 | $db[2]) / 10;
  9038. push @event, "3:voltageUnit:" . $measureUnit[$db[1]];
  9039. } else {
  9040. push @event, "3:measuredValue:" . ($db[3] << 8 | $db[2]);
  9041. push @event, "3:measureUnit:unknown";
  9042. }
  9043. } elsif ($mode == 3){
  9044. # not used
  9045. }
  9046. push @event, "3:powerSwitch:" . ($db[0] & 1 ? "on" : "off");
  9047. push @event, "3:state:" . ($db[0] & 1 ? "on" : "off");
  9048. } elsif ($st eq "switch.05") {
  9049. # Dual Channel Switch Actuator
  9050. # (A5-11-05)
  9051. if ($db[0] & 1) {
  9052. push @event, "3:workingMode:" . (($db[0] & 0x70) >> 4);
  9053. push @event, "3:channel1:" . ($db[0] & 2 ? "on" : "off");
  9054. push @event, "3:channel2:" . ($db[0] & 4 ? "on" : "off");
  9055. push @event, "3:state:1: " . ($db[0] & 2 ? "on" : "off") . " 2: " . ($db[0] & 4 ? "on" : "off");
  9056. }
  9057. } elsif ($st =~ m/^autoMeterReading\.0[0-3]$/ || $st eq "actuator.01" && $manufID eq "033") {
  9058. # Automated meter reading (AMR) (EEP A5-12-00 ... A5-12-03)
  9059. # $db[3] (MSB) + $db[2] + $db[1] (LSB) is the Meter reading
  9060. # $db[0]_bit_7 ... $db[0]_bit_4 is the Measurement channel
  9061. # $db[0]_bit_2 is the Data type where 0 = cumulative value, 1 = current value
  9062. # $db[0]_bit_1 ... $db[0]_bit_0 is the Divisor where 0 = x/1, 1 = x/10,
  9063. # 2 = x/100, 3 = x/1000
  9064. my $dataType = ($db[0] & 4) >> 2;
  9065. my $divisor = $db[0] & 3;
  9066. my $meterReading;
  9067. if ($divisor == 3) {
  9068. $meterReading = sprintf "%.3f", ($db[3] << 16 | $db[2] << 8 | $db[1]) / 1000;
  9069. } elsif ($divisor == 2) {
  9070. $meterReading = sprintf "%.2f", ($db[3] << 16 | $db[2] << 8 | $db[1]) / 100;
  9071. } elsif ($divisor == 1) {
  9072. $meterReading = sprintf "%.1f", ($db[3] << 16 | $db[2] << 8 | $db[1]) / 10;
  9073. } else {
  9074. $meterReading = $db[3] << 16 | $db[2] << 8 | $db[1];
  9075. }
  9076. my $channel = $db[0] >> 4;
  9077. if ($st eq "autoMeterReading.00") {
  9078. # Automated meter reading (AMR), Counter (EEP A5-12-01)
  9079. # [Thermokon SR-MI-HS, untested]
  9080. if ($dataType == 1) {
  9081. # current value
  9082. push @event, "3:currentValue" . sprintf('%02d', $channel) . ":$meterReading";
  9083. push @event, "3:state:$meterReading";
  9084. } else {
  9085. # cumulative counter
  9086. push @event, "3:counter" . sprintf('%02d', $channel) . ":$meterReading";
  9087. }
  9088. } elsif ($st eq "autoMeterReading.01" || $st eq "actuator.01" && $manufID eq "033") {
  9089. # Automated meter reading (AMR), Electricity (EEP A5-12-01)
  9090. # [Eltako FSS12, FWZ12, DSZ14DRS, DSZ14WDRS, DWZ61]
  9091. # $db[0]_bit_7 ... $db[0]_bit_4 is the Tariff info
  9092. # $db[0]_bit_2 is the Data type where 0 = cumulative value kWh,
  9093. # 1 = current value W
  9094. if ($db[0] == 0x8F && $manufID eq "00D") {
  9095. # Eltako, read meter serial number
  9096. my $serialNumber;
  9097. if ($db[1] == 0) {
  9098. # first 2 digits of the serial number
  9099. $serialNumber = substr(ReadingsVal($name, "serialNumber", "S-------"), 4, 4);
  9100. $serialNumber = sprintf "S-%01x%01x%4s", $db[3] >> 4, $db[3] & 0x0F, $serialNumber;
  9101. } else {
  9102. # last 4 digits of the serial number
  9103. $serialNumber = substr(ReadingsVal($name, "serialNumber", "S---"), 0, 4);
  9104. $serialNumber = sprintf "%4s%01x%01x%01x%01x", $serialNumber,
  9105. $db[2] >> 4, $db[2] & 0x0F, $db[3] >> 4, $db[3] & 0x0F;
  9106. }
  9107. push @event, "3:serialNumber:$serialNumber";
  9108. } elsif ($dataType == 1) {
  9109. # momentary power
  9110. push @event, "3:power:$meterReading";
  9111. if (!($st eq "actuator.01" && $manufID eq "033")) {
  9112. push @event, "3:state:$meterReading";
  9113. }
  9114. } else {
  9115. # power consumption
  9116. push @event, "3:energy$channel:$meterReading";
  9117. push @event, "3:currentTariff:$channel";
  9118. }
  9119. } elsif ($st eq "autoMeterReading.02" || $st eq "autoMeterReading.03") {
  9120. # Automated meter reading (AMR), Gas, Water (EEP A5-12-02, A5-12-03)
  9121. if ($dataType == 1) {
  9122. # current value
  9123. push @event, "3:flowrate:$meterReading";
  9124. push @event, "3:state:$meterReading";
  9125. } else {
  9126. # cumulative counter
  9127. push @event, "3:consumption$channel:$meterReading";
  9128. push @event, "3:currentTariff:$channel";
  9129. }
  9130. }
  9131. } elsif ($st =~ m/^autoMeterReading\.0[45]$/) {
  9132. # $db[1] is the temperature 0 .. 0xFF >> -40 ... 40
  9133. # $db[0]_bit_1 ... $db[0]_bit_0 is the battery level
  9134. my $temperature = sprintf "%0.1f", ($db[1] / 255 * 80) - 40;
  9135. my $battery = $db[0] & 3;
  9136. if ($battery == 3) {
  9137. $battery = "empty";
  9138. } elsif ($battery == 2) {
  9139. $battery = "low";
  9140. } elsif ($battery == 1) {
  9141. $battery = "ok";
  9142. } else {
  9143. $battery = "full";
  9144. }
  9145. push @event, "3:battery:$battery";
  9146. push @event, "3:temperature:$temperature";
  9147. if ($st eq "autoMeterReading.04") {
  9148. # Automated meter reading (AMR), Temperature, Load (EEP A5-12-04)
  9149. # $db[3] ... $db[2]_bit_2 is the Current Value in gram
  9150. my $weight = $db[3] << 6 | $db[2];
  9151. push @event, "3:weight:$weight";
  9152. push @event, "3:state:T: $temperature W: $weight B: $battery";
  9153. } elsif ($st eq "autoMeterReading.05") {
  9154. # Automated meter reading (AMR), Temperature, Container (EEP A5-12-05)
  9155. # $db[3] ... $db[2]_bit_6 is position sensor
  9156. my @sp;
  9157. $sp[0] = ($db[3] & 128) >> 7;
  9158. $sp[1] = ($db[3] & 64) >> 6;
  9159. $sp[2] = ($db[3] & 32) >> 5;
  9160. $sp[3] = ($db[3] & 16) >> 4;
  9161. $sp[4] = ($db[3] & 8) >> 3;
  9162. $sp[5] = ($db[3] & 4) >> 2;
  9163. $sp[6] = ($db[3] & 2) >> 1;
  9164. $sp[7] = $db[3] & 1;
  9165. $sp[8] = ($db[2] & 128) >> 7;
  9166. $sp[9] = ($db[2] & 64) >> 6;
  9167. my $amount = 0;
  9168. for (my $spCntr = 0; $spCntr <= 9; $spCntr ++) {
  9169. push @event, "3:location" . $spCntr . ":" . ($sp[$spCntr] ? "possessed" : "not_possessed");
  9170. $amount += $sp[$spCntr];
  9171. }
  9172. push @event, "3:amount:$amount";
  9173. push @event, "3:state:T: $temperature L: " . $sp[0] . $sp[1] . " " . $sp[2] . $sp[3] .
  9174. " " . $sp[4] . $sp[5] . " " . $sp[6] . $sp[7] . " " . $sp[8] . $sp[9] . " B: $battery";
  9175. }
  9176. } elsif ($st eq "autoMeterReading.10") {
  9177. # Automated meter reading (AMR), current meter 16 channels (EEP A5-12-10)
  9178. my $meterReading = hex(substr($data, 0, 6)) / 10 ** ($db[0] & 3);
  9179. my $channel = sprintf '%02d', $db[0] & 0xF0 >> 4;
  9180. my $scaleDecimals = "%0." . ($db[0] & 3) . "f";
  9181. push @event, "3:currentTariff:$channel";
  9182. if ($db[0] & 4) {
  9183. # current
  9184. push @event, "3:current$channel:" . sprintf "$scaleDecimals", $meterReading;
  9185. push @event, "3:state:" . sprintf "$scaleDecimals", $meterReading;
  9186. } else {
  9187. # electric charge
  9188. push @event, "3:electricChange$channel:" . sprintf "$scaleDecimals", $meterReading;
  9189. }
  9190. } elsif ($st eq "environmentApp") {
  9191. # Environmental Applications (EEP A5-13-01 ... EEP A5-13-06, EEP A5-13-10)
  9192. # [Eltako FWS61]
  9193. # $db[0]_bit_7 ... $db[0]_bit_4 is the Identifier
  9194. my $identifier = $db[0] >> 4;
  9195. if ($identifier == 1) {
  9196. # Weather Station (EEP A5-13-01)
  9197. # $db[3] is the dawn sensor where 0x00 = 0 lx ... 0xFF = 999 lx
  9198. # $db[2] is the temperature where 0x00 = -40 °C ... 0xFF = 80 °C
  9199. # $db[1] is the wind speed where 0x00 = 0 m/s ... 0xFF = 70 m/s
  9200. # $db[0]_bit_2 is day / night where 0 = day, 1 = night
  9201. # $db[0]_bit_1 is rain indication where 0 = no (no rain), 1 = yes (rain)
  9202. my $dawn = sprintf "%d", $db[3] * 999 / 255;
  9203. my $temp = sprintf "%0.1f", -40 + $db[2] * 120 / 255;
  9204. my $windSpeed = sprintf "%0.1f", $db[1] * 70 / 255;
  9205. my $dayNight = $db[0] & 4 ? "night" : "day";
  9206. my $isRaining = $db[0] & 2 ? "yes" : "no";
  9207. push @event, "3:brightness:$dawn";
  9208. push @event, "3:dayNight:$dayNight";
  9209. push @event, "3:isRaining:$isRaining";
  9210. push @event, "3:temperature:$temp";
  9211. push @event, "3:windSpeed:$windSpeed";
  9212. push @event, "3:state:T: $temp B: $dawn W: $windSpeed IR: $isRaining";
  9213. } elsif ($identifier == 2) {
  9214. # Sun Intensity (EEP A5-13-02)
  9215. # $db[3] is the sun exposure west where 0x00 = 1 lx ... 0xFF = 150 klx
  9216. # $db[2] is the sun exposure south where 0x00 = 1 lx ... 0xFF = 150 klx
  9217. # $db[1] is the sun exposure east where 0x00 = 1 lx ... 0xFF = 150 klx
  9218. # $db[0]_bit_2 is hemisphere where 0 = north, 1 = south
  9219. push @event, "3:hemisphere:" . ($db[0] & 4 ? "south" : "north");
  9220. push @event, "3:sunWest:" . sprintf "%d", 1 + $db[3] * 149999 / 255;
  9221. push @event, "3:sunSouth:" . sprintf "%d", 1 + $db[2] * 149999 / 255;
  9222. push @event, "3:sunEast:" . sprintf "%d", 1 + $db[1] * 149999 / 255;
  9223. } elsif ($identifier == 3) {
  9224. # Date exchange (EEP A5-13-03)
  9225. push @event, "3:date:" . ($db[1] + 2000) . '-' . $db[2] . '-' . $db[3];
  9226. } elsif ($identifier == 4) {
  9227. # Time und Day exchange (EEP A5-13-04)
  9228. my @day = ('', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday');
  9229. my $day = $db[3] >> 5;
  9230. push @event, "3:weekday:" . $day[$day];
  9231. if ($db[0] & 4) {
  9232. # 12 h time format
  9233. push @event, "3:time:" . ($db[3] & 0x1F) . ':' . $db[2] . ':' . $db[1] . ' ' . ($db[1] & 2 ? 'PM' : 'AM');
  9234. } else {
  9235. push @event, "3:time:" . ($db[3] & 0x1F) . ':' . $db[2] . ':' . $db[1];
  9236. }
  9237. push @event, "3:timeSource:" . ($db[0] & 1 ? 'GPS' : 'RTC');
  9238. } elsif ($identifier == 5) {
  9239. # Direction exchange (EEP A5-13-05)
  9240. push @event, "3:elevation:" . ($db[3] - 90);
  9241. push @event, "3:azimuth:" . hex(substr($data, 2, 4));
  9242. } elsif ($identifier == 6) {
  9243. # Geographic Position exchange (EEP A5-13-06)
  9244. push @event, "3:latitude:" . sprintf("%0.3f", hex(substr($data, 0, 1) . substr($data, 2, 2)) / 22.75 - 90);
  9245. push @event, "3:longitude:" . sprintf("%0.3f", hex(substr($data, 1, 1) . substr($data, 4, 2)) / 13.375 - 180);
  9246. } elsif ($identifier == 7) {
  9247. # Sun Position and Radiation (EEP A5-13-10)
  9248. # $db[3]_bit_7 ... $db[3]_bit_1 is Sun Elevation where 0 = 0 ° ... 90 = 90 °
  9249. # $db[3]_bit_0 is day / night where 0 = day, 1 = night
  9250. # $db[2] is Sun Azimuth where 0 = -90 ° ... 180 = 90 °
  9251. # $db[1] and $db[0]_bit_2 ... $db[0]_bit_0 is Solar Radiation where
  9252. # 0 = 0 W/m2 ... 2000 = 2000 W/m2
  9253. my $sunElev = $db[3] >> 1;
  9254. my $sunAzim = $db[2] - 90;
  9255. my $solarRad = $db[1] << 3 | $db[0] & 7;
  9256. push @event, "3:dayNight:" . ($db[3] & 1 ? "night" : "day");
  9257. push @event, "3:solarRadiation:$solarRad";
  9258. push @event, "3:sunAzimuth:$sunAzim";
  9259. push @event, "3:sunElevation:$sunElev";
  9260. push @event, "3:state:SRA: $solarRad SNA: $sunAzim SNE: $sunElev";
  9261. } else {
  9262. # EEP A5-13-03 ... EEP A5-13-06 not implemented
  9263. }
  9264. } elsif ($st eq "windSensor.01") {
  9265. # Wind Sensor (EEP A5-13-07)
  9266. my @windDirection = ('NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N');
  9267. push @event, "3:battery:" . ($db[0] & 1 ? 'low' : 'ok');
  9268. push @event, "3:windDirection:" . $windDirection[$db[3]];
  9269. push @event, "3:windSpeedAverage:" . $db[2] / 1.275;
  9270. push @event, "3:windSpeedMax:" . $db[1] / 1.275;
  9271. push @event, "3:state:" . $db[2] / 1.275;
  9272. } elsif ($st eq "rainSensor.01") {
  9273. # Rain Sensor (EEP A5-13-08)
  9274. my $ras = ($db[3] & 0x40) >> 6;
  9275. my $rfa = ($db[3] & 0x3F) / 10;
  9276. my $rfc = hex(substr($data, 2, 4));
  9277. my $rain = $rfc * 0.6875 * (1 + $ras * $rfa / 100);
  9278. push @event, "3:battery:" . ($db[0] & 1 ? 'low' : 'ok');
  9279. push @event, "3:rain:$rain";
  9280. push @event, "3:state:$rain";
  9281. } elsif ($st eq "multiFuncSensor") {
  9282. # Multi-Func Sensor (EEP A5-14-01 ... A5-14-06)
  9283. # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
  9284. # $db[3] > 0xFA is error code
  9285. # $db[2] is the illuminance where min 0x00 = 0 lx, max 0xFA = 1000 lx
  9286. # $db[0]_bit_1 is Vibration where 0 = off, 1 = on
  9287. # $db[0]_bit_0 is Contact where 0 = closed, 1 = open
  9288. my $lux = $db[2] << 2;
  9289. if ($db[2] == 251) {$lux = "over range";}
  9290. my $voltage = sprintf "%0.1f", $db[3] * 0.02;
  9291. if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
  9292. my $vibration = $db[0] & 2 ? "on" : "off";
  9293. my $contact = $db[0] & 1 ? "open" : "closed";
  9294. push @event, "3:brightness:$lux";
  9295. push @event, "3:contact:$contact";
  9296. push @event, "3:vibration:$vibration";
  9297. push @event, "3:voltage:$voltage";
  9298. push @event, "3:state:C: $contact V: $vibration E: $lux U: $voltage";
  9299. } elsif ($st eq "doorContact") {
  9300. # dual door contact (EEP A5-14-07, A5-14-08)
  9301. if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} ne $data) {
  9302. my $voltage = sprintf "%0.1f", $db[3] * 0.02;
  9303. my $doorContact = $db[0] & 4 ? 'open' : 'closed';
  9304. my $lockContact = $db[0] & 2 ? 'unlocked' : 'locked';
  9305. my $vibration = $db[0] & 1 ? 'on' : 'off';
  9306. push @event, "3:voltage:$voltage";
  9307. push @event, "3:contact:$doorContact";
  9308. push @event, "3:block:$lockContact";
  9309. push @event, "3:vibration:$vibration";
  9310. push @event, "3:state:C: $doorContact B: $lockContact V: $vibration U: $voltage";
  9311. $hash->{helper}{lastEvent} = $data;
  9312. }
  9313. CommandDeleteReading(undef, "$name alarm");
  9314. RemoveInternalTimer($hash->{helper}{timer}{alarm}) if(exists $hash->{helper}{timer}{alarm});
  9315. @{$hash->{helper}{timer}{alarm}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
  9316. InternalTimer(gettimeofday() + 66, 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0);
  9317. } elsif ($st eq "windowContact") {
  9318. # window contact (EEP A5-14-09, A5-14-0A)
  9319. if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} ne $data) {
  9320. my $voltage = sprintf "%0.1f", $db[3] * 0.02;
  9321. my %window = (0 => 'closed', 1 => 'tilt', 2 => 'reserved', 3 => 'open');
  9322. my $window = $window{(($db[0] & 6) >> 1)};
  9323. my $vibration = $db[0] & 1 ? 'on' : 'off';
  9324. push @event, "3:voltage:$voltage";
  9325. push @event, "3:window:$window";
  9326. push @event, "3:vibration:$vibration";
  9327. push @event, "3:state:W: $window V: $vibration U: $voltage";
  9328. $hash->{helper}{lastEvent} = $data;
  9329. }
  9330. CommandDeleteReading(undef, "$name alarm");
  9331. RemoveInternalTimer($hash->{helper}{timer}{alarm}) if(exists $hash->{helper}{timer}{alarm});
  9332. @{$hash->{helper}{timer}{alarm}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
  9333. InternalTimer(gettimeofday() + 66, 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0);
  9334. } elsif ($st =~ m/^digitalInput\.0[12]$/) {
  9335. # Digital Input (EEP A5-30-01, A5-30-02)
  9336. my $contact;
  9337. if ($st eq "digitalInput.01") {
  9338. # Single Input Contact, Batterie Monitor (EEP A5-30-01)
  9339. # [Thermokon SR65 DI, untested]
  9340. # $db[2] is the supply voltage, if >= 121 = battery ok
  9341. # $db[1] is the input state, if <= 195 = contact closed
  9342. my $battery = $db[2] >= 121 ? "ok" : "low";
  9343. $contact = $db[1] <= 195 ? "closed" : "open";
  9344. push @event, "3:battery:$battery";
  9345. } else {
  9346. # Single Input Contact (EEP A5-30-02)
  9347. # $db[0]_bit_0 is the input state where 0 = closed, 1 = open
  9348. $contact = $db[0] & 1 ? "open" : "closed";
  9349. }
  9350. push @event, "3:contact:$contact";
  9351. push @event, "3:state:$contact";
  9352. } elsif ($st eq "digitalInput.03") {
  9353. # 4 digital inputs, wake, temperature (EEP A5-30-03)
  9354. my $temperature = sprintf "%0.1f", 40 - $db[2] * 40 / 255;
  9355. push @event, "3:temperature:$temperature";
  9356. if ($model eq 'Eltako_TF_RWB') {
  9357. # Eltako TF-RWB smoke detector
  9358. my $alarm = $db[1] & 16 ? 'off' : 'smoke-alarm';
  9359. if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} ne $alarm || ReadingsVal($name, 'alarm', '') eq 'dead_sensor') {
  9360. push @event, "3:alarm:$alarm";
  9361. $hash->{helper}{lastEvent} = $alarm;
  9362. }
  9363. push @event, "3:state:$alarm";
  9364. RemoveInternalTimer($hash->{helper}{timer}{alarm}) if(exists $hash->{helper}{timer}{alarm});
  9365. @{$hash->{helper}{timer}{alarm}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
  9366. InternalTimer(gettimeofday() + 1980, 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0);
  9367. } else {
  9368. my $in0 = $db[1] & 1;
  9369. my $in1 = ($db[1] & 2) > 1;
  9370. my $in2 = ($db[1] & 4) > 2;
  9371. my $in3 = ($db[1] & 8) > 3;
  9372. my $wake = $db[1] & 16 ? 'high' : 'low';
  9373. push @event, "3:in0:$in0";
  9374. push @event, "3:in1:$in1";
  9375. push @event, "3:in2:$in2";
  9376. push @event, "3:in3:$in3";
  9377. push @event, "3:wake:$wake";
  9378. push @event, "3:state:T: $temperature I: " . $in0 . $in1 . $in2 . $in3 . " W: " . $wake;
  9379. }
  9380. } elsif ($st eq "digitalInput.04") {
  9381. # 3 digital inputs, 1 digital input 8 bit (EEP A5-30-04)
  9382. my $in0 = $db[0] & 1;
  9383. my $in1 = ($db[0] & 2) > 1;
  9384. my $in2 = ($db[0] & 4) > 2;
  9385. my $in3 = $db[1];
  9386. push @event, "3:in0:$in0";
  9387. push @event, "3:in1:$in1";
  9388. push @event, "3:in2:$in2";
  9389. push @event, "3:in3:$in3";
  9390. push @event, "3:state:" . $in0 . $in1 . $in2 . " " . $in3;
  9391. } elsif ($st eq "digitalInput.05") {
  9392. # single input contact, retransmission, battery monitor (EEP A5-30-05)
  9393. my $signalIdx = $db[1] & 0x7F;
  9394. my $signalIdxLast = ReadingsVal($name, "signalIdx", undef);
  9395. my $signalType = $db[1] & 0x80 ? "heartbeat" : "event";
  9396. push @event, "3:voltage:" . sprintf "%0.1f", $db[2] / 255 * 3.3;
  9397. push @event, "3:signalIdx:$signalIdx";
  9398. push @event, "3:telegramType:$signalType";
  9399. if (defined $signalIdxLast) {
  9400. if ($signalIdx == $signalIdxLast + 1 || $signalIdx == 0 && $signalIdxLast == 127) {
  9401. push @event, "3:state:$signalType";
  9402. } else {
  9403. push @event, "3:state:error";
  9404. }
  9405. } else {
  9406. push @event, "3:state:$signalType";
  9407. }
  9408. } elsif ($st eq "gateway") {
  9409. # Gateway (EEP A5-38-08)
  9410. # $db[3] is the command ID ($gwCmdID)
  9411. # Eltako devices not send teach-in telegrams
  9412. if(($db[0] & 8) == 0) {
  9413. # teach-in, identify and store command type in attr gwCmd
  9414. my $gwCmd = AttrVal($name, "gwCmd", undef);
  9415. if (!$gwCmd) {
  9416. $gwCmd = $EnO_gwCmd[$db[3] - 1];
  9417. $attr{$name}{gwCmd} = $gwCmd;
  9418. }
  9419. }
  9420. if ($db[3] == 1) {
  9421. # Switching
  9422. # Eltako devices not send A5 telegrams
  9423. push @event, "3:executeTime:" . sprintf "%0.1f", (($db[2] << 8) | $db[1]) / 10;
  9424. push @event, "3:block:" . ($db[0] & 4 ? "lock" : "unlock");
  9425. push @event, "3:executeType" . ($db[0] & 2 ? "delay" : "duration");
  9426. push @event, "3:state:" . ($db[0] & 1 ? "on" : "off");
  9427. } elsif ($db[3] == 2) {
  9428. # Dimming
  9429. # $db[0]_bit_2 is store final value, not used, because
  9430. # dimming value is always stored
  9431. push @event, "3:rampTime:$db[1]";
  9432. push @event, "3:state:" . ($db[0] & 0x01 ? "on" : "off");
  9433. if ($db[0] & 4) {
  9434. # Relative Dimming Range
  9435. push @event, "3:dim:" . sprintf "%d", $db[2] * 100 / 255;
  9436. } else {
  9437. push @event, "3:dim:$db[2]";
  9438. }
  9439. push @event, "3:dimValueLast:$db[2]" if ($db[2] > 0);
  9440. } elsif ($db[3] == 3) {
  9441. # Setpoint shift
  9442. # $db1 is setpoint shift where 0 = -12.7 K ... 255 = 12.8 K
  9443. my $setpointShift = sprintf "%0.1f", -12.7 + $db[1] / 10;
  9444. push @event, "3:setpointShift:$setpointShift";
  9445. push @event, "3:state:$setpointShift";
  9446. } elsif ($db[3] == 4) {
  9447. # Basic Setpoint
  9448. # $db1 is setpoint where 0 = 0 °C ... 255 = 51.2 °C
  9449. my $setpoint = sprintf "%0.1f", $db[1] / 5;
  9450. push @event, "3:setpoint:$setpoint";
  9451. push @event, "3:state:$setpoint";
  9452. } elsif ($db[3] == 5) {
  9453. # Control variable
  9454. # $db1 is control variable override where 0 = 0 % ... 255 = 100 %
  9455. push @event, "3:controlVar:" . sprintf "%d", $db[1] * 100 / 255;
  9456. my $controllerMode = ($db[0] & 0x60) >> 5;
  9457. if ($controllerMode == 0) {
  9458. push @event, "3:controllerMode:auto";
  9459. push @event, "3:state:auto";
  9460. } elsif ($controllerMode == 1) {
  9461. push @event, "3:controllerMode:heating";
  9462. push @event, "3:state:heating";
  9463. } elsif ($controllerMode == 2){
  9464. push @event, "3:controllerMode:cooling";
  9465. push @event, "3:state:cooling";
  9466. } elsif ($controllerMode == 3){
  9467. push @event, "3:controllerMode:off";
  9468. push @event, "3:state:off";
  9469. }
  9470. push @event, "3:controllerState:" . ($db[0] & 0x10 ? "override" : "auto");
  9471. push @event, "3:energyHoldOff:" . ($db[0] & 4 ? "holdoff" : "normal");
  9472. my $occupancy = $db[0] & 3;
  9473. if ($occupancy == 0) {
  9474. push @event, "3:presence:present";
  9475. } elsif ($occupancy == 1){
  9476. push @event, "3:presence:absent";
  9477. } elsif ($occupancy == 2){
  9478. push @event, "3:presence:standby";
  9479. }
  9480. } elsif ($db[3] == 6) {
  9481. # Fan stage
  9482. if ($db[1] == 0) {
  9483. push @event, "3:fan:0";
  9484. push @event, "3:state:0";
  9485. } elsif ($db[1] == 1) {
  9486. push @event, "3:fan:1";
  9487. push @event, "3:state:1";
  9488. } elsif ($db[1] == 2) {
  9489. push @event, "3:fan:2";
  9490. push @event, "3:state:2";
  9491. } elsif ($db[1] == 3) {
  9492. push @event, "3:fan:3";
  9493. push @event, "3:state:3";
  9494. } elsif ($db[1] == 255) {
  9495. push @event, "3:fan:auto";
  9496. push @event, "3:state:auto";
  9497. }
  9498. } else {
  9499. push @event, "3:state:Gateway Command ID $db[3] unknown.";
  9500. }
  9501. } elsif ($st eq "energyManagement.01") {
  9502. # Energy Management, Demand Response
  9503. # (A5-37-01)
  9504. EnOcean_energyManagement_01Parse($hash, @db);
  9505. } elsif ($st eq "manufProfile") {
  9506. # Manufacturer Specific Applications (EEP A5-3F-7F)
  9507. if ($manufID eq "002") {
  9508. # [Thermokon SR65 3AI, untested]
  9509. # $db[3] is the input 3 where 0x00 = 0 V ... 0xFF = 10 V
  9510. # $db[2] is the input 2 where 0x00 = 0 V ... 0xFF = 10 V
  9511. # $db[1] is the input 1 where 0x00 = 0 V ... 0xFF = 10 V
  9512. my $input3 = sprintf "%0.2f", $db[3] * 10 / 255;
  9513. my $input2 = sprintf "%0.2f", $db[2] * 10 / 255;
  9514. my $input1 = sprintf "%0.2f", $db[1] * 10 / 255;
  9515. push @event, "3:input1:$input1";
  9516. push @event, "3:input2:$input2";
  9517. push @event, "3:input3:$input3";
  9518. push @event, "3:state:I1: $input1 I2: $input2 I3: $input3";
  9519. } elsif ($manufID eq "005") {
  9520. # omnio
  9521. if ($db[0] == 0x0C) {
  9522. # omnio UPH 2330/1x
  9523. my $channel = ($db[1] & 0xF0) > 4;
  9524. my $emergencyMode = $db[1] & 8 ? "on" : "off";
  9525. my $window = $db[1] & 4 ? "open" : "closed";
  9526. my $nightReduction = $db[1] & 2 ? "on" : "off";
  9527. my $state = $db[1] & 1 ? "on" : "off";
  9528. my $temperature = sprintf "%0.1f", $db[2] * 40 / 250;
  9529. my $setpointTemp = sprintf "%0.1f", $db[3] * 40 / 250;
  9530. push @event, "3:emergencyMode" . $channel . ":$emergencyMode";
  9531. push @event, "3:window" . $channel . ":$window";
  9532. push @event, "3:nightReduction" . $channel . ":$nightReduction";
  9533. push @event, "3:temperature" . $channel . ":$temperature";
  9534. push @event, "3:setpointTemp" . $channel . ":$setpointTemp";
  9535. push @event, "3:state:$state";
  9536. }
  9537. } elsif ($manufID eq "00D") {
  9538. # [Eltako shutter]
  9539. my $angleMax = AttrVal($name, "angleMax", 90);
  9540. my $angleMin = AttrVal($name, "angleMin", -90);
  9541. my $anglePos = ReadingsVal($name, ".anglePosStart", undef);
  9542. my $angleTime = AttrVal($name, "angleTime", 0);
  9543. my $position = ReadingsVal($name, ".positionStart", undef);
  9544. my $shutTime = AttrVal($name, "shutTime", 255);
  9545. my $shutTimeStop = ($db[3] << 8 | $db[2]) * 0.1;
  9546. my $state;
  9547. $angleMax = 90 if ($angleMax !~ m/^[+-]?\d+$/);
  9548. $angleMax = 180 if ($angleMax > 180);
  9549. $angleMax = -180 if ($angleMax < -180);
  9550. $angleMin = -90 if ($angleMin !~ m/^[+-]?\d+$/);
  9551. $angleMin = 180 if ($angleMin > 180);
  9552. $angleMin = -180 if ($angleMin < -180);
  9553. ($angleMax, $angleMin) = ($angleMin, $angleMax) if ($angleMin > $angleMax);
  9554. $angleMax ++ if ($angleMin == $angleMax);
  9555. $angleTime = 0 if ($angleTime !~ m/^[+-]?\d+$/);
  9556. $angleTime = 6 if ($angleTime > 6);
  9557. $angleTime = 0 if ($angleTime < 0);
  9558. $shutTime = 255 if ($shutTime !~ m/^[+-]?\d+$/);
  9559. $shutTime = 255 if ($shutTime > 255);
  9560. $shutTime = 1 if ($shutTime < 1);
  9561. if ($db[0] == 0x0A) {
  9562. push @event, "3:block:unlock";
  9563. } elsif ($db[0] == 0x0E) {
  9564. push @event, "3:block:lock";
  9565. }
  9566. if (defined $position) {
  9567. if ($db[1] == 1) {
  9568. # up
  9569. $position -= $shutTimeStop / $shutTime * 100;
  9570. if ($angleTime) {
  9571. $anglePos -= ($angleMax - $angleMin) * $shutTimeStop / $angleTime;
  9572. if ($anglePos < $angleMin) {
  9573. $anglePos = $angleMin;
  9574. }
  9575. } else {
  9576. $anglePos = $angleMin;
  9577. }
  9578. if ($position <= 0) {
  9579. $anglePos = 0;
  9580. $position = 0;
  9581. push @event, "3:endPosition:open";
  9582. $state = "open";
  9583. } else {
  9584. push @event, "3:endPosition:not_reached";
  9585. $state = "stop";
  9586. }
  9587. push @event, "3:anglePos:" . sprintf("%d", $anglePos);
  9588. push @event, "3:position:" . sprintf("%d", $position);
  9589. push @event, "3:.anglePosStart:" . sprintf("%d", $anglePos);
  9590. push @event, "3:.positionStart:" . sprintf("%d", $position);
  9591. } elsif ($db[1] == 2) {
  9592. # down
  9593. $position += $shutTimeStop / $shutTime * 100;
  9594. if ($angleTime) {
  9595. $anglePos += ($angleMax - $angleMin) * $shutTimeStop / $angleTime;
  9596. if ($anglePos > $angleMax) {
  9597. $anglePos = $angleMax;
  9598. }
  9599. } else {
  9600. $anglePos = $angleMax;
  9601. }
  9602. if($position >= 100) {
  9603. $anglePos = $angleMax;
  9604. $position = 100;
  9605. push @event, "3:endPosition:closed";
  9606. $state = "closed";
  9607. } else {
  9608. push @event, "3:endPosition:not_reached";
  9609. $state = "stop";
  9610. }
  9611. push @event, "3:anglePos:" . sprintf("%d", $anglePos);
  9612. push @event, "3:position:" . sprintf("%d", $position);
  9613. push @event, "3:.anglePosStart:" . sprintf("%d", $anglePos);
  9614. push @event, "3:.positionStart:" . sprintf("%d", $position);
  9615. } else {
  9616. $state = "not_reached";
  9617. }
  9618. push @event, "3:state:$state";
  9619. }
  9620. } else {
  9621. # Unknown Application
  9622. push @event, "3:state:Manufacturer Specific Application unknown";
  9623. }
  9624. } elsif ($st eq "PM101") {
  9625. # Light and Presence Sensor [Omnio Ratio eagle-PM101]
  9626. # The sensor also sends switching commands (RORG F6) with the senderID-1
  9627. # $db[2] is the illuminance where 0x00 = 0 lx ... 0xFF = 1000 lx
  9628. my $channel2 = $db[0] & 2 ? "on" : "off";
  9629. push @event, "3:brightness:" . ($db[2] << 2);
  9630. push @event, "3:channel1:" . ($db[0] & 1 ? "on" : "off");
  9631. push @event, "3:channel2:" . $channel2;
  9632. push @event, "3:motion:" . $channel2;
  9633. push @event, "3:state:" . $channel2;
  9634. } elsif ($st eq "radioLinkTest") {
  9635. # Radio Link Test (EEP A5-3F-00)
  9636. @{$hash->{helper}{rlt}{param}} = ('parse', $hash, $data, $subDef, 'master', $RSSI);
  9637. EnOcean_RLT($hash->{helper}{rlt}{param});
  9638. } elsif ($st eq "raw") {
  9639. # raw
  9640. push @event, "3:state:RORG: $rorg DATA: $data STATUS: $status ODATA: $odata";
  9641. } else {
  9642. # unknown devices
  9643. push @event, "3:state:$db[3]";
  9644. push @event, "3:sensor1:$db[3]";
  9645. push @event, "3:sensor2:$db[2]";
  9646. push @event, "3:sensor3:$db[1]";
  9647. push @event, "3:D3:" . ($db[0] & 8 ? 1 : 0);
  9648. push @event, "3:D2:" . ($db[0] & 4 ? 1 : 0);
  9649. push @event, "3:D1:" . ($db[0] & 2 ? 1 : 0);
  9650. push @event, "3:D0:" . ($db[0] & 1 ? 1 : 0);
  9651. }
  9652. } elsif ($rorg eq "D2") {
  9653. # VLD telegram
  9654. if ($st eq "roomCtrlPanel.00") {
  9655. # EEP D2-10-01 - D2-10-03
  9656. my ($key, $val);
  9657. # message identifier
  9658. my $mid = hex(substr($data, 0, 2)) >> 5;
  9659. # message continuation flag
  9660. my $mcf = hex(substr($data, 0, 2)) & 3;
  9661. my ($irc, $fbc, $gmt);
  9662. if ($mcf == 0) {
  9663. # message complete
  9664. # read stored telegrams
  9665. if (!defined($hash->{helper}{$mid}{messagePart})) {
  9666. $hash->{helper}{$mid}{messagePart} = 1;
  9667. $hash->{helper}{$mid}{data}{1} = $data;
  9668. } else {
  9669. $hash->{helper}{$mid}{messagePart} += 1;
  9670. $hash->{helper}{$mid}{data}{$hash->{helper}{$mid}{messagePart}} = $data;
  9671. }
  9672. if ($mid == 4) {
  9673. CommandDeleteAttr(undef, "$name timeProgram1");
  9674. CommandDeleteAttr(undef, "$name timeProgram2");
  9675. CommandDeleteAttr(undef, "$name timeProgram3");
  9676. CommandDeleteAttr(undef, "$name timeProgram4");
  9677. }
  9678. for (my $partCntr = $hash->{helper}{$mid}{messagePart}; $partCntr > 0; $partCntr --) {
  9679. $data = $hash->{helper}{$mid}{data}{$partCntr};
  9680. delete $hash->{helper}{$mid}{data}{$partCntr};
  9681. if ($partCntr == 1) {
  9682. delete $hash->{helper}{$mid}{messagePart};
  9683. } else {
  9684. $hash->{helper}{$mid}{messagePart} --;
  9685. }
  9686. $dbCntr = 0;
  9687. for (my $strCntr = length($data) / 2 - 1; $strCntr >= 0; $strCntr --) {
  9688. $db[$dbCntr] = hex substr($data, $strCntr * 2, 2);
  9689. $dbCntr++;
  9690. }
  9691. #Log3 $name, 2, "EnOcean $name EnOcean_Parse write MID $mid DATA $data to part $partCntr";
  9692. #Log3 $name, 2, "EnOcean $name EnOcean_Parse write 1 MID $mid DATA $data to " . sprintf "%02X%02X%02X%02X%02X%02X", $db[5], $db[4], $db[3], $db[2], $db[1], $db[0];
  9693. if ($mid == 0) {
  9694. # general message
  9695. $irc = ($db[0] & 56) >> 3;
  9696. $fbc = ($db[0] & 6) >> 1;
  9697. $gmt = $db[0] & 1;
  9698. #push @event, "3:general:$data";
  9699. } elsif ($mid == 1) {
  9700. # data message
  9701. my $temperature = "-";
  9702. $temperature = sprintf "%.1f", $db[0] / 254 * 40 if ($db[2] & 1);
  9703. push @event, "3:temperature:$temperature";
  9704. my $setpointTemp = "-";
  9705. $setpointTemp = sprintf "%.1f", $db[1] / 254 * 40 if ($db[2] & 2);
  9706. push @event, "3:setpointTemp:$setpointTemp";
  9707. my $roomCtrlMode = ($db[2] & 12) >> 2;
  9708. if ($roomCtrlMode == 3) {
  9709. $roomCtrlMode = "buildingProtection";
  9710. } elsif ($roomCtrlMode == 2) {
  9711. $roomCtrlMode = "preComfort";
  9712. } elsif ($roomCtrlMode == 1) {
  9713. $roomCtrlMode = "economy";
  9714. } else{
  9715. $roomCtrlMode = "comfort";
  9716. }
  9717. push @event, "3:roomCtrlMode:$roomCtrlMode";
  9718. my $heating = ($db[2] & 48) >> 4;
  9719. if ($heating == 3) {
  9720. $heating = "auto";
  9721. } elsif ($heating == 2) {
  9722. $heating = "off";
  9723. } elsif ($heating == 1) {
  9724. $heating = "on";
  9725. } else{
  9726. $heating = "-";
  9727. }
  9728. if ($heating ne "-") {
  9729. push @event, "3:heating:$heating";
  9730. }
  9731. my $cooling = ($db[2] & 192) >> 6;
  9732. if ($cooling == 3) {
  9733. $cooling = "auto";
  9734. } elsif ($cooling == 2) {
  9735. $cooling = "off";
  9736. } elsif ($cooling == 1) {
  9737. $cooling = "on";
  9738. } else{
  9739. $cooling = "-";
  9740. }
  9741. if ($cooling ne "-") {
  9742. push @event, "3:cooling:$cooling";
  9743. }
  9744. my $occupancy = $db[3] & 3;
  9745. if ($occupancy == 3) {
  9746. $occupancy = "reserved";
  9747. } elsif ($occupancy == 2) {
  9748. $occupancy = "absent";
  9749. } elsif ($occupancy == 1) {
  9750. $occupancy = "present";
  9751. } else{
  9752. $occupancy = "-";
  9753. }
  9754. if ($occupancy eq "-") {
  9755. $occupancy = ReadingsVal($name, "occupancy", "-");
  9756. } else {
  9757. push @event, "3:occupancy:$occupancy";
  9758. }
  9759. my $motion = ($db[3] & 12) >> 2;
  9760. if ($motion == 3) {
  9761. $motion = "reserved";
  9762. } elsif ($motion == 2) {
  9763. $motion = "on";
  9764. } elsif ($motion == 1) {
  9765. $motion = "off";
  9766. } else{
  9767. $motion = "-";
  9768. }
  9769. if ($motion eq "-") {
  9770. $motion = ReadingsVal($name, "motion", "-");
  9771. } else {
  9772. push @event, "3:motion:$motion";
  9773. }
  9774. push @event, "3:solarPowered:" . ($db[3] & 16 ? "no" : "yes");
  9775. my $battery = ($db[3] & 96) >> 5;
  9776. if ($battery == 3) {
  9777. $battery = "empty";
  9778. } elsif ($battery == 2) {
  9779. $battery = "low";
  9780. } elsif ($battery == 1) {
  9781. $battery = "ok";
  9782. } else{
  9783. $battery = "-";
  9784. }
  9785. if ($battery ne "-") {
  9786. push @event, "3:battery:$battery";
  9787. }
  9788. my $window = $db[4] & 3;
  9789. if ($window == 3) {
  9790. $window = "reserved";
  9791. } elsif ($window == 2) {
  9792. $window = "open";
  9793. } elsif ($window == 1) {
  9794. $window = "closed";
  9795. } else{
  9796. $window = "-";
  9797. }
  9798. if ($window ne "-") {
  9799. push @event, "3:window:$window";
  9800. }
  9801. push @event, "3:moldWarning:" . ($db[4] & 4 ? "on" : "off");
  9802. push @event, "3:customWarning1:" . ($db[4] & 8 ? "on" : "off");
  9803. push @event, "3:customWarning2:" . ($db[4] & 16 ? "on" : "off");
  9804. push @event, "3:fanSpeedMode:" . ($db[4] & 64 ? "local" : "central");
  9805. my $fanSpeed = 0;
  9806. $fanSpeed = sprintf "%d", $db[5] & 127 if ($db[4] & 128);
  9807. push @event, "3:fanSpeed:$fanSpeed";
  9808. my $humi = "-";
  9809. $humi = sprintf "%d", $db[6] / 2.55 if ($db[5] & 128);
  9810. push @event, "3:humidity:$humi";
  9811. push @event, "3:state:T: $temperature H: $humi F: $fanSpeed SPT: $setpointTemp O: $occupancy M: $motion";
  9812. } elsif ($mid == 2) {
  9813. # configuration message
  9814. $attr{$name}{blockFanSpeed} = $db[6] & 1 ? "no" : "yes";
  9815. $attr{$name}{blockSetpointTemp} = $db[6] & 2 ? "no" : "yes";
  9816. $attr{$name}{blockOccupancy} = $db[6] & 4 ? "no" : "yes";
  9817. $attr{$name}{blockTimeProgram} = $db[6] & 8 ? "no" : "yes";
  9818. $attr{$name}{blockDateTime} = $db[6] & 16 ? "no" : "yes";
  9819. $attr{$name}{blockDisplay} = $db[6] & 32 ? "no" : "yes";
  9820. $attr{$name}{blockTemp} = $db[6] & 64 ? "no" : "yes";
  9821. $attr{$name}{blockMotion} = $db[6] & 128 ? "no" : "yes";
  9822. my $pollInterval = $db[5] >> 2;
  9823. if ($pollInterval == 63) {
  9824. $attr{$name}{pollInterval} = 1440;
  9825. } elsif ($pollInterval == 62) {
  9826. $attr{$name}{pollInterval} = 720;
  9827. } elsif ($pollInterval == 61) {
  9828. $attr{$name}{pollInterval} = 180;
  9829. } else {
  9830. $attr{$name}{pollInterval} = $pollInterval;
  9831. }
  9832. $attr{$name}{blockKey} = $db[5] & 2 ? "no" : "yes";
  9833. my $displayContent = $db[4] >> 5;
  9834. my %displayContent = (7 => "humidity",
  9835. 6 => "off",
  9836. 5 => "setpointTemp",
  9837. 4 => "tempertureExtern",
  9838. 3 => "temperatureIntern",
  9839. 2 => "time",
  9840. 1 => "default",
  9841. 0 => "no_change"
  9842. );
  9843. while (($key, $val) = each(%displayContent)) {
  9844. $attr{$name}{displayContent} = $val if ($key == $displayContent);
  9845. }
  9846. my $temperatureScale = ($db[4] & 24) >> 3;
  9847. my %temperatureScale = (3 => "F",
  9848. 2 => "C",
  9849. 1 => "default",
  9850. 0 => "no_change"
  9851. );
  9852. while (($key, $val) = each(%temperatureScale)) {
  9853. $attr{$name}{temperatureScale} = $val if ($key == $temperatureScale);
  9854. }
  9855. $attr{$name}{daylightSavingTime} = $db[4] & 4 ? "not_supported" : "supported";
  9856. my $timeNotation = $db[4] & 3;
  9857. if ($timeNotation == 0) {
  9858. $attr{$name}{timeNotation} = "no_change";
  9859. } elsif ($timeNotation == 1) {
  9860. $attr{$name}{timeNotation} = "default";
  9861. } elsif ($timeNotation == 2) {
  9862. $attr{$name}{timeNotation} = 24;
  9863. } elsif ($timeNotation == 3) {
  9864. $attr{$name}{timeNotation} = 12;
  9865. }
  9866. EnOcean_CommandSave(undef, undef);
  9867. } elsif ($mid == 3) {
  9868. # room control setup
  9869. my $setpointComfort = "-";
  9870. $setpointComfort = sprintf "%.1f", $db[1] / 254 * 40 if ($db[0] & 1);
  9871. push @event, "3:setpointComfortTemp:$setpointComfort";
  9872. my $setpointEconomy = "-";
  9873. $setpointEconomy = sprintf "%.1f", $db[2] / 254 * 40 if ($db[0] & 2);
  9874. push @event, "3:setpointEconomyTemp:$setpointEconomy";
  9875. my $setpointPreComfort = "-";
  9876. $setpointPreComfort = sprintf "%.1f", $db[3] / 254 * 40 if ($db[0] & 4);
  9877. push @event, "3:setpointPreComfortTemp:$setpointPreComfort";
  9878. my $setpointBuildingProtection = "-";
  9879. $setpointBuildingProtection = sprintf "%.1f", $db[3] / 254 * 40 if ($db[0] & 8);
  9880. push @event, "3:setpointBuildingProtectionTemp:$setpointBuildingProtection";
  9881. } elsif ($mid == 4) {
  9882. # time program setup
  9883. my $timeProgram = "timeProgram" . $partCntr;
  9884. my $period = $db[0] >> 4;
  9885. my $periodVal = "";
  9886. my %period = (15 => "FrMo",
  9887. 14 => "FrSu",
  9888. 13 => "ThFr",
  9889. 12 => "WeFr",
  9890. 11 => "TuTh",
  9891. 10 => "MoWe",
  9892. 9 => "Su",
  9893. 8 => "Sa",
  9894. 7 => "Fr",
  9895. 6 => "Th",
  9896. 5 => "We",
  9897. 4 => "Tu",
  9898. 3 => "Mo",
  9899. 2 => "SaSu",
  9900. 1 => "MoFr",
  9901. 0 => "MoSu"
  9902. );
  9903. while (($key, $val) = each(%period)) {
  9904. $periodVal = $val if ($key == $period);
  9905. }
  9906. my $roomCtrlMode = ($db[0] & 12) >> 2;
  9907. if ($roomCtrlMode == 3) {
  9908. $roomCtrlMode = "buildingProtection";
  9909. } elsif ($roomCtrlMode == 2) {
  9910. $roomCtrlMode = "preComfort";
  9911. } elsif ($roomCtrlMode == 1) {
  9912. $roomCtrlMode = "economy";
  9913. } else{
  9914. $roomCtrlMode = "comfort";
  9915. }
  9916. my ($startHour, $startMinute, $endHour, $endMinute) = ($db[1], $db[2], $db[3], $db[4]);
  9917. $startHour = $startHour < 10 ? $startHour = "0" . $startHour : $startHour;
  9918. $startMinute = $startMinute < 10 ? $startMinute = "0" . $startMinute : $startMinute;
  9919. $endHour = $endHour < 10 ? $endHour = "0" . $endHour : $endHour;
  9920. $endMinute = $endMinute < 10 ? $endMinute = "0" . $endMinute : $endMinute;
  9921. #Log3 $name, 2, "EnOcean $name EnOcean_Parse write 2 MID $mid DATA $data to " . sprintf "%02X%02X%02X%02X%02X%02X", $db[5], $db[4], $db[3], $db[2], $db[1], $db[0];
  9922. #Log3 $name, 2, "EnOcean $name EnOcean_Parse write 3 MID $mid DATA $data to $timeProgram VAL: $periodVal $startHour:$startMinute $endHour:$endMinute $roomCtrlMode";
  9923. $attr{$name}{$timeProgram} = "$periodVal $startHour:$startMinute $endHour:$endMinute $roomCtrlMode";
  9924. #Log3 $name, 2, "EnOcean $name EnOcean_Parse write 4 MID $mid DATA $data to $timeProgram VAL: $attr{$name}{$timeProgram}";
  9925. EnOcean_CommandSave(undef, undef);
  9926. }
  9927. }
  9928. ($err, $response) = EnOcean_roomCtrlPanel_00Snd(undef, $hash, $packetType, $mid, $mcf, $irc, $fbc, $gmt);
  9929. } elsif ($mcf == 1) {
  9930. # message incomplete
  9931. if (!defined($hash->{helper}{$mid}{messagePart})) {
  9932. $hash->{helper}{$mid}{messagePart} = 1;
  9933. } elsif ($hash->{helper}{$mid}{messagePart} >= 4) {
  9934. # max 4 message parts stored
  9935. for (my $partCntr = 1; $partCntr < $hash->{helper}{$mid}{messagePart}; $partCntr ++) {
  9936. $hash->{helper}{$mid}{data}{$partCntr} = $hash->{helper}{$mid}{data}{$partCntr + 1};
  9937. }
  9938. } else {
  9939. $hash->{helper}{$mid}{messagePart} += 1;
  9940. }
  9941. $hash->{helper}{$mid}{data}{$hash->{helper}{$mid}{messagePart}} = $data;
  9942. #Log3 $name, 2, "EnOcean $name EnOcean_Parse store MID $mid DATA $data to messagePart $hash->{helper}{$mid}{messagePart}";
  9943. ($err, $response) = EnOcean_roomCtrlPanel_00Snd(undef, $hash, $packetType, $mid, $mcf, undef, undef, undef);
  9944. }
  9945. } elsif ($st eq "actuator.01") {
  9946. # Electronic switches and dimmers with Energy Measurement and Local Control
  9947. # (D2-01-00 - D2-01-12)
  9948. my $channel = (hex substr($data, 2, 2)) & 0x1F;
  9949. if ($channel == 31) {$channel = "Input";}
  9950. my $cmd = hex substr($data, 1, 1);
  9951. if ($cmd == 4) {
  9952. # actuator status response
  9953. my $overCurrentOff;
  9954. my $error;
  9955. my $localControl;
  9956. my $dim;
  9957. push @event, "3:powerFailure" . $channel . ":" . ($db[2] & 0x80 ? "enabled" : "disabled");
  9958. push @event, "3:powerFailureDetection" . $channel . ":" . ($db[2] & 0x40 ? "detected" : "not_detected");
  9959. if (($db[1] & 0x80) == 0) {
  9960. $overCurrentOff = "ready";
  9961. } else {
  9962. $overCurrentOff = "executed";
  9963. }
  9964. push @event, "3:overCurrentOff" . $channel . ":" . $overCurrentOff;
  9965. if ((($db[1] & 0x60) >> 5) == 1) {
  9966. $error = "warning";
  9967. } elsif ((($db[1] & 0x60) >> 5) == 2) {
  9968. $error = "failure";
  9969. } elsif ((($db[1] & 0x60) >> 5) == 3) {
  9970. $error = "not_supported";
  9971. } else {
  9972. $error = "ok";
  9973. }
  9974. push @event, "3:error" . $channel . ":" . $error;
  9975. if (($db[0] & 0x80) == 0) {
  9976. $localControl = "disabled";
  9977. } else {
  9978. $localControl = "enabled";
  9979. }
  9980. push @event, "3:localControl" . $channel . ":" . $localControl;
  9981. my $dimValue = $db[0] & 0x7F;
  9982. if ($dimValue == 0) {
  9983. push @event, "3:channel" . $channel . ":off";
  9984. push @event, "3:state:off";
  9985. } else {
  9986. push @event, "3:channel" . $channel . ":on";
  9987. push @event, "3:state:on";
  9988. }
  9989. if ($channel ne "Input") {
  9990. push @event, "3:dim:" . $dimValue;
  9991. push @event, "3:dim" . $channel . ":" . $dimValue;
  9992. } else {
  9993. push @event, "3:dim" . $channel . ":" . $dimValue;
  9994. }
  9995. } elsif ($cmd == 7) {
  9996. # actuator measurement response
  9997. my $unit = $db[4] >> 5;
  9998. if ($unit == 1) {
  9999. #$unit = "Wh";
  10000. $unit = "KWh";
  10001. push @event, "3:energyUnit" . $channel . ":" . $unit;
  10002. push @event, "3:energy" . $channel . ":" . sprintf("%.3f", (hex substr($data, 4, 8)) / 1000);
  10003. } elsif ($unit == 2) {
  10004. $unit = "KWh";
  10005. push @event, "3:energyUnit" . $channel . ":" . $unit;
  10006. push @event, "3:energy" . $channel . ":" . hex substr($data, 4, 8);
  10007. } elsif ($unit == 3) {
  10008. $unit = "W";
  10009. push @event, "3:powerUnit" . $channel . ":" . $unit;
  10010. push @event, "3:power" . $channel . ":" . hex substr($data, 4, 8);
  10011. } elsif ($unit == 4) {
  10012. $unit = "KW";
  10013. push @event, "3:powerUnit" . $channel . ":" . $unit;
  10014. push @event, "3:power" . $channel . ":" . hex substr($data, 4, 8);
  10015. } else {
  10016. $unit = "Ws";
  10017. push @event, "3:energyUnit" . $channel . ":" . $unit;
  10018. push @event, "3:energy" . $channel . ":" . hex substr($data, 4, 8);
  10019. }
  10020. } elsif ($cmd == 10) {
  10021. # pilot wire mode response
  10022. my $roomCtrlMode = $db[0] & 3;
  10023. my %roomCtrlMode = (
  10024. 0 => "off",
  10025. 1 => "comfort",
  10026. 2 => "economy",
  10027. 3 => "buildingProtection",
  10028. 4 => "comfort-1",
  10029. 5 => "comfort-2"
  10030. );
  10031. push @event, "3:roomCtrlMode" . $roomCtrlMode{$roomCtrlMode} if (exists $roomCtrlMode{$roomCtrlMode});
  10032. } elsif ($cmd == 13) {
  10033. # external interface settings
  10034. push @event, "3:autoOffTime" . $channel . ":" . sprintf("%0.1f", hex(substr($data, 4, 4)) * 0.1);
  10035. push @event, "3:delayOffTime" . $channel . ":" . sprintf("%0.1f", hex(substr($data, 8, 4)) * 0.1);
  10036. my $extSwitchMode = ($db[0] & 0xC0) >> 6;
  10037. my %extSwitchMode = (
  10038. 0 => "unavailable",
  10039. 1 => "switch",
  10040. 2 => "pushbutton",
  10041. 3 => "auto"
  10042. );
  10043. push @event, "3:extSwitchMode" . $extSwitchMode{$extSwitchMode} if (exists $extSwitchMode{$extSwitchMode});
  10044. push @event, "3:extSwitchType" . ($db[0] & 0x20 ? 'direction' : 'toggle');
  10045. } else {
  10046. # unknown response
  10047. }
  10048. } elsif ($st eq "switch.00" || $st eq "windowHandle.10") {
  10049. $db[0] &= 0x0F;
  10050. if ($db[0] == 1) {
  10051. push @event, "3:state:open_from_tilted";
  10052. } elsif ($db[0] == 2) {
  10053. push @event, "3:state:closed";
  10054. } elsif ($db[0] == 3) {
  10055. push @event, "3:state:open";
  10056. } elsif ($db[0] == 4) {
  10057. push @event, "3:state:tilted";
  10058. } elsif ($db[0] == 5) {
  10059. push @event, "3:state:AI,B0";
  10060. push @event, "3:channelA:AI";
  10061. push @event, "3:channelB:B0";
  10062. push @event, "3:energyBow:pressed";
  10063. } elsif ($db[0] == 6) {
  10064. CommandDeleteReading(undef, "$name channel.*");
  10065. push @event, "3:state:pressed34";
  10066. push @event, "3:energyBow:pressed";
  10067. } elsif ($db[0] == 7) {
  10068. push @event, "3:state:A0,B0";
  10069. push @event, "3:channelA:A0";
  10070. push @event, "3:channelB:B0";
  10071. push @event, "3:energyBow:pressed";
  10072. } elsif ($db[0] == 8) {
  10073. if (AttrVal($name, "sensorMode", "switch") eq "pushbutton") {
  10074. push @event, "3:state:pressed";
  10075. }
  10076. push @event, "3:energyBow:pressed";
  10077. } elsif ($db[0] == 9) {
  10078. push @event, "3:state:AI,BI";
  10079. push @event, "3:channelA:AI";
  10080. push @event, "3:channelB:BI";
  10081. push @event, "3:energyBow:pressed";
  10082. } elsif ($db[0] == 10) {
  10083. push @event, "3:state:A0,BI";
  10084. push @event, "3:channelA:A0";
  10085. push @event, "3:channelB:BI";
  10086. push @event, "3:energyBow:pressed";
  10087. } elsif ($db[0] == 11) {
  10088. push @event, "3:state:BI";
  10089. push @event, "3:channelB:BI";
  10090. push @event, "3:energyBow:pressed";
  10091. } elsif ($db[0] == 12) {
  10092. push @event, "3:state:B0";
  10093. push @event, "3:channelB:B0";
  10094. push @event, "3:energyBow:pressed";
  10095. } elsif ($db[0] == 13) {
  10096. push @event, "3:state:AI";
  10097. push @event, "3:channelA:AI";
  10098. push @event, "3:energyBow:pressed";
  10099. } elsif ($db[0] == 14) {
  10100. push @event, "3:state:A0";
  10101. push @event, "3:channelA:A0";
  10102. push @event, "3:energyBow:pressed";
  10103. } elsif ($db[0] == 15) {
  10104. if (AttrVal($name, "sensorMode", "switch") eq "pushbutton") {
  10105. push @event, "3:state:released";
  10106. }
  10107. push @event, "3:energyBow:released";
  10108. }
  10109. } elsif ($st eq "switch.0A") {
  10110. # Push Button - Single Button EEP D2-03-0A
  10111. if (!exists($hash->{helper}{batteryPrecent}) || $hash->{helper}{batteryPrecent} != $db[1]) {
  10112. push @event, "3:batteryPrecent:$db[1]";
  10113. $hash->{helper}{batteryPrecent} = $db[1];
  10114. }
  10115. if ($db[0] == 1) {
  10116. push @event, "3:buttonS:on";
  10117. RemoveInternalTimer($hash->{helper}{timer}{buttonS}) if (exists $hash->{helper}{timer}{buttonS});
  10118. @{$hash->{helper}{timer}{buttonS}} = ($hash, 'buttonS', 'off', 1, 5);
  10119. InternalTimer(gettimeofday() + 0.5, 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{buttonS}, 0);
  10120. } elsif ($db[0] == 2) {
  10121. push @event, "3:buttonD:on";
  10122. RemoveInternalTimer($hash->{helper}{timer}{buttonD}) if (exists $hash->{helper}{timer}{buttonD});
  10123. @{$hash->{helper}{timer}{buttonD}} = ($hash, 'buttonD', 'off', 1, 5);
  10124. InternalTimer(gettimeofday() + 0.5, 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{buttonD}, 0);
  10125. } elsif ($db[0] == 3) {
  10126. push @event, "3:buttonL:on";
  10127. push @event, "3:state:on";
  10128. } elsif ($db[0] == 4) {
  10129. push @event, "3:buttonL:off";
  10130. push @event, "3:state:off";
  10131. }
  10132. } elsif ($st =~ m/^blindsCtrl\.0[01]$/) {
  10133. # EEP D2-05-0x
  10134. my $channel = (($db[0] & 0xF0) >> 4) + 1;
  10135. my $cmd = $db[0] & 0x0F;
  10136. if ($cmd == 4) {
  10137. # actuator status response
  10138. if ($db[3] == 0) {
  10139. push @event, "3:state:open";
  10140. push @event, "3:endPosition" . sprintf('%02d', $channel) . ":open";
  10141. push @event, "3:position" . sprintf('%02d', $channel) . ":" . $db[3];
  10142. push @event, "3:position:" . $db[3];
  10143. } elsif ($db[3] == 100) {
  10144. push @event, "3:state:closed";
  10145. push @event, "3:endPosition" . sprintf('%02d', $channel) . ":closed";
  10146. push @event, "3:position" . sprintf('%02d', $channel) . ":" . $db[3];
  10147. push @event, "3:position:" . $db[3];
  10148. } elsif ($db[3] == 127) {
  10149. push @event, "3:state:unknown";
  10150. push @event, "3:endPosition" . sprintf('%02d', $channel) . ":unknown";
  10151. push @event, "3:position" . sprintf('%02d', $channel) . ":unknown";
  10152. push @event, "3:position:" . $db[3];
  10153. } else {
  10154. push @event, "3:state:" . $db[3];
  10155. push @event, "3:endPosition" . sprintf('%02d', $channel) . ":not_reached";
  10156. push @event, "3:position" . sprintf('%02d', $channel) . ":" . $db[3];
  10157. push @event, "3:position:" . $db[3];
  10158. }
  10159. if ($db[2] == 127) {
  10160. push @event, "3:anglePos" . sprintf('%02d', $channel) . ":unknown";
  10161. } else {
  10162. push @event, "3:anglePos" . sprintf('%02d', $channel) . ":" . $db[2];
  10163. push @event, "3:anglePos:" . $db[2];
  10164. }
  10165. if ($db[1] == 0) {
  10166. push @event, "3:block" . sprintf('%02d', $channel) . ":unlock";
  10167. } elsif ($db[1] == 1) {
  10168. push @event, "3:block" . sprintf('%02d', $channel) . ":lock";
  10169. } elsif ($db[1] == 2) {
  10170. push @event, "3:block" . sprintf('%02d', $channel) . ":alarm";
  10171. } else {
  10172. push @event, "3:block" . sprintf('%02d', $channel) . ":reserved";
  10173. }
  10174. } else {
  10175. # unknown response
  10176. }
  10177. } elsif ($st eq "multisensor.01") {
  10178. #####
  10179. # Multisensor Windows Handle
  10180. # (D2-06-01)
  10181. # message type
  10182. my $msgType = hex(substr($data, 0, 2));
  10183. my $blinkInterval = 0;
  10184. my $updateInterval = 0;
  10185. my $waitingCmds = ReadingsVal($name, "waitingCmds", undef);
  10186. if ($msgType == 0) {
  10187. # sensor values
  10188. my %onOffTrigger = (
  10189. 0 => "off",
  10190. 1 => "on",
  10191. 14 => "invalid",
  10192. 15 => "not_supported",
  10193. );
  10194. my $onOffTrigger = $db[8] >> 4;
  10195. if (exists $onOffTrigger{$onOffTrigger}) {
  10196. push @event, "3:burglaryAlarm:$onOffTrigger{$onOffTrigger}";
  10197. } else {
  10198. push @event, "3:burglaryAlarm:unknown";
  10199. }
  10200. $onOffTrigger = $db[8] & 15;
  10201. if (exists $onOffTrigger{$onOffTrigger}) {
  10202. push @event, "3:protectionAlarm:$onOffTrigger{$onOffTrigger}";
  10203. } else {
  10204. push @event, "3:protectionAlarm:unknown";
  10205. }
  10206. my %handlePosition = (
  10207. 0 => "unknown",
  10208. 1 => "up",
  10209. 2 => "down",
  10210. 3 => "left",
  10211. 4 => "right",
  10212. 14 => "invalid",
  10213. 15 => "not_supported"
  10214. );
  10215. my %handleRPS = (
  10216. 1 => 'D0',
  10217. 2 => 'F0',
  10218. 3 => 'E0',
  10219. 4 => 'E0'
  10220. );
  10221. my $handlePosition = $db[7] >> 4;
  10222. if (exists $handlePosition{$handlePosition}) {
  10223. push @event, "3:handle:$handlePosition{$handlePosition}";
  10224. } else {
  10225. push @event, "3:handle:unknown";
  10226. }
  10227. # forward handle position (RPS telegam)
  10228. if (exists($handleRPS{$handlePosition}) && defined(AttrVal($name, "subDefH", undef))) {
  10229. EnOcean_SndRadio(undef, $hash, $packetType, "F6", $handleRPS{$handlePosition}, AttrVal($name, "subDefH", "00000000"), '20', 'FFFFFFFF');
  10230. }
  10231. my %windowState = (
  10232. 0 => "undef",
  10233. 1 => "not_tilted",
  10234. 2 => "tilted",
  10235. 14 => "invalid",
  10236. 15 => "not_supported"
  10237. );
  10238. my %window1BS = (
  10239. 1 => '09',
  10240. 2 => '08'
  10241. );
  10242. my $windowState = $db[7] & 15;
  10243. if (exists $windowState{$windowState}) {
  10244. push @event, "3:window:$windowState{$windowState}";
  10245. } else {
  10246. push @event, "3:window:unknown";
  10247. }
  10248. # forward window state (1BS telegam)
  10249. if (exists($window1BS{$windowState}) && defined(AttrVal($name, "subDefW", undef))) {
  10250. EnOcean_SndRadio(undef, $hash, $packetType, "D5", $window1BS{$windowState}, AttrVal($name, "subDefW", "00000000"), '00', 'FFFFFFFF');
  10251. }
  10252. my %button = (
  10253. 0 => "no_change",
  10254. 1 => "pressed",
  10255. 2 => "released",
  10256. 14 => "invalid",
  10257. 15 => "not_supported"
  10258. );
  10259. my $button = $db[6] >> 4;
  10260. if (exists $button{$button}) {
  10261. if ($button == 0 && ReadingsVal($name, "buttonRight", 'unknown') eq 'unknown') {
  10262. push @event, "3:buttonRight:unknown";
  10263. } elsif ($button == 0) {
  10264. } else {
  10265. push @event, "3:buttonRight:$button{$button}";
  10266. }
  10267. } else {
  10268. push @event, "3:buttonRight:unknown";
  10269. }
  10270. $button = $db[6] & 15;
  10271. if (exists $button{$button}) {
  10272. if ($button == 0 && ReadingsVal($name, "buttonLeft", 'unknown') eq 'unknown') {
  10273. push @event, "3:buttonLeft:unknown";
  10274. } elsif ($button == 0) {
  10275. } else {
  10276. push @event, "3:buttonLeft:$button{$button}";
  10277. }
  10278. } else {
  10279. push @event, "3:buttonLeft:unknown";
  10280. }
  10281. my $motion = $db[5] >> 4;
  10282. if (exists $onOffTrigger{$motion}) {
  10283. push @event, "3:motion:$onOffTrigger{$motion}";
  10284. } else {
  10285. push @event, "3:motion:unknown";
  10286. }
  10287. my %vacation = (
  10288. 0 => "no_change",
  10289. 1 => "absent",
  10290. 2 => "present",
  10291. 14 => "invalid",
  10292. 15 => "not_supported"
  10293. );
  10294. my $vacation = $db[5] & 15;
  10295. if (exists $vacation{$vacation}) {
  10296. if ($vacation == 0 && ReadingsVal($name, "presence", 'unknown') eq 'unknown') {
  10297. push @event, "3:presence:unknown";
  10298. } elsif ($vacation == 0) {
  10299. } else {
  10300. push @event, "3:presence:$vacation{$vacation}";
  10301. }
  10302. } else {
  10303. push @event, "3:presence:unknown";
  10304. }
  10305. my $temperature;
  10306. if ($db[4] <= 250) {
  10307. $temperature = sprintf "%0.1f", $db[4] * 80 / 250 - 20;
  10308. push @event, "3:temperature:$temperature";
  10309. } elsif ($db[4] <= 254) {
  10310. $temperature = '-';
  10311. push @event, "3:temperature:invalid";
  10312. } elsif ($db[4] <= 255) {
  10313. $temperature = '-';
  10314. push @event, "3:temperature:not_supported";
  10315. } else {
  10316. $temperature = '-';
  10317. push @event, "3:temperature:unknown";
  10318. }
  10319. my $humidity;
  10320. if ($db[3] <= 200) {
  10321. $humidity = $db[3] / 2;
  10322. push @event, "3:humidity:$humidity";
  10323. } elsif ($db[3] <= 254) {
  10324. $humidity = '-';
  10325. push @event, "3:humidity:invalid";
  10326. } elsif ($db[3] <= 255) {
  10327. $humidity = '-';
  10328. push @event, "3:humidity:not_supported";
  10329. } else {
  10330. $humidity = '-';
  10331. push @event, "3:humidity:unknown";
  10332. }
  10333. my $brightness = hex(substr($data, 14, 4));
  10334. if ($brightness <= 60000) {
  10335. push @event, "3:brightness:$brightness";
  10336. } elsif ($brightness == 60001) {
  10337. $brightness = '-';
  10338. push @event, "3:brightness:over_range";
  10339. } elsif ($brightness == 65534) {
  10340. $brightness = '-';
  10341. push @event, "3:brightness:invalid";
  10342. } elsif ($brightness == 65535) {
  10343. $brightness = '-';
  10344. push @event, "3:brightness:not_supported";
  10345. } else {
  10346. $brightness = '-';
  10347. push @event, "3:brightness:unknown";
  10348. }
  10349. my $energyStorage = ($db[0] >> 3) * 5;
  10350. my $battery;
  10351. if ($energyStorage <= 5) {
  10352. $battery = 'low';
  10353. push @event, "3:battery:low";
  10354. push @event, "3:energyStorage:$energyStorage";
  10355. } elsif ($energyStorage <= 100) {
  10356. $battery = 'ok';
  10357. push @event, "3:battery:ok";
  10358. push @event, "3:energyStorage:$energyStorage";
  10359. } else {
  10360. $battery = '-';
  10361. push @event, "3:battery:-";
  10362. $energyStorage = '-';
  10363. push @event, "3:energyStorage:unknown";
  10364. }
  10365. push @event, "3:state:T: $temperature H: $humidity E: $brightness M: $onOffTrigger{$motion}";
  10366. } elsif ($msgType == 0x10) {
  10367. # configuration report
  10368. if (defined $waitingCmds) {
  10369. $waitingCmds = $db[3] & 0x80 ? $waitingCmds & 0xDF | 32 : $waitingCmds & 0xDF;
  10370. }
  10371. push @event, "3:presence:" . ($db[3] & 0x80 ? 'absent' : 'present');
  10372. push @event, "3:handleClosedClick:" . ($db[3] & 0x40 ? 'enabled' : 'disabled');
  10373. push @event, "3:batteryLowClick:" . ($db[3] & 0x20 ? 'enabled' : 'disabled');
  10374. $updateInterval = hex(substr($data, 4, 4));
  10375. if ($updateInterval <= 4) {
  10376. $updateInterval = '-';
  10377. push @event, "3:updateInterval:unknown";
  10378. } else {
  10379. push @event, "3:updateInterval:$updateInterval";
  10380. }
  10381. $blinkInterval = $db[0];
  10382. if ($blinkInterval <= 2) {
  10383. $blinkInterval = '-';
  10384. push @event, "3:blinkInterval:unknown";
  10385. } else {
  10386. push @event, "3:blinkInterval:$blinkInterval";
  10387. }
  10388. } elsif ($msgType == 0x20) {
  10389. # log data 01
  10390. push @event, "3:powerOns:" . substr($data, 2, 8);
  10391. push @event, "3:alarms:" . substr($data, 10, 8);
  10392. } elsif ($msgType == 0x21) {
  10393. # log data 02
  10394. push @event, "3:handleMoveClosed:" . substr($data, 2, 8);
  10395. push @event, "3:handleMoveOpend:" . substr($data, 10, 8);
  10396. push @event, "3:handleMoveTilted:" . substr($data, 18, 8);
  10397. } elsif ($msgType == 0x22) {
  10398. # log data 03
  10399. push @event, "3:windowTilts:" . substr($data, 2, 8);
  10400. } elsif ($msgType == 0x23) {
  10401. # log data 04
  10402. push @event, "3:buttonRightPresses:" . substr($data, 2, 8);
  10403. push @event, "3:buttonLeftPresses:" . substr($data, 10, 8);
  10404. } else {
  10405. }
  10406. if (defined $waitingCmds) {
  10407. $updateInterval = 0;
  10408. $blinkInterval = 0;
  10409. if ($waitingCmds & 1) {
  10410. $waitingCmds &= 0xFE;
  10411. $updateInterval = ReadingsVal($name, "updateIntervalSet", 0);
  10412. $updateInterval = $updateInterval =~ m/^\d+$/ ? $updateInterval : 0;
  10413. $blinkInterval = ReadingsVal($name, "blinkIntervalSet", 0);
  10414. $blinkInterval = $blinkInterval =~ m/^\d+$/ ? $blinkInterval : 0;
  10415. CommandDeleteReading(undef, "$name .*Set");
  10416. }
  10417. CommandDeleteReading(undef, "$name waitingCmds");
  10418. $data = sprintf "80%02X%04X%02X", $waitingCmds, $updateInterval, $blinkInterval;
  10419. EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  10420. #EnOcean_multisensor_01Snd($crtl, $hash, $packetType);
  10421. }
  10422. } elsif ($st eq "roomCtrlPanel.01") {
  10423. # Room Control Panel
  10424. # (D2-11-01 - D2-11-08)
  10425. my $msgType = hex(substr($data, 1, 1));
  10426. my $setpointType = ReadingsVal($name, "setpointType", 'setpointShift');
  10427. my $waitingCmds = ReadingsVal($name, "waitingCmds", 0);
  10428. if (($waitingCmds & 0x80) == 0) {
  10429. $setpointType = hex(substr($data, 0, 1)) & 8 ? 'setpointTemp' : 'setpointShift';
  10430. push @event, "3:setpointType:$setpointType";
  10431. }
  10432. if ($msgType == 2) {
  10433. my $trigger = ($db[5] & 0x60) >> 5;
  10434. my %trigger = (0 => 'heartbeat', 1 => 'sensor', 2 => 'input');
  10435. push @event, "3:trigger:" . $trigger{$trigger};
  10436. my $temperature = sprintf "%0.1f", $db[4] / 255 * 40;
  10437. push @event, "3:temperature:$temperature";
  10438. my $humidity = sprintf "%d", $db[3] / 2.5;
  10439. push @event, "3:humidity:$humidity";
  10440. my $setpointShiftMax = ($db[0] & 0xF0) >> 4;
  10441. push @event, "3:setpointShiftMax:$setpointShiftMax";
  10442. my $setpointShift = int(0.5 + $db[2] * $setpointShiftMax / 128 * 10) / 10 - $setpointShiftMax;
  10443. push @event, "3:setpointShift:" . sprintf "%0.1f", $setpointShift;
  10444. push @event, "3:setpointBase:$db[1]";
  10445. push @event, "3:setpointTemp:" . sprintf "%0.1f", ($db[1] + $setpointShift);
  10446. my %fanSpeed = (0 => 'auto', 1 => 'off', 2 => 1, 3 => 2, 4 => 3);
  10447. my $fanSpeed = ($db[0] & 0xE) >> 1;
  10448. push @event, "3:fanSpeed:" . $fanSpeed{$fanSpeed};
  10449. push @event, "3:occupancy:" . ($db[0] & 1 ? 'occupied' : 'unoccupied');
  10450. push @event, "3:state:T: $temperature H: $humidity SPT: " . ($db[1] + $setpointShift) . " F: " . $fanSpeed{$fanSpeed};
  10451. }
  10452. CommandDeleteReading(undef, "$name waitingCmds");
  10453. } elsif ($st eq "multiFuncSensor.30") {
  10454. # Sensor for Smoke, Air quality, Hygrothermal comfort, Temperature and Humidity
  10455. # (D2-14-30)
  10456. my @airQuality = ('optimal', 'air_dry', 'humidity_high', 'temperature_humidity_high', '', '', 'error');
  10457. my @comfort = ('good', 'medium', 'bad', 'error');
  10458. my @energyStorage = ('ok', 'medium', 'low', 'critical');
  10459. my $alarm = ($db[5] & 0x80) == 0 ? 'off' : 'smoke-alarm';
  10460. my $battery = $energyStorage[($db[4] & 6) >> 1];
  10461. if (!exists($hash->{helper}{lastAlarm}) || $hash->{helper}{lastAlarm} ne $alarm || ReadingsVal($name, 'alarm', '') eq 'dead_sensor') {
  10462. push @event, "3:alarm:" . $alarm;
  10463. }
  10464. if (!exists($hash->{helper}{lastBattery}) || $hash->{helper}{lastBattery} ne $battery) {
  10465. push @event, "3:battery:" . $battery;
  10466. }
  10467. push @event, "3:sensorFaultMode:" . (($db[5] & 0x40) == 0 ? 'off' : 'on');
  10468. push @event, "3:smokeAlarmMaintenance:" . (($db[5] & 0x20) == 0 ? 'ok' : 'not_done');
  10469. push @event, "3:smokeAlarmHumidity:" . (($db[5] & 0x10) == 0 ? 'ok' : 'not_ok');
  10470. push @event, "3:smokeAlarmTemperature:" . (($db[5] & 8) == 0 ? 'ok' : 'not_ok');
  10471. push @event, "3:maintenanceLast:" . (($db[5] & 7) << 5 | $db[4] >> 3);
  10472. push @event, "3:endOffLife:" . (($db[4] & 1) << 7 | $db[3] >> 1);
  10473. push @event, "3:temperature:" . sprintf "%0.1f", (($db[3] & 1) << 7 | $db[2] >> 1) / 5;
  10474. push @event, "3:humidity:" . sprintf "%0.1f", (($db[2] & 1) << 7 | $db[1] >> 1) / 2;
  10475. push @event, "3:hygrothermalComfort:" . $comfort[(($db[1] & 1) << 1 | $db[0] >> 7)];
  10476. push @event, "3:airQuality:" . $airQuality[($db[0] & 0x70) >> 4];
  10477. push @event, "3:state:" . $alarm;
  10478. $hash->{helper}{lastAlarm} = $alarm;
  10479. $hash->{helper}{lastBattery} = $battery;
  10480. RemoveInternalTimer($hash->{helper}{timer}{alarm}) if (exists $hash->{helper}{timer}{alarm});
  10481. @{$hash->{helper}{timer}{alarm}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
  10482. InternalTimer(gettimeofday() + 1440, 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0);
  10483. } elsif ($st eq "fanCtrl.00") {
  10484. # Fan Control
  10485. # (D2-20-00 - D2-20-02)
  10486. if ($db[3] & 1) {
  10487. # fan status message
  10488. my $fanSpeed = $db[0];
  10489. my $humidity = $db[1];
  10490. my $roomSize = $db[2] & 15;
  10491. my $roomSizeRef = ($db[2] & 0x30) >> 4;
  10492. my $humidityCtrl = ($db[2] & 0xC0) >> 6;
  10493. my $serviceInfo = ($db[3] & 14) >> 1;
  10494. my $opMode = ($db[3] & 0xF0) >> 4;
  10495. my %roomSizeTbl = (
  10496. 0 => 25,
  10497. 1 => 50,
  10498. 2 => 75,
  10499. 3 => 100,
  10500. 4 => 125,
  10501. 5 => 150,
  10502. 6 => 175,
  10503. 7 => 200,
  10504. 8 => 225,
  10505. 9 => 250,
  10506. 10 => 275,
  10507. 11 => 300,
  10508. 12 => 325,
  10509. 13 => 350,
  10510. 14 => "max",
  10511. 15 => "no_change"
  10512. );
  10513. if ($opMode == 0) {
  10514. push @event, "3:state:off";
  10515. } elsif ($opMode == 1) {
  10516. push @event, "3:state:on";
  10517. } elsif ($opMode == 15) {
  10518. push @event, "3:state:not_supported";
  10519. }
  10520. if ($serviceInfo == 0) {
  10521. push @event, "3:error:ok";
  10522. } elsif ($serviceInfo == 1) {
  10523. push @event, "3:error:air_filter";
  10524. } elsif ($serviceInfo == 2) {
  10525. push @event, "3:error:hardware";
  10526. } elsif ($serviceInfo == 7) {
  10527. push @event, "3:error:not_supported";
  10528. }
  10529. if ($humidityCtrl == 0) {
  10530. push @event, "3:humidityCtrl:disabled";
  10531. } elsif ($humidityCtrl == 1) {
  10532. push @event, "3:humidityCtrl:enabled";
  10533. } elsif ($humidityCtrl == 3) {
  10534. push @event, "3:humidityCtrl:not_supported";
  10535. }
  10536. if ($roomSizeRef == 0) {
  10537. push @event, "3:roomSizeRef:used";
  10538. } elsif ($roomSizeRef == 1) {
  10539. push @event, "3:roomSizeRef:not_used";
  10540. } elsif ($roomSizeRef == 3) {
  10541. push @event, "3:roomSizeRef:not_supported";
  10542. }
  10543. if ($roomSize < 15) {
  10544. push @event, "3:roomSize:" . $roomSizeTbl{$roomSize};
  10545. }
  10546. if ($humidity >= 0 && $humidity <= 100) {
  10547. push @event, "3:humidity:$humidity";
  10548. } elsif ($humidity == 255) {
  10549. push @event, "3:humidity:not_supported";
  10550. }
  10551. if ($fanSpeed >= 0 && $fanSpeed <= 100) {
  10552. push @event, "3:fanSpeed:$fanSpeed";
  10553. } elsif ($fanSpeed == 255) {
  10554. push @event, "3:fanSpeed:not_supported";
  10555. }
  10556. }
  10557. } elsif ($st eq "currentClamp.00") {
  10558. # AC current clamp (EEP D2-32-00)
  10559. if ($db[2] & 0x80) {
  10560. # power fail
  10561. push @event, "3:current1:0";
  10562. push @event, "3:state:I1: 0";
  10563. } else {
  10564. my $current1 = hex(substr($data, 2, 3));
  10565. if ($db[2] & 0x40) {
  10566. # divisor = 1/10
  10567. $current1 = $current1 / 10;
  10568. push @event, "3:current1:" . sprintf "%0.1f", $current1;
  10569. push @event, "3:state:I1: " . sprintf "%0.1f", $current1;
  10570. } else {
  10571. push @event, "3:current1:" . $current1;
  10572. push @event, "3:state:I1: " . $current1;
  10573. }
  10574. }
  10575. } elsif ($st eq "currentClamp.01") {
  10576. # AC current clamp (EEP D2-32-01)
  10577. if ($db[3] & 0x80) {
  10578. # power fail
  10579. push @event, "3:current1:0";
  10580. push @event, "3:current2:0";
  10581. push @event, "3:state:I1: 0 I2: 0";
  10582. } else {
  10583. my $current1 = hex(substr($data, 2, 3));
  10584. my $current2 = hex(substr($data, 5, 3));
  10585. if ($db[3] & 0x40) {
  10586. # divisor = 1/10
  10587. $current1 = $current1 / 10;
  10588. $current2 = $current2 / 10;
  10589. push @event, "3:current1:" . sprintf "%0.1f", $current1;
  10590. push @event, "3:current2:" . sprintf "%0.1f", $current2;
  10591. push @event, "3:state:I1: " . sprintf("%0.1f", $current1) . " I2: " . sprintf("%0.1f", $current2);
  10592. } else {
  10593. push @event, "3:current1:" . $current1;
  10594. push @event, "3:current2:" . $current2;
  10595. push @event, "3:state:I1: $current1 I2: $current2";
  10596. }
  10597. }
  10598. } elsif ($st eq "currentClamp.02") {
  10599. # AC current clamp (EEP D2-32-02)
  10600. if ($db[5] & 0x80) {
  10601. # power fail
  10602. push @event, "3:current1:0";
  10603. push @event, "3:current2:0";
  10604. push @event, "3:current3:0";
  10605. push @event, "3:state:I1: 0 I2: 0 I3: 0";
  10606. } else {
  10607. my $current1 = hex(substr($data, 2, 3));
  10608. my $current2 = hex(substr($data, 5, 3));
  10609. my $current3 = hex(substr($data, 8, 3));
  10610. if ($db[5] & 0x40) {
  10611. # divisor = 1/10
  10612. $current1 = $current1 / 10;
  10613. $current2 = $current2 / 10;
  10614. $current3 = $current3 / 10;
  10615. push @event, "3:current1:" . sprintf "%0.1f", $current1;
  10616. push @event, "3:current2:" . sprintf "%0.1f", $current2;
  10617. push @event, "3:current3:" . sprintf "%0.1f", $current3;
  10618. push @event, "3:state:I1: " . sprintf("%0.1f", $current1) . " I2: " . sprintf("%0.1f", $current2) . " I3: " . sprintf("%0.1f", $current3);
  10619. } else {
  10620. push @event, "3:current1:" . $current1;
  10621. push @event, "3:current2:" . $current2;
  10622. push @event, "3:current3:" . $current3;
  10623. push @event, "3:state:I1: $current1 I2: $current2 I3: $current3";
  10624. }
  10625. }
  10626. } elsif ($st eq "ledCtrlState.00") {
  10627. # LED Controller Status (EEP D2-40-00)
  10628. if ($db[1] & 0x80) {
  10629. # powerSwitch
  10630. push @event, "3:powerSwitch:on";
  10631. push @event, "3:state:on";
  10632. } else {
  10633. push @event, "3:powerSwitch:off";
  10634. push @event, "3:state:off";
  10635. }
  10636. if ($db[1] & 0x40) {
  10637. # Demand Response Mode
  10638. push @event, "3:demandResp:on";
  10639. } else {
  10640. push @event, "3:demandResp:off";
  10641. }
  10642. if ($db[1] & 0x20) {
  10643. # Daylight Harvesting
  10644. push @event, "3:daylightHarvesting:on";
  10645. } else {
  10646. push @event, "3:daylightHarvesting:off";
  10647. }
  10648. my $occupancy = ($db[1] & 0x18) >> 3;
  10649. if ($occupancy == 0) {
  10650. push @event, "3:occupany:unoccupied";
  10651. } elsif ($occupancy == 1) {
  10652. push @event, "3:occupany:occupied";
  10653. } else {
  10654. push @event, "3:occupany:unknown";
  10655. }
  10656. push @event, "3:telegramType:" . ($db[1] & 4 ? "event" : "heartbeat");
  10657. if ($db[0] >= 0 && $db[0] <= 200) {
  10658. push @event, "3:dim:" . sprintf "%0.1f", $db[0] / 2;
  10659. }
  10660. } elsif ($st eq "ledCtrlState.01") {
  10661. # LED Controller Status (EEP D2-40-01)
  10662. if ($db[3] & 0x80) {
  10663. # powerSwitch
  10664. push @event, "3:powerSwitch:on";
  10665. push @event, "3:state:on";
  10666. } else {
  10667. push @event, "3:powerSwitch:off";
  10668. push @event, "3:state:off";
  10669. }
  10670. if ($db[3] & 0x40) {
  10671. # Demand Response Mode
  10672. push @event, "3:demandResp:on";
  10673. } else {
  10674. push @event, "3:demandResp:off";
  10675. }
  10676. if ($db[3] & 0x20) {
  10677. # Daylight Harvesting
  10678. push @event, "3:daylightHarvesting:on";
  10679. } else {
  10680. push @event, "3:daylightHarvesting:off";
  10681. }
  10682. my $occupancy = ($db[3] & 0x18) >> 3;
  10683. if ($occupancy == 0) {
  10684. push @event, "3:occupany:unoccupied";
  10685. } elsif ($occupancy == 1) {
  10686. push @event, "3:occupany:occupied";
  10687. } else {
  10688. push @event, "3:occupany:unknown";
  10689. }
  10690. push @event, "3:telegramType:" . ($db[3] & 4 ? "event" : "heartbeat");
  10691. my ($red, $green, $blue) = ('00', '00', '00');
  10692. if ($db[2] >= 0 && $db[2] <= 200) {
  10693. push @event, "3:red:" . sprintf "%0.1f", $db[2] / 2;
  10694. $red = sprintf "%02X", abs($db[2] / 200 * 255);
  10695. }
  10696. if ($db[1] >= 0 && $db[1] <= 200) {
  10697. push @event, "3:green:" . sprintf "%0.1f", $db[1] / 2;
  10698. $green = sprintf "%02X", abs($db[1] / 200 * 255);
  10699. }
  10700. if ($db[0] >= 0 && $db[0] <= 200) {
  10701. push @event, "3:blue:" . sprintf "%0.1f", $db[0] / 2;
  10702. $blue = sprintf "%02X", abs($db[0] / 200 * 255);
  10703. }
  10704. push @event, "3:rgb:" . $red . $green . $blue;
  10705. } elsif ($st eq "heatRecovery.00") {
  10706. # heat recovery ventilation
  10707. # (D2-50-00)
  10708. my $msgType = hex(substr($data, 0, 1)) >> 1;
  10709. if ($msgType == 2) {
  10710. my $ventilation = 'unknown';
  10711. my %ventilation = (
  10712. 0 => "off",
  10713. 1 => 1,
  10714. 2 => 2,
  10715. 3 => 3,
  10716. 4 => 4,
  10717. 11 => "auto",
  10718. 12 => "demand",
  10719. 13 => "supplyAir",
  10720. 14 => "exhaustAir"
  10721. );
  10722. $ventilation = $db[13] & 15;
  10723. $ventilation = $ventilation{$ventilation} if (exists $ventilation{$ventilation});
  10724. push @event, "3:ventilation:$ventilation";
  10725. push @event, "3:fireplaceSafetyMode:" . ($db[12] & 8 ? 'enabled' : 'disabled');
  10726. push @event, "3:heatExchangerBypass:" . ($db[12] & 4 ? 'opened' : 'closed');
  10727. push @event, "3:supplyAirFlap:" . ($db[12] & 2 ? 'opened' : 'closed');
  10728. push @event, "3:exhaustAirFlap:" . ($db[12] & 1 ? 'opened' : 'closed');
  10729. push @event, "3:defrost:" . ($db[11] & 0x80 ? 'on' : 'off');
  10730. push @event, "3:coolingProtection:" . ($db[11] & 0x40 ? 'on' : 'off');
  10731. push @event, "3:outdoorAirHeater:" . ($db[11] & 0x20 ? 'on' : 'off');
  10732. push @event, "3:supplyAirHeater:" . ($db[11] & 0x10 ? 'on' : 'off');
  10733. push @event, "3:drainHeater:" . ($db[11] & 8 ? 'on' : 'off');
  10734. push @event, "3:timerMode:" . ($db[11] & 4 ? 'on' : 'off');
  10735. push @event, "3:filterMaintenance:" . ($db[11] & 2 ? 'required' : 'not_required');
  10736. push @event, "3:weeklyTimer:" . ($db[11] & 1 ? 'on' : 'off');
  10737. push @event, "3:roomTempCtrl:" . ($db[10] & 0x80 ? 'on' : 'off');
  10738. my $airQuatity = $db[10] & 0x7F;
  10739. if ($airQuatity <= 100) {
  10740. push @event, "3:airQuality1:$airQuatity";
  10741. } else {
  10742. CommandDeleteReading(undef, "$name airQuality1");
  10743. }
  10744. push @event, "3:deviceMode:" . ($db[9] & 0x80 ? 'slave' : 'master');
  10745. $airQuatity = $db[9] & 0x7F;
  10746. if ($airQuatity <= 100) {
  10747. push @event, "3:airQuality2:$airQuatity";
  10748. } else {
  10749. CommandDeleteReading(undef, "$name airQuality2");
  10750. }
  10751. my $outdoorTemp = ($db[8] & 0xFE) >> 1;
  10752. #$outdoorTemp -= $outdoorTemp if ($outdoorTemp & 0x40);
  10753. $outdoorTemp -= 64;
  10754. push @event, "3:outdoorTemp:$outdoorTemp";
  10755. my $supplyTemp = ($db[8] & 1) << 6 | ($db[7] & 0xFC) >> 2;
  10756. #$supplyTemp -= $supplyTemp if ($supplyTemp & 0x40);
  10757. $supplyTemp -= 64;
  10758. push @event, "3:supplyTemp:$supplyTemp";
  10759. my $roomTemp = ($db[7] & 3) << 5 | ($db[6] & 0xF8) >> 3;
  10760. #$roomTemp -= $roomTemp if ($roomTemp & 0x40);
  10761. $roomTemp -= 64;
  10762. push @event, "3:roomTemp:$roomTemp";
  10763. my $exhaustTemp = ($db[6] & 7) << 4 | ($db[5] & 0xF0) >> 4;
  10764. #$exhaustTemp -= $exhaustTemp if ($exhaustTemp & 0x40);
  10765. $exhaustTemp -= 64;
  10766. push @event, "3:exhaustTemp:$exhaustTemp";
  10767. push @event, "3:supplyAirFlow:". (($db[5] & 0x0F) << 2 | ($db[4] & 0xFC) >> 2);
  10768. push @event, "3:exhaustAirFlow:" . (($db[4] & 3) << 8 | $db[3]);
  10769. push @event, "3:supplyFanSpeed:". ($db[2] << 4 | ($db[1] & 0xF0) >> 4);
  10770. push @event, "3:exhaustFanSpeed:" . (($db[1] & 0x0F) << 8 | $db[0]);
  10771. push @event, "3:state:$ventilation";
  10772. } elsif ($msgType == 3) {
  10773. push @event, "3:SWVersion:" . (($db[13] & 0x0F) << 8 | $db[12]);
  10774. push @event, "3:operationHours:" . (($db[11] << 8 | $db[10]) * 3);
  10775. push @event, "3:input:" . sprintf("%02b %02b", $db[9], $db[8]);
  10776. push @event, "3:output:" . sprintf("%02b %02b", $db[7], $db[6]);
  10777. push @event, "3:info:" . sprintf("%02b %02b", $db[5], $db[4]);
  10778. push @event, "3:fault:" . sprintf("%02b %02b %02b %02b", $db[3], $db[2], $db[1], $db[0]);
  10779. }
  10780. } elsif ($st eq "valveCtrl.00") {
  10781. # Valve Control
  10782. # (D2-A0-01)
  10783. $db[0] &= 3;
  10784. if (AttrVal($name, "devMode", "master") eq "slave") {
  10785. # devMode slave
  10786. if ($db[0] == 1 || $db[0] == 3) {
  10787. push @event, "3:state:closes";
  10788. } elsif ($db[0] == 2) {
  10789. push @event, "3:state:opens";
  10790. } elsif ($db[0] == 0) {
  10791. my $state = ReadingsVal($name, "state", "-");
  10792. if ($state eq "closed" || $state eq "closes") {
  10793. $state = "01";
  10794. } elsif ($state eq "open" || $state eq "opens") {
  10795. $state = "02";
  10796. } else {
  10797. $state = "00";
  10798. }
  10799. EnOcean_SndRadio(undef, $hash, 1, "D2", $state, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  10800. }
  10801. } else {
  10802. #devMode master
  10803. if ($db[0] == 1) {
  10804. push @event, "3:state:closed";
  10805. } elsif ($db[0] == 2) {
  10806. push @event, "3:state:open";
  10807. } elsif ($db[0] == 0 || $db[0] == 3) {
  10808. push @event, "3:state:-";
  10809. }
  10810. }
  10811. } elsif ($st eq "liquidLeakage.51") {
  10812. # liquid leakage sensor
  10813. push @event, "3:state:" . $db[0] & 3 ? 'wet' : 'dry';
  10814. } elsif ($st eq "raw") {
  10815. # raw
  10816. push @event, "3:state:RORG: $rorg DATA: $data STATUS: $status ODATA: $odata";
  10817. # display data bytes $db[0] ... $db[x]
  10818. for (my $dbCntr = 0; $dbCntr <= $#db; $dbCntr++) {
  10819. push @event, "3:DB_" . $dbCntr . ":" . $db[$dbCntr];
  10820. }
  10821. } else {
  10822. # unknown devices
  10823. push @event, "3:state:$data";
  10824. }
  10825. } elsif ($rorg eq "D1") {
  10826. # MSC telegram
  10827. if ($st eq "actuator.01") {
  10828. if ($manufID eq "033") {
  10829. if (substr($data, 3, 1) == 4) {
  10830. my $getParam = ReadingsVal($name, "getParam", 0);
  10831. if ($getParam == 8) {
  10832. push @event, "3:loadClassification:no";
  10833. push @event, "3:loadLink:" . ($db[1] & 16 ? "connected" : "disconnected");
  10834. push @event, "3:loadOperation:3-wire";
  10835. push @event, "3:loadState:" . ($db[1] & 64 ? "on" : "off");
  10836. CommandDeleteReading(undef, "$name getParam");
  10837. } elsif ($getParam == 7) {
  10838. if ($db[0] & 4) {
  10839. push @event, "3:devTempState:warning";
  10840. } elsif ($db[0] & 2) {
  10841. push @event, "3:devTempState:max";
  10842. } else {
  10843. push @event, "3:devTempState:ok";
  10844. }
  10845. push @event, "3:mainsPower:" . ($db[0] & 8 ? "failure" : "ok");
  10846. if ($db[1] == 0xFF) {
  10847. push @event, "3:devTemp:invalid";
  10848. } else {
  10849. push @event, "3:devTemp:" . $db[1];
  10850. }
  10851. CommandDeleteReading(undef, "$name getParam");
  10852. } elsif ($getParam == 9) {
  10853. push @event, "3:voltage:" . sprintf("%.2f", (hex(substr($data, 4, 4)) * 0.01));
  10854. CommandDeleteReading(undef, "$name getParam");
  10855. } elsif ($getParam == 0x81) {
  10856. $hash->{READINGS}{serialNumber}{VAL} = substr($data, 4, 4);
  10857. $hash->{READINGS}{getParam}{VAL} = 0x82;
  10858. EnOcean_SndRadio(undef, $hash, $packetType, "D1", "033182", AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  10859. } elsif ($getParam == 0x82) {
  10860. push @event, "3:serialNumber:" . $hash->{READINGS}{serialNumber}{VAL} . substr($data, 4, 4);
  10861. CommandDeleteReading(undef, "$name getParam");
  10862. }
  10863. }
  10864. } elsif ($manufID eq "046") {
  10865. my $cmd = hex(substr($data, 4, 2));
  10866. if ($cmd == 3) {
  10867. push @event, "3:firmwareVersion:" . substr($data, 6, 6);
  10868. } elsif ($cmd == 5) {
  10869. push @event, "3:taughtInDevNum:$db[0]";
  10870. } elsif ($cmd == 7) {
  10871. CommandDeleteReading(undef, "$name taughtInDevID.*");
  10872. push @event, "3:taughtInDevID" . sprintf('%02d', $db[4]) . ":" . substr($data, 8, 8);
  10873. }
  10874. }
  10875. } elsif ($st eq "raw") {
  10876. # raw
  10877. push @event, "3:state:RORG: $rorg DATA: $data STATUS: $status ODATA: $odata";
  10878. push @event, "3:manufID:" . substr($data, 0, 3);
  10879. # display data bytes $db[0] ... $db[x]
  10880. for (my $dbCntr = 0; $dbCntr <= $#db; $dbCntr++) {
  10881. push @event, "3:DB_" . $dbCntr . ":" . $db[$dbCntr];
  10882. }
  10883. } else {
  10884. # unknown devices
  10885. if(AttrVal($name, "blockUnknownMSC", "no") eq "yes") {
  10886. push @event, "3:MSC:$data";
  10887. }
  10888. }
  10889. } elsif ($rorg eq "D0") {
  10890. # Signal Telegram
  10891. my $signalMID = hex(substr($data, 0, 2));
  10892. if ($signalMID == 1) {
  10893. push @event, "3:smartAckMailbox:empty";
  10894. } elsif ($signalMID == 2) {
  10895. push @event, "3:smartAckMailbox:not_exits";
  10896. } elsif ($signalMID == 3) {
  10897. push @event, "3:smartAckMailbox:reset";
  10898. } elsif ($signalMID == 4) {
  10899. my $responseID = $subDef eq $hash->{DEF} ? $iohash->{ChipID} : $subDef;
  10900. if ($db[0] == 0) {
  10901. } elsif ($db[0] == 1) {
  10902. # send MID 0x06
  10903. EnOcean_SndRadio(undef, $hash, 1, 'D0', '0664', $responseID, '00', 'F' x 8);
  10904. } elsif ($db[0] == 2) {
  10905. # send MID 0x07
  10906. my $swVersion = sprintf("%s", AttrVal('global', 'featurelevel', '99.99')) . '.00.00';
  10907. #my @revision = split(/\./, $swVersion);
  10908. $swVersion =~ /^(.*)\.(.*)\.(..)\.(..)$/;
  10909. EnOcean_SndRadio(undef, $hash, 1, 'D0', sprintf("07%02X%02X%02X%02X00000000", $1, $2, $3, $4), $responseID, '00', 'F' x 8);
  10910. } elsif ($db[0] == 3) {
  10911. # send MID 0x0A
  10912. if (exists($hash->{LASTInputDev}) && exists($hash->{"$hash->{LASTInputDev}_RSSI"}) &&
  10913. exists($hash->{"$hash->{LASTInputDev}_RepeatingCounter"}) && exists($hash->{"$hash->{LASTInputDev}_SubTelNum"})) {
  10914. my $data = '0A' . $iohash->{ChipID} . sprintf("%02X%02X%01X%01X", 127 - $hash->{"$hash->{LASTInputDev}_RSSI"},
  10915. 127 - $hash->{"$hash->{LASTInputDev}_RSSI"},
  10916. $hash->{"$hash->{LASTInputDev}_SubTelNum"},
  10917. $hash->{"$hash->{LASTInputDev}_RepeatingCounter"});
  10918. EnOcean_SndRadio(undef, $hash, 1, 'D0', $data, $responseID, '00', 'F' x 8);
  10919. }
  10920. } elsif ($db[0] == 4) {
  10921. # send MID 0x0D
  10922. EnOcean_SndRadio(undef, $hash, 1, 'D0', '0D00', $responseID, '00', 'F' x 8);
  10923. }
  10924. } elsif ($signalMID == 5) {
  10925. $hash->{Dev_ACK} = 'signal';
  10926. DoTrigger($name, "SIGNAL: Dev_ACK", 1);
  10927. } elsif ($signalMID == 6) {
  10928. push @event, "3:batteryPrecent:$db[0]";
  10929. } elsif ($signalMID == 7) {
  10930. push @event, "3:hwVersion:" . substr($data, 10, 8);
  10931. push @event, "3:swVersion:" . substr($data, 2, 8);
  10932. } elsif ($signalMID == 8) {
  10933. push @event, "3:trigger:heartbeat";
  10934. } elsif ($signalMID == 9) {
  10935. DoTrigger($name, "SIGNAL: RX_WINDOW_OPEN", 1);
  10936. } elsif ($signalMID == 10) {
  10937. $hash->{Dev_EURID} = substr($data, 2, 8);
  10938. if ($db[1] < 255) {$hash->{Dev_RSSImax} = 127 - $db[1]};
  10939. if ($db[2] < 255) {$hash->{Dev_RSSImin} = 127 - $db[2]};
  10940. my $subTelNum = $db[0] >> 4;
  10941. if ($subTelNum > 0) {$hash->{Dev_SubTelNum} = $subTelNum};
  10942. my $repeatingCounter = $db[0] & 0x0F;
  10943. if ($repeatingCounter < 0x0F) {$hash->{Dev_RepeatingCounter} = $repeatingCounter};
  10944. } elsif ($signalMID == 11) {
  10945. DoTrigger($name, "SIGNAL: DUTYCYCLE_LIMIT: " . ($db[0] >> 4 == 1 ? 'released' : 'reached'), 1);
  10946. Log3 $name, 2, "EnOcean $name SIGNAL DUTYCYCLE_LIMIT: " . ($db[0] >> 4 == 1 ? 'released' : 'reached');
  10947. } elsif ($signalMID == 12) {
  10948. DoTrigger($name, "SIGNAL: Dev_CHANGED", 1);
  10949. Log3 $name, 2, "EnOcean $name SIGNAL Dev_CHANGED";
  10950. } elsif ($signalMID == 13) {
  10951. my @harvester = ('very_good', 'good', 'average', 'bad', 'very_bad');
  10952. push @event, "3:harvester:" . $harvester[$db[0]];
  10953. }
  10954. } elsif ($rorg eq "B2") {
  10955. # GP complete data (GPCD)
  10956. my ($channel, $channelName, $value, $gpDef, $resolution) = (0, undef, undef, AttrVal($name, 'gpDef', undef), undef);
  10957. return "generic profil not defined" if (!defined $gpDef);
  10958. my @gpDef = split("[ \t][ \t]*", $gpDef);
  10959. my ($readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType);
  10960. $data = EnOcean_convHexToBit($data);
  10961. while (defined $gpDef[$channel]) {
  10962. ($channelName, undef, undef, undef, undef, $resolution, undef, undef, undef, undef) = split(':', $gpDef[$channel]);
  10963. $resolution = 0 if (!defined $resolution);
  10964. $data =~ m/^(.{$EnO_resolution[$resolution]})(.*)$/;
  10965. $value = hex(unpack('H8', pack('B32', '0' x (32 - $EnO_resolution[$resolution]) . $1)));
  10966. $data = $2;
  10967. ($err, $logLevel, $response, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType)=
  10968. EnOcean_gpConvDataToValue (undef, $hash, $channel, $value, $gpDef[$channel]);
  10969. $channel ++;
  10970. next if (defined $err);
  10971. push @event, "3:$readingName:" . sprintf("$readingFormat", $readingValue);
  10972. push @event, "3:" . $readingName . "Unit:$readingUnit";
  10973. push @event, "3:" . $readingName . "ValueType:$valueType";
  10974. push @event, "3:" . $readingName . "ChannelType:$readingType";
  10975. }
  10976. } elsif ($rorg eq "B3") {
  10977. # GP selective data (GPSD)
  10978. my $gpDef = AttrVal($name, "gpDef", undef);
  10979. return "generic profil not defined" if (!defined $gpDef);
  10980. my @gpDef = split("[ \t][ \t]*", $gpDef);
  10981. my ($channel, $channelName, $resolution, $value);
  10982. my ($readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType);
  10983. #Log3 $name, 2, "EnOcean $name parse GPSD data: $data start";
  10984. $data =~ m/^(.)(.*)$/;
  10985. my $header = hex $1;
  10986. $data = substr(EnOcean_convHexToBit($data), 4);
  10987. #Log3 $name, 2, "EnOcean $name parse GPSD header: $header data: $data start";
  10988. for (my $cntr = 1; $cntr <= $header; $cntr ++) {
  10989. $data =~ m/^(.{6})(.*)$/;
  10990. ($channel, $data) = (unpack('C', pack('B8', '00' . $1)), $2);
  10991. #Log3 $name, 2, "EnOcean $name parse GPSD channel: $channel data: $data";
  10992. if (defined $gpDef[$channel]) {
  10993. ($channelName, undef, undef, undef, undef, $resolution, undef, undef, undef, undef) = split(':', $gpDef[$channel]);
  10994. $resolution = 0 if (!defined $resolution || $resolution eq '');
  10995. $data =~ m/^(.{$EnO_resolution[$resolution]})(.*)$/;
  10996. $value = hex(unpack('H8', pack('B32', '0' x (32 - $EnO_resolution[$resolution]) . $1)));
  10997. $data = $2;
  10998. #Log3 $name, 2, "EnOcean $name parse GPSD channel: $channel value: " . $value . " data: $data";
  10999. ($err, $logLevel, $response, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType) =
  11000. EnOcean_gpConvDataToValue(undef, $hash, $channel, $value, $gpDef[$channel]);
  11001. push @event, "3:$readingName:" . sprintf("$readingFormat", $readingValue);
  11002. push @event, "3:" . $readingName . "Unit:$readingUnit";
  11003. push @event, "3:" . $readingName . "ValueType:$valueType";
  11004. push @event, "3:" . $readingName . "ChannelType:$readingType";
  11005. }
  11006. }
  11007. } elsif ($rorg eq "B0" && $teach) {
  11008. # GP teach in request (GPTI)
  11009. #
  11010. $data =~ m/^(....)(.*)$/;
  11011. my $header = hex($1);
  11012. $data = $2;
  11013. my $purpose = ($header & 12) >> 2;
  11014. if ($purpose == 0 || ($purpose == 2 && AttrVal($name, "subType", "") ne "genericProfile")) {
  11015. # teach-in request
  11016. $attr{$name}{comMode} = $header & 16 ? "biDir" : "uniDir";
  11017. $attr{$name}{eep} = "B0-00-00";
  11018. $attr{$name}{manufID} = sprintf "%03X", ($header & 0xFFE0) >> 5;
  11019. $attr{$name}{subType} = "genericProfile";
  11020. $attr{$name}{teachMethod} = 'GP';
  11021. my $channel = 0;
  11022. my $channelDir = "I";
  11023. my $channelDef;
  11024. my $channelName;
  11025. my $channelType;
  11026. my $cntr = 0;
  11027. my $dataOutboundDefLen = 0;
  11028. my @gpDef;
  11029. my $signalType;
  11030. #Log3 $name, 2, "EnOcean $name parse GPTI header: $header data: $data start";
  11031. $data = EnOcean_convHexToBit($data);
  11032. #Log3 $name, 2, "EnOcean $name parse GPTI data: $data start";
  11033. while (length($data) >= 12) {
  11034. last if ($cntr > 64);
  11035. $cntr ++;
  11036. $data =~ m/^(..)(.{8})(.*)$/;
  11037. $channelType = unpack('C', pack('B8', '000000' . $1));
  11038. $signalType = unpack('C', pack('B8', $2));
  11039. $data = $3;
  11040. #Log3 $name, 2, "EnOcean $name parse GPTI channel: $channel channelType: $channelType signalType: $signalType data: $data";
  11041. if ($channelType == 0) {
  11042. # teach-in information
  11043. if ($signalType == 1) {
  11044. # outbound channel description
  11045. $channelDir = "O";
  11046. $data =~ m/^(.{8})(.*)$/;
  11047. $dataOutboundDefLen = unpack('C', pack('B8', $1));
  11048. $data = $2;
  11049. } elsif ($signalType == 2) {
  11050. # produkt ID
  11051. $data =~ m/^(.{32})(.*)$/;
  11052. $attr{$name}{productID} = EnOcean_convBitToHex($1);
  11053. $data = $2;
  11054. }
  11055. } elsif ($channelType == 1) {
  11056. # data
  11057. $data =~ m/^(..)(....)(.{8})(....)(.{8})(....)(.*)$/;
  11058. $channelDef = $channelDir . ':' . $channelType . ':' . $signalType . ':' .
  11059. unpack('C', pack('B8', '000000' . $1)) . ':' . unpack('C', pack('B8', '0000' . $2)) . ':' .
  11060. unpack('c', pack('B8', $3)) . ':' . unpack('C', pack('B8', '0000' . $4)) . ':' .
  11061. unpack('c', pack('B8', $5)) . ':' . unpack('C', pack('B8', '0000' . $6));
  11062. $data = $7;
  11063. if (defined $EnO_gpValueData{$signalType}{name}) {
  11064. $channelName = $EnO_gpValueData{$signalType}{name};
  11065. } else {
  11066. $channelName = "none";
  11067. }
  11068. $gpDef[$channel] = $channelName . ':' . $channelDef;
  11069. $channel ++;
  11070. } elsif ($channelType == 2) {
  11071. # flag
  11072. $data =~ m/^(..)(.*)$/;
  11073. $channelDef = $channelDir . ':' .$channelType . ':' . $signalType . ':' .
  11074. unpack('C', pack('B8', '000000' . $1));
  11075. $data = $2;
  11076. if (defined $EnO_gpValueFlag{$signalType}{name}) {
  11077. $channelName = $EnO_gpValueFlag{$signalType}{name};
  11078. } else {
  11079. $channelName = "none";
  11080. }
  11081. $gpDef[$channel] = $channelName . ':' . $channelDef;
  11082. $channel ++;
  11083. } elsif ($channelType == 3) {
  11084. # enumeration
  11085. $data =~ m/^(..)(....)(.*)$/;
  11086. $channelDef = $channelDir . ':' .$channelType . ':' . $signalType . ':' .
  11087. unpack('C', pack('B8', '000000' . $1)) . ':' . unpack('C', pack('B8', '0000' . $2));
  11088. $data = $3;
  11089. if (defined $EnO_gpValueEnum{$signalType}{name}) {
  11090. $channelName = $EnO_gpValueEnum{$signalType}{name};
  11091. } else {
  11092. $channelName = "none";
  11093. }
  11094. $gpDef[$channel] = $channelName . ':' . $channelDef;
  11095. $channel ++;
  11096. }
  11097. }
  11098. $attr{$name}{gpDef} = join(' ', @gpDef);
  11099. if (AttrVal($name, "comMode", "uniDir") eq "biDir") {
  11100. # send GP Teach-In Response message
  11101. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
  11102. $data = sprintf "%04X", ((hex(AttrVal($name, "manufID", "7FF")) << 5) | 8);
  11103. EnOcean_SndCdm(undef, $hash, $packetType, "B1", $data, $subDef, "00", $senderID);
  11104. Log3 $name, 2, "EnOcean $name GP teach-in response sent to $senderID";
  11105. }
  11106. my $mid = $attr{$name}{manufID};
  11107. $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
  11108. push @event, "3:teach:GP teach-in accepted Manufacturer: $mid";
  11109. Log3 $name, 2, "EnOcean $name GP teach-in accepted Manufacturer: $mid";
  11110. # store attr subType, manufID, gpDef ...
  11111. EnOcean_CommandSave(undef, undef);
  11112. } elsif ($purpose == 1 || ($purpose == 2 && AttrVal($name, "subType", "") eq "genericProfile")) {
  11113. # teach-in deletion request
  11114. $deleteDevice = $name;
  11115. if (AttrVal($name, "comMode", "uniDir") eq "biDir") {
  11116. # send GP Teach-In Deletion Response message
  11117. $data = sprintf "%04X", (hex(AttrVal($name, "manufID", "7FF")) << 5) | 16;
  11118. EnOcean_SndCdm(undef, $hash, $packetType, "B1", $data, AttrVal($name, "subDef", "00000000"), "00", $senderID);
  11119. Log3 $name, 2, "EnOcean $name GP teach-in deletion response send to $senderID";
  11120. }
  11121. Log3 $name, 2, "EnOcean $name GP teach-in delete request executed";
  11122. }
  11123. } elsif ($rorg eq "B1" && $teach) {
  11124. # GP teach-in response (GPTR)
  11125. $data =~ m/^(....)(.*)$/;
  11126. my $header = hex($1);
  11127. $data = $2;
  11128. my $mid = sprintf "%03X", ($header & 0xFFE0) >> 5;
  11129. my $purpose = ($header & 24) >> 3;
  11130. if (exists $hash->{IODev}{helper}{gpRespWait}{$destinationID}) {
  11131. if ($purpose == 0) {
  11132. # teach-in rejected generally
  11133. if ($hash->{IODev}{helper}{gpRespWait}{$destinationID}{teachInReq} eq "in") {
  11134. push @event, "3:teach:GP teach-in rejected";
  11135. Log3 $name, 2, "EnOcean $name GP teach-in rejected by $senderID";
  11136. }
  11137. # clear teach-in request
  11138. delete $hash->{IODev}{helper}{gpRespWait}{$destinationID};
  11139. } elsif ($purpose == 1) {
  11140. # teach-in accepted
  11141. if ($hash->{IODev}{helper}{gpRespWait}{$destinationID}{teachInReq} eq "in") {
  11142. $attr{$name}{manufID} = $mid;
  11143. # substitute subDef with DEF
  11144. $attr{$name}{subDef} = $hash->{DEF};
  11145. $hash->{DEF} = $senderID;
  11146. $modules{EnOcean}{defptr}{$senderID} = $hash;
  11147. delete $modules{EnOcean}{defptr}{$destinationID};
  11148. EnOcean_CommandSave(undef, undef);
  11149. $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
  11150. push @event, "3:teach:GP teach-in accepted Manufacturer: $mid";
  11151. Log3 $name, 2, "EnOcean $name GP teach-in accepted by $senderID";
  11152. }
  11153. # clear teach-in request
  11154. delete $hash->{IODev}{helper}{gpRespWait}{$destinationID};
  11155. } elsif ($purpose == 2) {
  11156. # teach-out accepted
  11157. if ($hash->{IODev}{helper}{gpRespWait}{$destinationID}{teachInReq} eq "out") {
  11158. if (defined $attr{$name}{subDef}) {
  11159. delete $modules{EnOcean}{defptr}{$hash->{DEF}};
  11160. $hash->{DEF} = $attr{$name}{subDef};
  11161. $modules{EnOcean}{defptr}{$hash->{DEF}} = $hash;
  11162. delete $attr{$name}{subDef};
  11163. EnOcean_CommandSave(undef, undef);
  11164. }
  11165. push @event, "3:teach:GP teach-out accepted";
  11166. Log3 $name, 2, "EnOcean $name GP teach-out accepted";
  11167. }
  11168. # clear teach-in request
  11169. delete $hash->{IODev}{helper}{gpRespWait}{$destinationID};
  11170. } else {
  11171. if ($hash->{IODev}{helper}{gpRespWait}{$destinationID}{teachInReq} eq "in") {
  11172. # rejected channels outbound or inbound, sent teach-in response with teach-out
  11173. $data = sprintf "%04X", (hex(AttrVal($name, "manufID", "7FF")) << 5) | 16;
  11174. EnOcean_SndCdm(undef, $hash, $packetType, "B1", $data, $destinationID, "00", $senderID);
  11175. push @event, "3:teach:GP teach-in channels rejected, sent teach-out";
  11176. Log3 $name, 2, "EnOcean $name GP teach-in channels rejected, sent teach-out to $senderID";
  11177. }
  11178. # clear teach-in request
  11179. delete $hash->{IODev}{helper}{gpRespWait}{$destinationID};
  11180. }
  11181. } else {
  11182. Log3 $name, 2, "EnOcean $name GP teach-in response from $senderID received, teach-in request unknown";
  11183. }
  11184. } elsif ($rorg eq "D4" && $teach) {
  11185. # UTE - Universal Uni- and Bidirectional Teach-In / Teach-Out
  11186. #
  11187. Log3 $name, 5, "EnOcean $name UTE teach-in received from $senderID";
  11188. my $rorg = sprintf "%02X", $db[0];
  11189. my $func = sprintf "%02X", $db[1];
  11190. my $type = sprintf "%02X", $db[2];
  11191. my $mid = sprintf "%03X", ((($db[3] & 7) << 8) | $db[4]);
  11192. my $devChannel = $db[5];
  11193. my $comMode = $db[6] & 0x80 ? "biDir" : "uniDir";
  11194. my $comModeUTE = AttrVal($hash->{IODev}{NAME}, "comModeUTE", "auto");
  11195. $comMode = $comModeUTE if ($comModeUTE ne 'auto');
  11196. my $subType = "$rorg.$func.$type";
  11197. if (($db[6] & 0xF) == 0) {
  11198. # Teach-In Query telegram received
  11199. my $teachInReq = ($db[6] & 0x30) >> 4;
  11200. if ($teachInReq == 0 || $teachInReq == 2) {
  11201. # Teach-In Request
  11202. $attr{$name}{teachMethod} = 'UTE';
  11203. if(exists $EnO_eepConfig{$subType}) {
  11204. # Teach-In EEP supported
  11205. foreach my $attrCntr (keys %{$EnO_eepConfig{$subType}{attr}}) {
  11206. $attr{$name}{$attrCntr} = $EnO_eepConfig{$subType}{attr}{$attrCntr};
  11207. }
  11208. $attr{$name}{manufID} = $mid;
  11209. $attr{$name}{devChannel} = $devChannel;
  11210. $attr{$name}{comMode} = $comMode;
  11211. $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
  11212. $attr{$name}{eep} = "$rorg-$func-$type";
  11213. if (exists($hash->{helper}{teachInWait}) && $hash->{helper}{teachInWait} =~ m/^UTE|STE$/) {
  11214. $attr{$filelogName}{logtype} = $EnO_eepConfig{$subType}{GPLOT} . 'text'
  11215. if (exists $attr{$filelogName}{logtype});
  11216. EnOcean_CreateSVG(undef, $hash, undef);
  11217. delete $hash->{helper}{teachInWait};
  11218. }
  11219. $subType = $EnO_eepConfig{$subType}{attr}{subType};
  11220. #$attr{$name}{subType} = $subType;
  11221. push @event, "3:teach:UTE teach-in accepted EEP $rorg-$func-$type Manufacturer: $mid";
  11222. if (!($db[6] & 0x40)) {
  11223. # UTE Teach-In-Response expected
  11224. # send UTE Teach-In Response message
  11225. $data = (sprintf "%02X", $db[6] & 0x80 | 0x11) . substr($data, 2, 12);
  11226. if ($comMode eq "biDir") {
  11227. ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", $comMode);
  11228. } else {
  11229. $subDef = "00000000";
  11230. }
  11231. EnOcean_SndRadio(undef, $hash, $packetType, "D4", $data, $subDef, "00", $senderID);
  11232. Log3 $name, 2, "EnOcean $name UTE teach-in response send to $senderID";
  11233. }
  11234. Log3 $name, 2, "EnOcean $name UTE teach-in accepted EEP $rorg-$func-$type Manufacturer: $mid";
  11235. } else {
  11236. # Teach-In EEP not supported
  11237. $attr{$name}{subType} = "raw";
  11238. $attr{$name}{manufID} = $mid;
  11239. $attr{$name}{devChannel} = $devChannel;
  11240. $attr{$name}{comMode} = $comMode;
  11241. $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
  11242. push @event, "3:teach:UTE teach-in accepted EEP $rorg-$func-$type not supported Manufacturer: $mid";
  11243. # send EEP Teach-In Response message
  11244. if (!($db[6] & 0x40)) {
  11245. # UTE Teach-In-Response expected
  11246. # send UTE Teach-In Response message
  11247. $data = (sprintf "%02X", $db[6] & 0x80 | 0x31) . substr($data, 2, 12);
  11248. EnOcean_SndRadio(undef, $hash, $packetType, "D4", $data, "00000000", "00", $senderID);
  11249. Log3 $name, 2, "EnOcean $name UTE teach-in response send to $senderID";
  11250. }
  11251. Log3 $name, 2, "EnOcean $name UTE teach-in accepted EEP $rorg-$func-$type not supported Manufacturer: $mid";
  11252. }
  11253. # store attr subType, manufID ...
  11254. EnOcean_CommandSave(undef, undef);
  11255. } elsif ($teachInReq == 1) {
  11256. # Teach-In Deletion Request
  11257. $deleteDevice = $name;
  11258. if (!($db[6] & 0x40)) {
  11259. # UTE Teach-In Deletion Response expected
  11260. # send UTE Teach-In Deletion Response message
  11261. $data = (sprintf "%02X", $db[6] & 0x80 | 0x21) . substr($data, 2, 12);
  11262. EnOcean_SndRadio(undef, $hash, $packetType, "D4", $data, AttrVal($name, "subDef", "00000000"), "00", $senderID);
  11263. Log3 $name, 2, "EnOcean $name UTE teach-in deletion response send to $senderID";
  11264. }
  11265. Log3 $name, 2, "EnOcean $name UTE teach-in delete request executed";
  11266. }
  11267. } else {
  11268. # Teach-In Respose telegram received
  11269. my $teachInAccepted = ($db[6] & 0x30) >> 4;
  11270. Log3 $name, 5, "EnOcean $name UTE teach-in response message from $senderID received";
  11271. if (exists $hash->{IODev}{helper}{UTERespWait}{$destinationID}) {
  11272. if ($comMode eq "uniDir") {
  11273. $attr{$name}{manufID} = $mid;
  11274. if ($teachInAccepted == 0) {
  11275. $teachInAccepted = "request not accepted";
  11276. } elsif ($teachInAccepted == 1){
  11277. $teachInAccepted = "teach-in accepted";
  11278. } elsif ($teachInAccepted == 2){
  11279. $teachInAccepted = "teach-out accepted";
  11280. } else {
  11281. $teachInAccepted = "EEP not supported";
  11282. }
  11283. $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
  11284. push @event, "3:teach:UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
  11285. Log3 $name, 2, "EnOcean $name UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
  11286. } else {
  11287. if ($hash->{IODev}{helper}{UTERespWait}{$destinationID}{teachInReq} eq "in") {
  11288. # Teach-In Request
  11289. if ($teachInAccepted == 0) {
  11290. $teachInAccepted = "request not accepted";
  11291. } elsif ($teachInAccepted == 1){
  11292. $teachInAccepted = "teach-in accepted";
  11293. $attr{$name}{subDef} = $hash->{DEF};
  11294. $hash->{DEF} = $senderID;
  11295. $modules{EnOcean}{defptr}{$senderID} = $hash;
  11296. delete $modules{EnOcean}{defptr}{$destinationID};
  11297. $attr{$name}{manufID} = $mid;
  11298. # store attr subType, manufID ...
  11299. EnOcean_CommandSave(undef, undef);
  11300. } elsif ($teachInAccepted == 2){
  11301. $teachInAccepted = "teach-out accepted";
  11302. } else {
  11303. $teachInAccepted = "EEP not supported";
  11304. }
  11305. $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
  11306. push @event, "3:teach:UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
  11307. Log3 $name, 2, "EnOcean $name UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
  11308. } elsif ($hash->{IODev}{helper}{UTERespWait}{$destinationID}{teachInReq} eq "out") {
  11309. # Teach-In Deletion Request
  11310. if ($teachInAccepted == 0) {
  11311. $teachInAccepted = "request not accepted";
  11312. } elsif ($teachInAccepted == 1){
  11313. $teachInAccepted = "teach-in accepted";
  11314. } elsif ($teachInAccepted == 2){
  11315. $teachInAccepted = "teach-out accepted";
  11316. if (defined $attr{$name}{subDef}) {
  11317. delete $modules{EnOcean}{defptr}{$hash->{DEF}};
  11318. $hash->{DEF} = $attr{$name}{subDef};
  11319. $modules{EnOcean}{defptr}{$hash->{DEF}} = $hash;
  11320. delete $attr{$name}{subDef};
  11321. EnOcean_CommandSave(undef, undef);
  11322. }
  11323. } else {
  11324. $teachInAccepted = "EEP not supported";
  11325. }
  11326. $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
  11327. push @event, "3:teach:UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
  11328. Log3 $name, 2, "EnOcean $name UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
  11329. }
  11330. }
  11331. # clear teach-in request
  11332. delete $hash->{IODev}{helper}{UTERespWait}{$destinationID};
  11333. } else {
  11334. # teach-in request unknown, delete response device, no action
  11335. $deleteDevice = $name;
  11336. Log3 $name, 2, "EnOcean $name UTE teach-in request unknown";
  11337. }
  11338. }
  11339. } elsif ($rorg eq "35" && $teach) {
  11340. # Secure Teach-In
  11341. ($err, $msg) = EnOcean_sec_parseTeachIn($hash, $data, $subDef, $destinationID);
  11342. if (defined $err) {
  11343. Log3 $name, 2, "EnOcean $name secure teach-in ERROR: $err";
  11344. return "";
  11345. }
  11346. Log3 $name, 3, "EnOcean $name secure teach-in $msg";
  11347. EnOcean_CommandSave(undef, undef);
  11348. return "";
  11349. } elsif ($rorg eq "C6" && $smartAckLearn) {
  11350. # Smart Ack Learn Request
  11351. #####
  11352. $data =~ m/^(....)(..)(..)(..)(..)(........)$/;
  11353. my $subType = "$2.$3.$4";
  11354. my $postmasterID = '0' x 8;
  11355. my $mid = '7FF';
  11356. my $responseTime = 150;
  11357. my $sendData = '';
  11358. if (exists $EnO_eepConfig{$subType}) {
  11359. if (($db[9] & 0xF8) == 0xF8) {
  11360. # Smart Ack send by sensor
  11361. $attr{$name}{subType} = $EnO_eepConfig{$subType}{attr}{subType};
  11362. $attr{$name}{eep} = "$3-$4-$5";
  11363. $mid = substr(sprintf("%04X", hex($1) & 0x7FF), 1);
  11364. $attr{$name}{manufID} = $mid;
  11365. $mid = $EnO_manuf{$mid} if(exists $EnO_manuf{$mid});
  11366. $attr{$name}{repeaterID} = $6;
  11367. $postmasterID = $6;
  11368. $attr{$name}{postmasterID} = $postmasterID;
  11369. $attr{$name}{teachMethod} = 'smartAck';
  11370. $hash->{SmartAckRSSI} = - hex($db[4]);
  11371. foreach my $attrCntr (keys %{$EnO_eepConfig{$subType}{attr}}) {
  11372. if ($attrCntr ne "subDef") {
  11373. $attr{$name}{$attrCntr} = $EnO_eepConfig{$subType}{attr}{$attrCntr};
  11374. }
  11375. }
  11376. if (defined AttrVal($name, 'subDef', undef)) {
  11377. $subDef = $attr{$name}{subDef};
  11378. } else {
  11379. $subDef = EnOcean_CheckSenderID('getNextID', $hash->{IODev}{NAME}, "00000000");
  11380. $attr{$name}{subDef} = $subDef;
  11381. }
  11382. # create mailbox
  11383. $sendData = sprintf "03%04X00%04X%04X", $responseTime, $postmasterID, $hash->{DEF};
  11384. EnOcean_SndRadio(undef, $hash, 6, $rorg, $sendData, $subDef, '00', $hash->{DEF});
  11385. # next commands will be sent with a delay
  11386. #usleep($responseTime * 1000);
  11387. # send learn reply
  11388. $sendData = sprintf "01%04X00%04X", $responseTime, $hash->{DEF};
  11389. EnOcean_SndRadio(undef, $hash, 1, 'C7', $sendData, $subDef, '00', $hash->{DEF});
  11390. push @event, "3:teach:Smart Ack teach-in accepted EEP " . $attr{$name}{eep} . " Manufacturer: $mid";
  11391. Log3 $name, 2, "EnOcean $name Smart Ack teach-in accepted EEP " . $attr{$name}{eep} . " Manufacturer: $mid";
  11392. EnOcean_CommandSave(undef, undef);
  11393. }
  11394. } else {
  11395. # EEP not supported
  11396. # send learn reply
  11397. $sendData = sprintf "01%04X10%04X", $responseTime, $hash->{DEF};
  11398. EnOcean_SndRadio(undef, $hash, 1, 'C7', $sendData, '0' x 8, '00', $hash->{DEF});
  11399. CommandDelete(undef, $name);
  11400. Log3 $name, 2, "EnOcean $name Smart Ack teach-in not accepted EEP " . $attr{$name}{eep} . " Manufacturer: $mid";
  11401. }
  11402. } elsif ($packetType == 7) {
  11403. # packet type REMOTE_MAN_COMMAND
  11404. my $remoteCode = AttrVal($name, 'remoteCode', '0' x 8);
  11405. my $remoteLastStatusReturnCode = $manufID eq '7FF' ? '00' : '04';
  11406. my $remoteManagement = AttrVal($name, "remoteManagement", "off");
  11407. my $sendData = '';
  11408. push @event, "3:remoteLastFunctionNumber:" . sprintf("%03X", $funcNumber);
  11409. if ($funcNumber == 1 && $remoteManagement eq 'client') {
  11410. # unlock
  11411. if ($data eq uc($remoteCode) && !exists($hash->{RemoteClientUnlockFailed})) {
  11412. $hash->{RemoteClientUnlock} = 1;
  11413. #my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
  11414. #RemoveInternalTimer(\%functionHash);
  11415. #InternalTimer(gettimeofday() + 1800, 'EnOcean_cdmClearHashVal', \%functionHash, 0);
  11416. RemoveInternalTimer($hash->{helper}{timer}{RemoteClientUnlock}) if(exists $hash->{helper}{timer}{RemoteClientUnlock});
  11417. $hash->{helper}{timer}{RemoteClientUnlock} = {hash => $hash, param => 'RemoteClientUnlock'};
  11418. InternalTimer(gettimeofday() + 1800, 'EnOcean_cdmClearHashVal', $hash->{helper}{timer}{RemoteClientUnlock}, 0);
  11419. Log3 $name, 2, "EnOcean $name RMCC unlock request executed.";
  11420. } else {
  11421. $remoteLastStatusReturnCode = '02';
  11422. $hash->{RemoteClientUnlockFailed} = 1;
  11423. #my %functionHash = (hash => $hash, param => 'RemoteClientUnlockFailed');
  11424. #RemoveInternalTimer(\%functionHash);
  11425. #InternalTimer(gettimeofday() + 30, 'EnOcean_cdmClearHashVal', \%functionHash, 0);
  11426. RemoveInternalTimer($hash->{helper}{timer}{RemoteClientUnlockFailed}) if(exists $hash->{helper}{timer}{RemoteClientUnlockFailed});
  11427. $hash->{helper}{timer}{RemoteClientUnlockFailed} = {hash => $hash, param => 'RemoteClientUnlockFailed'};
  11428. InternalTimer(gettimeofday() + 30, 'EnOcean_cdmClearHashVal', $hash->{helper}{timer}{RemoteClientUnlockFailed}, 0);
  11429. Log3 $name, 2, "EnOcean $name RMCC unlock request not executed, remote Code $data wrong.";
  11430. }
  11431. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11432. } elsif ($funcNumber == 2 && $remoteManagement eq 'client') {
  11433. # lock
  11434. if ($hash->{RemoteClientUnlock} && $data eq uc($remoteCode)) {
  11435. delete $hash->{RemoteClientUnlock};
  11436. #####
  11437. #my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
  11438. #RemoveInternalTimer(\%functionHash);
  11439. RemoveInternalTimer($hash->{helper}{timer}{RemoteClientUnlock}) if(exists $hash->{helper}{timer}{RemoteClientUnlock});
  11440. Log3 $name, 2, "EnOcean $name RMCC lock request executed.";
  11441. } else {
  11442. $remoteLastStatusReturnCode = '02';
  11443. Log3 $name, 2, "EnOcean $name RMCC lock request not executed.";
  11444. }
  11445. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11446. } elsif ($funcNumber == 3 && $remoteManagement eq 'client') {
  11447. # set code
  11448. if ($hash->{RemoteClientUnlock} && $data =~ m/^[A-Fa-f0-9]{8}$/ && uc($data) ne 'FFFFFFFF') {
  11449. $attr{$name}{remoteCode} = $data;
  11450. EnOcean_CommandSave(undef, undef);
  11451. Log3 $name, 2, "EnOcean $name RMCC set code request executed.";
  11452. } else {
  11453. $remoteLastStatusReturnCode = '05';
  11454. Log3 $name, 2, "EnOcean $name RMCC set code request not executed.";
  11455. }
  11456. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11457. } elsif ($funcNumber == 4 && $remoteManagement eq 'client') {
  11458. # query ID
  11459. if ($hash->{RemoteClientUnlock}) {
  11460. my $eepRcv = hex(substr($data, 0, 6)) >> 3;
  11461. my $rorg = sprintf "%02X", ($eepRcv >> 13);
  11462. my $func = sprintf "%02X", (($eepRcv & 0x1F80) >> 7);
  11463. my $type = sprintf "%02X", ($eepRcv & 127);
  11464. $eepRcv = "$rorg-$func-$type";
  11465. if ($hash->{RemoteClientUnlock} && $eep eq $eepRcv) {
  11466. $sendData = '06040' . AttrVal($name, 'manufID', '7FF') . substr($data, 0, 4) . sprintf("%02X", hex(substr($data, 4, 2)) & 0xF8);
  11467. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
  11468. Log3 $name, 2, "EnOcean $name RMCC query ID answer sent.";
  11469. } else {
  11470. $remoteLastStatusReturnCode = '03';
  11471. Log3 $name, 2, "EnOcean $name RMCC query ID request not executed, EEP $eepRcv wrong or client locked.";
  11472. }
  11473. } else {
  11474. $remoteLastStatusReturnCode = '07';
  11475. Log3 $name, 2, "EnOcean $name RMCC query ID request not executed.";
  11476. }
  11477. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11478. } elsif ($funcNumber == 5 && $remoteManagement eq 'client') {
  11479. # action
  11480. if ($hash->{RemoteClientUnlock}) {
  11481. Log3 $name, 2, "EnOcean $name RMCC action request executed";
  11482. } else {
  11483. $remoteLastStatusReturnCode = '01';
  11484. Log3 $name, 2, "EnOcean $name RMCC action request not executed.";
  11485. }
  11486. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11487. } elsif ($funcNumber == 6 && $remoteManagement eq 'client') {
  11488. # ping
  11489. my $eep = AttrVal($name, "eep", "C5-00-00");
  11490. if ($eep =~ m/^([A-Fa-f0-9]{2})-([A-Fa-f0-9]{2})-([A-Fa-f0-9]{2})$/i) {
  11491. $eep = (((hex($1) << 6) | hex($2)) << 7) | hex($3);
  11492. } else {
  11493. $eep = (((hex("C5") << 6) | hex("00")) << 7) | hex("00");
  11494. }
  11495. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11496. push @event, "3:remoteRSSI:" . -$RSSI;
  11497. $sendData = '06060' . AttrVal($name, 'manufID', '7FF') . sprintf("%04X%02X", $eep << 3, $RSSI);
  11498. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
  11499. Log3 $name, 2, "EnOcean $name RMCC ping answer executed";
  11500. } elsif ($funcNumber == 7 && $remoteManagement eq 'client') {
  11501. # query function
  11502. if ($hash->{RemoteClientUnlock}) {
  11503. $sendData = '06070' . AttrVal($name, 'manufID', '7FF') . '020107FF';
  11504. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
  11505. Log3 $name, 2, "EnOcean $name RMCC query function answer sent.";
  11506. } else {
  11507. $remoteLastStatusReturnCode = '07';
  11508. Log3 $name, 2, "EnOcean $name RMCC query function request not executed.";
  11509. }
  11510. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11511. } elsif ($funcNumber == 8 && $remoteManagement eq 'client') {
  11512. # query status
  11513. if ($hash->{RemoteClientUnlock}) {
  11514. $sendData = '06080' . AttrVal($name, 'manufID', '7FF') . '000' . ReadingsVal($name, "remoteLastFunctionNumber", "000") .
  11515. ReadingsVal($name, "$remoteLastStatusReturnCode", '00');
  11516. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
  11517. Log3 $name, 2, "EnOcean $name RMCC query status answer sent.";
  11518. } else {
  11519. $remoteLastStatusReturnCode = '07';
  11520. Log3 $name, 2, "EnOcean $name RMCC query status request not executed.";
  11521. }
  11522. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11523. } elsif ($funcNumber == 0x201) {
  11524. $data =~ m/^(..)(..)(..)(..)$/;
  11525. my ($rorg, $func, $type, $flag) = ($1, $2, $3, hex($4));
  11526. if ($flag == 1) {
  11527. # learn in
  11528. if (exists $EnO_eepConfig{"$rorg.$func.$type"}) {
  11529. $attr{$name}{eep} = "$rorg-$func-$type";
  11530. #$attr{$name}{remoteID} = $remoteID if (defined $remoteID);
  11531. $attr{$name}{remoteManagement} = "client";
  11532. $attr{$name}{teachMethod} = 'RPC';
  11533. foreach my $attrCntr (keys %{$EnO_eepConfig{"$rorg.$func.$type"}{attr}}) {
  11534. if ($attrCntr eq "subDef") {
  11535. if (!exists $attr{$name}{$attrCntr}) {
  11536. $attr{$name}{$attrCntr} = EnOcean_CheckSenderID($EnO_eepConfig{"$rorg.$func.$type"}{attr}{$attrCntr}, $hash->{IODev}{NAME}, "00000000");
  11537. }
  11538. } else {
  11539. $attr{$name}{$attrCntr} = $EnO_eepConfig{"$rorg.$func.$type"}{attr}{$attrCntr};
  11540. }
  11541. }
  11542. EnOcean_CreateSVG(undef, $hash, $attr{$name}{eep});
  11543. push @event, "3:teach:RPC teach-in accepted EEP $rorg-$func-$type Manufacturer: " .
  11544. (exists($EnO_manuf{$manufID}) ? $EnO_manuf{$manufID} : $manufID);
  11545. Log3 $name, 2, "EnOcean $name RPC teach-in with EEP $rorg-$func-$type ManufacturerID: $manufID accepted.";
  11546. } else {
  11547. Log3 $name, 2, "EnOcean $name RPC teach-in with EEP $rorg-$func-$type ManufacturerID: $manufID not supported.";
  11548. }
  11549. } elsif ($flag == 3) {
  11550. # learn out
  11551. #Log3 $name, 2, "EnOcean $name device $name deleted";
  11552. CommandDelete(undef, $name);
  11553. Log3 $name, 2, "EnOcean $name RPC teach-out with EEP $rorg-$func-$type ManufacturerID: $manufID executed.";
  11554. EnOcean_CommandSave(undef, undef);
  11555. } else {
  11556. Log3 $name, 2, "EnOcean $name RPC learn function $flag not supported";
  11557. }
  11558. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11559. } elsif ($funcNumber == 0x240 && $remoteManagement eq 'manager') {
  11560. # acknowledge
  11561. $remoteLastStatusReturnCode = '00';
  11562. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11563. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11564. Log3 $name, 2, "EnOcean $name RPC acknowledge received";
  11565. } elsif ($funcNumber == 0x604 && $remoteManagement eq 'manager') {
  11566. # query id answer
  11567. my $eep = hex(substr($data, 0, 6)) >> 3;
  11568. my $rorg = sprintf "%02X", ($eep >> 13);
  11569. my $func = sprintf "%02X", (($eep & 0x1F80) >> 7);
  11570. my $type = sprintf "%02X", ($eep & 127);
  11571. $attr{$name}{remoteManufID} = $manufID;
  11572. $manufID = $EnO_manuf{$manufID} if($EnO_manuf{$manufID});
  11573. $attr{$name}{remoteEEP} = "$rorg-$func-$type";
  11574. my $subType = "$rorg.$func.$type";
  11575. #$attr{$name}{subType} = $EnO_eepConfig{$subType}{attr}{subType} if($EnO_manuf{$subType});
  11576. $remoteLastStatusReturnCode = '00';
  11577. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11578. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11579. Log3 $name, 2, "EnOcean $name RMCC query ID answer received EEP $rorg-$func-$type Manufacturer: $manufID";
  11580. EnOcean_CommandSave(undef, undef);
  11581. } elsif ($funcNumber == 0x606 && $remoteManagement eq 'manager') {
  11582. # ping answer
  11583. my $eep = hex(substr($data, 0, 6)) >> 3;
  11584. my $rorg = sprintf "%02X", ($eep >> 13);
  11585. my $func = sprintf "%02X", (($eep & 0x1F80) >> 7);
  11586. my $type = sprintf "%02X", ($eep & 127);
  11587. $attr{$name}{remoteManufID} = $manufID;
  11588. $manufID = $EnO_manuf{$manufID} if($EnO_manuf{$manufID});
  11589. $attr{$name}{remoteEEP} = "$rorg-$func-$type";
  11590. my $subType = "$rorg.$func.$type";
  11591. #$attr{$name}{subType} = $EnO_eepConfig{$subType}{attr}{subType} if($EnO_manuf{$subType});
  11592. push @event, "3:remoteRSSI:" . -$RSSI;
  11593. $remoteLastStatusReturnCode = '00';
  11594. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11595. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11596. Log3 $name, 2, "EnOcean $name RMCC ping answer received EEP $rorg-$func-$type Manufacturer: $manufID";
  11597. EnOcean_CommandSave(undef, undef);
  11598. } elsif ($funcNumber == 0x607 && $remoteManagement eq 'manager') {
  11599. # query function answer
  11600. CommandDeleteReading(undef, "$name remoteFunction.*");
  11601. my $count = 1;
  11602. my $len = length($data);
  11603. while ($len > 0) {
  11604. $data =~ m/^.(...).(...)(.*)$/;
  11605. push @event, "3:remoteFunction" . sprintf("%02d", $count) . ":$1:$2:" . (exists($EnO_extendedRemoteFunctionCode{hex($1)}) ? $EnO_extendedRemoteFunctionCode{hex($1)} : '-');
  11606. $count ++;
  11607. $data = $3;
  11608. $len -= 8;
  11609. }
  11610. $remoteLastStatusReturnCode = '00';
  11611. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11612. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11613. Log3 $name, 2, "EnOcean $name RMCC query function answer received";
  11614. } elsif ($funcNumber == 0x608 && $remoteManagement eq 'manager') {
  11615. # query status answer
  11616. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11617. push @event, "3:remoteLastFunctionNumber:" . substr($data, 3, 3);
  11618. push @event, "3:remoteLastStatusReturnCode:" . substr($data, 6, 2);
  11619. Log3 $name, 2, "EnOcean $name RMCC query status answer received LastFunction: " . substr($data, 3, 3) .
  11620. " LastFunctionCode: " . substr($data, 6, 2);
  11621. } elsif ($funcNumber == 0x810 && $remoteManagement eq 'manager') {
  11622. # teach-in supported link tables response
  11623. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11624. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11625. my $supportFlags = hex(substr($data, 0, 2));
  11626. my $linkTableInCurrent = substr($data, 6, 2) && $supportFlags & 0x10 ? substr($data, 6, 2) : '00';
  11627. my $linkTableInMax = substr($data, 8, 2) && $supportFlags & 0x10 ? substr($data, 8, 2) : '00';
  11628. my $linkTableOutCurrent = substr($data, 2, 2) && $supportFlags & 0x20 ? substr($data, 2, 2) : '00';
  11629. my $linkTableOutMax = substr($data, 4, 2) && $supportFlags & 0x20 ? substr($data, 4, 2) : '00';
  11630. push @event, "3:remoteLearn:" . ($supportFlags & 0x80 ? 'supported' : 'not_supported');
  11631. push @event, "3:remoteLinkTableIn:" . ($supportFlags & 0x10 ? 'supported' : 'not_supported');
  11632. push @event, "3:remoteLinkTableOut:" . ($supportFlags & 0x20 ? 'supported' : 'not_supported');
  11633. push @event, "3:remoteLinkTableInCurrent:" . $linkTableInCurrent if ($supportFlags & 0x10);
  11634. push @event, "3:remoteLinkTableInMax:" . $linkTableInMax if ($supportFlags & 0x10);
  11635. push @event, "3:remoteLinkTableOutCurrent:" . $linkTableOutCurrent if ($supportFlags & 0x20);
  11636. push @event, "3:remoteLinkTableOutMax:" . $linkTableOutMax if ($supportFlags & 0x20);
  11637. push @event, "3:remoteTeach:" . ($supportFlags & 0x40 ? 'supported' : 'not_supported');
  11638. Log3 $name, 2, "EnOcean $name RPC teach-in supported link tables response received Data: $data";
  11639. # request outbound table
  11640. #$hash->{IODev}{helper}{remoteAnswerWait}{0x821}{hash} = $hash;
  11641. #$hash->{IODev}{helper}{remoteLinkTableStartRef} = 0;
  11642. #my %functionHash = (hash => $hash, param => 0x821);
  11643. #RemoveInternalTimer(\%functionHash);
  11644. #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
  11645. #$sendData = '022107FF';
  11646. #EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
  11647. #Log3 $name, 2, "EnOcean $name RPC request teach-in outbound table";
  11648. } elsif ($funcNumber == 0x811 && $remoteManagement eq 'manager') {
  11649. # link table response
  11650. CommandDeleteReading(undef, "$name remoteLinkTableDesc.*");
  11651. $data =~ m/^(..)(.*)$/;
  11652. my $direction = hex($1) & 0x80 ? 'Out' : 'In';
  11653. $data = $2;
  11654. while (length($data) > 0) {
  11655. $data =~ m/^(..)(........)(..)(..)(..)(..)(.*)$/;
  11656. push @event, "3:remoteLinkTableDesc" . $direction . "$1:S2:S3-S4-$5:$6";
  11657. $data = $7;
  11658. }
  11659. $remoteLastStatusReturnCode = '00';
  11660. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11661. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11662. Log3 $name, 2, "EnOcean $name RPC link table response received";
  11663. } elsif ($funcNumber == 0x813 && $remoteManagement eq 'manager') {
  11664. # link table GP response
  11665. CommandDeleteReading(undef, "$name remoteLinkTableGPDesc.*");
  11666. $data =~ m/^(..)(.*)$/;
  11667. my $direction = hex($1) & 0x80 ? 'Out' : 'In';
  11668. $data = $2;
  11669. my $channel = 0;
  11670. my $channelDir = hex($1) & 0x80 ? 'O' : 'I';
  11671. my $channelDef;
  11672. my $channelName;
  11673. my $channelType;
  11674. my $dataOutboundDefLen = 0;
  11675. my $gpData;
  11676. my @gpDef;
  11677. my $gpIdx;
  11678. my $signalType;
  11679. while (length($data) > 0) {
  11680. $data =~ m/^(..)(.{12})(.*)$/;
  11681. $gpIdx = $1;
  11682. $gpData = EnOcean_convHexToBit($2);
  11683. $data = $3;
  11684. $gpData =~ m/^(..)(.{8})(.*)$/;
  11685. $channelType = unpack('C', pack('B8', '000000' . $1));
  11686. $signalType = unpack('C', pack('B8', $2));
  11687. $data = $3;
  11688. #Log3 $name, 2, "EnOcean $name parse RPC link table GP idx: $gpIdx channelType: $channelType signalType: $signalType data: $data";
  11689. if ($channelType == 0) {
  11690. # teach-in information
  11691. if ($signalType == 1) {
  11692. # outbound channel description
  11693. } elsif ($signalType == 2) {
  11694. # produkt ID
  11695. }
  11696. } elsif ($channelType == 1) {
  11697. # data
  11698. $gpData =~ m/^(..)(....)(.{8})(....)(.{8})(....)(.*)$/;
  11699. $channelDef = $channelDir . ':' . $channelType . ':' . $signalType . ':' .
  11700. unpack('C', pack('B8', '000000' . $1)) . ':' . unpack('C', pack('B8', '0000' . $2)) . ':' .
  11701. unpack('c', pack('B8', $3)) . ':' . unpack('C', pack('B8', '0000' . $4)) . ':' .
  11702. unpack('c', pack('B8', $5)) . ':' . unpack('C', pack('B8', '0000' . $6));
  11703. if (defined $EnO_gpValueData{$signalType}{name}) {
  11704. $channelName = $EnO_gpValueData{$signalType}{name};
  11705. } else {
  11706. $channelName = "none";
  11707. }
  11708. $gpDef[$channel] = $channelName . ':' . $channelDef;
  11709. } elsif ($channelType == 2) {
  11710. # flag
  11711. $gpData =~ m/^(..)(.*)$/;
  11712. $channelDef = $channelDir . ':' .$channelType . ':' . $signalType . ':' .
  11713. unpack('C', pack('B8', '000000' . $1));
  11714. if (defined $EnO_gpValueFlag{$signalType}{name}) {
  11715. $channelName = $EnO_gpValueFlag{$signalType}{name};
  11716. } else {
  11717. $channelName = "none";
  11718. }
  11719. $gpDef[$channel] = $channelName . ':' . $channelDef;
  11720. } elsif ($channelType == 3) {
  11721. # enumeration
  11722. $gpData =~ m/^(..)(....)(.*)$/;
  11723. $channelDef = $channelDir . ':' .$channelType . ':' . $signalType . ':' .
  11724. unpack('C', pack('B8', '000000' . $1)) . ':' . unpack('C', pack('B8', '0000' . $2));
  11725. if (defined $EnO_gpValueEnum{$signalType}{name}) {
  11726. $channelName = $EnO_gpValueEnum{$signalType}{name};
  11727. } else {
  11728. $channelName = "none";
  11729. }
  11730. $gpDef[$channel] = $channelName . ':' . $channelDef;
  11731. }
  11732. push @event, "3:remoteLinkTableGPDesc" . $direction . "$gpIdx:$gpDef[0]";
  11733. }
  11734. $remoteLastStatusReturnCode = '00';
  11735. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11736. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11737. Log3 $name, 2, "EnOcean $name RPC link table GP response received";
  11738. } elsif ($funcNumber == 0x827 && $remoteManagement eq 'manager') {
  11739. # product ID answer
  11740. $manufID = substr($data, 1, 3);
  11741. $attr{$name}{remoteManufID} = $manufID;
  11742. $manufID = $EnO_manuf{$manufID} if($EnO_manuf{$manufID});
  11743. $remoteLastStatusReturnCode = '00';
  11744. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11745. $sendData = '024007FF';
  11746. EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
  11747. push @event, "3:remoteProductID:" . substr($data, 4, 8);
  11748. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11749. Log3 $name, 2, "EnOcean $name RPC Product ID answer received ProductID: " . substr($data, 4, 8) . " Manufacturer: $manufID";
  11750. Log3 $name, 2, "EnOcean $name RPC acknowledge sent";
  11751. EnOcean_CommandSave(undef, undef);
  11752. } elsif ($funcNumber == 0x830 && $remoteManagement eq 'manager') {
  11753. # device config response
  11754. CommandDeleteReading(undef, "$name remoteDevCfg.*");
  11755. my $idx;
  11756. my $valueLen;
  11757. while (length($data) > 0) {
  11758. $data =~ m/^(....)(..)(.*)$/;
  11759. $idx = $1;
  11760. $valueLen = hex($2) * 2;
  11761. $data = $3;
  11762. $data =~ m/^(.{$valueLen})(.*)$/;
  11763. push @event, "3:remoteDevCfg$idx:S1";
  11764. $data = $2;
  11765. }
  11766. $remoteLastStatusReturnCode = '00';
  11767. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11768. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11769. Log3 $name, 2, "EnOcean $name RPC device configuration response received";
  11770. } elsif ($funcNumber == 0x832 && $remoteManagement eq 'manager') {
  11771. # link based configuration response
  11772. $data =~ m/^(..)(..)(.*)$/;
  11773. my $direction = hex($1) & 0x80 ? 'Out' : 'In';
  11774. my $linkTableIdx = $2;
  11775. CommandDeleteReading(undef, "$name remoteLinkCfg$direction$linkTableIdx.*");
  11776. $data = $3;
  11777. my $idx;
  11778. my $valueLen;
  11779. while (length($data) > 0) {
  11780. $data =~ m/^(....)(..)(.*)$/;
  11781. $idx = $1;
  11782. $valueLen = hex($2) * 2;
  11783. $data = $3;
  11784. $data =~ m/^(.{$valueLen})(.*)$/;
  11785. push @event, "3:remoteLinkCfg$direction$linkTableIdx:$idx:S1";
  11786. $data = $2;
  11787. }
  11788. $remoteLastStatusReturnCode = '00';
  11789. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11790. push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
  11791. Log3 $name, 2, "EnOcean $name RPC link table response received";
  11792. } elsif ($funcNumber == 0x850 && $remoteManagement eq 'manager') {
  11793. # query status answer
  11794. delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
  11795. my $repeaterFunction = 'off';
  11796. if (($db[0] & 0xC0) == 0) {
  11797. $repeaterFunction = 'off';
  11798. } elsif (($db[0] & 0xC0) == 0x40) {
  11799. $repeaterFunction = 'on';
  11800. } elsif (($db[0] & 0xC0) == 0x80) {
  11801. $repeaterFunction = 'filter';
  11802. }
  11803. push @event, "3:remoteRepeaterFunction:$repeaterFunction";
  11804. push @event, "3:remoteRepeaterLevel:" . (($db[0] & 0x30) == 0x20 ? 2 : 1);
  11805. push @event, "3:remoteRepeaterFilter:" . ($db[0] & 8 ? 'OR' : 'AND');
  11806. Log3 $name, 2, "EnOcean $name RPC repeater functions response received";
  11807. } else {
  11808. Log3 $name, 2, "EnOcean $name RMCC/RPC function number " . sprintf("%03X", $funcNumber) . " not supported.";
  11809. }
  11810. } elsif ($rorg eq "C5" && $packetType == 1) {
  11811. # remote management >> packetType = 7
  11812. return $name;
  11813. }
  11814. readingsBeginUpdate($hash);
  11815. for(my $i = 0; $i < int(@event); $i++) {
  11816. # Flag & 1: reading, Flag & 2: changed. Currently ignored.
  11817. my ($flag, $vn, $vv) = split(':', $event[$i], 3);
  11818. readingsBulkUpdate($hash, $vn, $vv);
  11819. my @cmdObserve = ($name, $vn, $vv);
  11820. EnOcean_observeParse(2, $hash, @cmdObserve);
  11821. }
  11822. readingsEndUpdate($hash, 1);
  11823. if (defined $deleteDevice) {
  11824. # delete device and save config
  11825. CommandDelete(undef, $deleteDevice);
  11826. Log3 $name, 2, "EnOcean $name device $deleteDevice deleted";
  11827. if (defined $oldDevice) {
  11828. Log3 $name, 2, "EnOcean $name renamed $oldDevice to $deleteDevice";
  11829. CommandRename(undef, "$oldDevice $deleteDevice");
  11830. EnOcean_CommandSave(undef, undef);
  11831. return $deleteDevice;
  11832. } else {
  11833. EnOcean_CommandSave(undef, undef);
  11834. return '';
  11835. }
  11836. }
  11837. return $name;
  11838. }
  11839. sub EnOcean_Attr(@)
  11840. {
  11841. my ($cmd, $name, $attrName, $attrVal) = @_;
  11842. my $hash = $defs{$name};
  11843. # return if attribute list is incomplete
  11844. return undef if (!$init_done);
  11845. my $err;
  11846. my $loglevel = 2;
  11847. my $waitingCmds = AttrVal($name, "waitingCmds", 0);
  11848. if ($attrName eq "angleTime") {
  11849. my $channel;
  11850. my $data;
  11851. if (!defined $attrVal) {
  11852. if (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
  11853. # no rotation
  11854. $data = "7FFF0007F5";
  11855. EnOcean_SndRadio(undef, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  11856. }
  11857. } elsif (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
  11858. my @attrVal = split(':', $attrVal);
  11859. for (my $channel = 0; $channel <= $#attrVal && $channel < 4; $channel ++) {
  11860. if ($attrVal[$channel] =~ m/^\d+(\.\d+)?$/ && $attrVal[$channel] >= 0 && $attrVal[$channel] <= 2.54) {
  11861. if ($attrVal[$channel] < 0.01) {
  11862. $attrVal[$channel] = 0;
  11863. } else {
  11864. $attrVal[$channel] = int($attrVal[$channel] * 100);
  11865. }
  11866. $data = sprintf "7FFF%02X07%02X", $attrVal[$channel], $channel << 4 | 5;
  11867. EnOcean_SndRadio(0.2 + $channel * 0.5, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  11868. } else {
  11869. $err = "attribute-value [$attrName] = $attrVal wrong";
  11870. }
  11871. }
  11872. } elsif (AttrVal($name, "subType", "") eq "manufProfile" && AttrVal($name, "manufID", "") eq "00D" &&
  11873. $attrVal =~ m/^[+-]?\d+?$/ && $attrVal >= 1 && $attrVal <= 6) {
  11874. } else {
  11875. $err = "attribute-value [$attrName] = $attrVal wrong";
  11876. #Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong";
  11877. #CommandDeleteAttr(undef, "$name $attrName");
  11878. }
  11879. } elsif ($attrName eq "alarmAction") {
  11880. my $data;
  11881. if (!defined $attrVal) {
  11882. if (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
  11883. # no alarm action
  11884. $data = "7FFFFF00F5";
  11885. EnOcean_SndRadio(undef, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  11886. }
  11887. } elsif (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
  11888. my @attrVal = split(':', $attrVal);
  11889. for (my $channel = 0; $channel <= $#attrVal && $channel < 4; $channel ++) {
  11890. if ($attrVal =~ m/no|stop|opens|closes$/) {
  11891. my $alarmAction = 0;
  11892. if ($attrVal[$channel] eq "no") {
  11893. $alarmAction = 0;
  11894. } elsif ($attrVal[$channel] eq "stop") {
  11895. $alarmAction = 1;
  11896. } elsif ($attrVal[$channel] eq "opens") {
  11897. $alarmAction = 2;
  11898. } elsif ($attrVal[$channel] eq "closes") {
  11899. $alarmAction = 3;
  11900. } else {
  11901. $err = "attribute-value [$attrName] = $attrVal wrong";
  11902. }
  11903. $data = sprintf "7FFFFF%02X%02X", $alarmAction, $channel << 4 | 5;
  11904. EnOcean_SndRadio(0.2 + $channel * 0.5, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  11905. } else {
  11906. $err = "attribute-value [$attrName] = $attrVal wrong";
  11907. }
  11908. }
  11909. } else {
  11910. $err = "attribute-value [$attrName] = $attrVal wrong";
  11911. }
  11912. } elsif ($attrName =~ m/^block.*/) {
  11913. if (!defined $attrVal){
  11914. } elsif ($attrVal =~ m/^(no|yes)$/) {
  11915. if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
  11916. $waitingCmds |= 64;
  11917. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
  11918. }
  11919. } else {
  11920. $err = "attribute-value [$attrName] = $attrVal wrong";
  11921. }
  11922. } elsif ($attrName eq "comMode") {
  11923. if (!defined $attrVal){
  11924. } elsif ($attrVal !~ m/^biDir|uniDir|confirm$/) {
  11925. $err = "attribute-value [$attrName] = $attrVal wrong";
  11926. }
  11927. } elsif ($attrName eq "creator") {
  11928. if (!defined $attrVal){
  11929. } elsif ($attrVal !~ m/^autocreate|manual$/) {
  11930. $err = "attribute-value [$attrName] = $attrVal wrong";
  11931. }
  11932. } elsif ($attrName eq "dataEnc") {
  11933. if (!defined $attrVal){
  11934. } elsif ($attrVal !~ m/^VAES|AES-CBC$/) {
  11935. $err = "attribute-value [$attrName] = $attrVal wrong";
  11936. }
  11937. } elsif ($attrName eq "daylightSavingTime") {
  11938. if (!defined $attrVal){
  11939. } elsif ($attrVal =~ m/^(supported|not_supported)$/) {
  11940. if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
  11941. $waitingCmds |= 64;
  11942. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
  11943. }
  11944. } else {
  11945. $err = "attribute-value [$attrName] = $attrVal wrong";
  11946. }
  11947. } elsif ($attrName eq "defaultChannel") {
  11948. my $defaultChannel = '';
  11949. my $subType = AttrVal($name, "subType", "");
  11950. if ($subType eq "actuator.01") {
  11951. $defaultChannel = join("|", @EnO_defaultChannel);
  11952. } elsif ($subType =~ m/^blindsCtrl\.0[01]$/) {
  11953. $defaultChannel = '[1234]|all';
  11954. }
  11955. if (!defined $attrVal){
  11956. } elsif ($attrVal !~ m/^($defaultChannel)$/) {
  11957. $err = "attribute-value [$attrName] = $attrVal wrong";
  11958. }
  11959. } elsif ($attrName eq "demandRespAction") {
  11960. if (!defined $attrVal){
  11961. } else {
  11962. my %specials = ("%TARGETNAME" => $name,
  11963. "%NAME" => $name,
  11964. "%TARGETTYPE" => '',
  11965. "%TYPE" => '',
  11966. "%LEVEL" => '',
  11967. "%SETPOINT" => '',
  11968. "%POWERUSAGE" => '',
  11969. "%POWERUSAGESCALE" => '',
  11970. "%POWERUSAGELEVEL" => '',
  11971. "%TARGETSTATE" => '',
  11972. "%STATE" => ''
  11973. );
  11974. $err = perlSyntaxCheck($attrVal, %specials);
  11975. }
  11976. } elsif ($attrName eq "demandRespRandomTime") {
  11977. if (!defined $attrVal) {
  11978. } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 1) {
  11979. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  11980. }
  11981. } elsif ($attrName =~ m/^(demandRespMax|demandRespMin)$/) {
  11982. if (!defined $attrVal){
  11983. } elsif ($attrVal !~ m/^(A0|AI|B0|BI|C0|CI|D0|DI)$/) {
  11984. $err = "attribute-value [$attrName] = $attrVal wrong";
  11985. CommandDeleteAttr(undef, "$name $attrName");
  11986. }
  11987. } elsif ($attrName eq "demandRespThreshold") {
  11988. if (!defined $attrVal) {
  11989. } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 0 || $attrVal > 15) {
  11990. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  11991. }
  11992. } elsif ($attrName eq "devChannel") {
  11993. if (!defined $attrVal){
  11994. } elsif ($attrVal =~ m/^[\dA-Fa-f]{2}$/) {
  11995. # actions see EnOcean_Notify, global ATTR
  11996. } elsif ($attrVal =~ m/^\d+$/ && $attrVal >= 0 && $attrVal <= 255) {
  11997. } else {
  11998. $err = "attribute-value [$attrName] = $attrVal wrong";
  11999. }
  12000. } elsif ($attrName eq "devMode") {
  12001. if (!defined $attrVal){
  12002. } elsif ($attrVal !~ m/^master|slave$/) {
  12003. $err = "attribute-value [$attrName] = $attrVal wrong";
  12004. }
  12005. } elsif ($attrName eq "devUpdate") {
  12006. if (!defined $attrVal){
  12007. } elsif ($attrVal !~ m/^(off|auto|demand|polling|interrupt)$/) {
  12008. $err = "attribute-value [$attrName] = $attrVal wrong";
  12009. }
  12010. } elsif ($attrName =~ m/^dimMax|dimMin$/) {
  12011. if (!defined $attrVal){
  12012. } elsif ($attrVal !~ m/^off|[\d+]$/) {
  12013. $err = "attribute-value [$attrName] = $attrVal wrong";
  12014. }
  12015. } elsif ($attrName eq "displayContent") {
  12016. if (!defined $attrVal){
  12017. } elsif ($attrVal =~ m/^(humidity|off|setpointTemp|tempertureExtern|temperatureIntern|time|default|no_change)$/) {
  12018. if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
  12019. $waitingCmds |= 64;
  12020. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
  12021. }
  12022. } else {
  12023. $err = "attribute-value [$attrName] = $attrVal wrong";
  12024. }
  12025. } elsif ($attrName eq "displayOrientation") {
  12026. if (!defined $attrVal){
  12027. } elsif ($attrVal !~ m/^0|90|180|270$/) {
  12028. $err = "attribute-value [$attrName] = $attrVal wrong";
  12029. }
  12030. } elsif ($attrName eq "eep" || $attrName eq "remoteEEP") {
  12031. if (!defined $attrVal){
  12032. } elsif ($attrVal !~ m/^[\dA-Fa-f]{2}-[\dA-Fa-f]{2}-[\dA-Fa-f]{2}$/) {
  12033. $err = "attribute-value [$attrName] = $attrVal wrong";
  12034. }
  12035. } elsif ($attrName eq "model") {
  12036. if (!defined $attrVal){
  12037. } else {
  12038. # set model specific attributes
  12039. foreach my $attrCntr (keys %{$EnO_models{$attrVal}{attr}}) {
  12040. if ($attrCntr eq "remoteID") {
  12041. if (exists $hash->{DEF}) {
  12042. $attr{$name}{$attrCntr} = $hash->{DEF};
  12043. } else {
  12044. $attr{$name}{$attrCntr} = EnOcean_CheckSenderID($EnO_models{$attrVal}{attr}{$attrCntr}, $hash->{IODev}{NAME}, "00000000");
  12045. }
  12046. } else {
  12047. $attr{$name}{$attrCntr} = $EnO_models{$attrVal}{attr}{$attrCntr};
  12048. }
  12049. }
  12050. if (exists $EnO_models{$attrVal}{xml}) {
  12051. # read xml device description to $hash->{helper}
  12052. if ($xmlFunc == 1) {
  12053. my $xmlFile = $attr{global}{modpath} . $EnO_models{$attrVal}{xml}{xmlDescrLocation};
  12054. if (-e -f -r $xmlFile) {
  12055. $hash->{helper} = $xml->XMLin($xmlFile);
  12056. if (exists $hash->{helper}{Device}) {
  12057. } else {
  12058. Log3 $name, 2, "EnOcean $name <attr> Device Description not defined";
  12059. }
  12060. } else {
  12061. Log3 $name, 2, "EnOcean $name <attr> Device Description file $xmlFile not exists";
  12062. }
  12063. } else {
  12064. Log3 $name, 2, "EnOcean $name <attr> XML functions are not available";
  12065. }
  12066. }
  12067. }
  12068. } elsif ($attrName eq "gpDef") {
  12069. if (!defined $attrVal){
  12070. } else {
  12071. my @gpDef = split("[ \t][ \t]*", $attrVal);
  12072. my ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax);
  12073. for (my $channel = 0; $channel < @gpDef; $channel ++) {
  12074. my @err;
  12075. ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
  12076. split(':', $gpDef[$channel]);
  12077. push(@err, "channelName") if (!defined $channelName);
  12078. push(@err, "channelDir") if (!defined($channelDir) || $channelDir !~ m/^O|I$/);
  12079. push(@err, "channelType") if (!defined($channelType) || $channelType !~ m/^\d+$/ || $channelType > 3);
  12080. push(@err, "signalType") if (!defined($signalType) || $signalType !~ m/^\d+$/ || $signalType > 255);
  12081. push(@err, "valueType") if (!defined($valueType) || $valueType !~ m/^\d+$/ || $valueType > 3);
  12082. if ($channelType == 1 || $channelType == 3) {
  12083. push(@err, "resolution") if (!defined($resolution) || $resolution !~ m/^\d+$/ || $resolution > 12);
  12084. }
  12085. if ($channelType == 1) {
  12086. push(@err, "engMin") if (!defined($engMin) || $engMin !~ m/^[+-]?\d+$/ || $engMin < -128 || $engMin > 127);
  12087. push(@err, "scalingMin") if (!defined($scalingMin) || $scalingMin !~ m/^\d+$/ || $scalingMin < 1 || $scalingMin > 13);
  12088. push(@err, "engMax") if (!defined($engMax) || $engMax !~ m/^[+-]?\d+$/ || $engMax < -128 || $engMax > 127);
  12089. push(@err, "scalingMax") if (!defined($scalingMax) || $scalingMax !~ m/^\d+$/ || $scalingMax < 1 || $scalingMax > 13);
  12090. }
  12091. $err = "attribute-value $attrName/channel " . sprintf('%02d', $channel) . ": " .
  12092. join(', ', @err) . " wrong" if (defined $err[0]);
  12093. }
  12094. }
  12095. } elsif ($attrName eq "humidity") {
  12096. if (!defined $attrVal) {
  12097. } elsif ($attrVal =~ m/^\d+$/ && $attrVal >= 0 && $attrVal <= 100) {
  12098. } else {
  12099. #RemoveInternalTimer($hash);
  12100. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  12101. }
  12102. } elsif ($attrName =~ m/^key/) {
  12103. if (!defined $attrVal){
  12104. } elsif ($attrVal !~ m/^[\dA-Fa-f]{32}$/) {
  12105. $err = "attribute-value [$attrName] = $attrVal wrong";
  12106. }
  12107. } elsif ($attrName eq "macAlgo") {
  12108. if (!defined $attrVal){
  12109. } elsif ($attrVal !~ m/^no|[3-4]$/) {
  12110. $err = "attribute-value [$attrName] = $attrVal wrong";
  12111. }
  12112. } elsif ($attrName eq "manufID" || $attrName eq "remoteManufID") {
  12113. if (!defined $attrVal){
  12114. } elsif ($attrVal !~ m/^[0-7][\dA-Fa-f]{2}$/) {
  12115. $err = "attribute-value [$attrName] = $attrVal wrong";
  12116. }
  12117. } elsif ($attrName eq "measurementCtrl") {
  12118. if (!defined $attrVal){
  12119. } elsif ($attrVal !~ m/^disable|enable$/) {
  12120. $err = "attribute-value [$attrName] = $attrVal wrong";
  12121. }
  12122. } elsif ($attrName eq "observe") {
  12123. if (!defined $attrVal){
  12124. } elsif (lc($attrVal) !~ m/^(off|on)$/) {
  12125. $err = "attribute-value [$attrName] = $attrVal wrong";
  12126. }
  12127. } elsif ($attrName eq "observeCmdRepetition") {
  12128. if (!defined $attrVal){
  12129. } elsif ($attrVal !~ m/^[1-5]$/) {
  12130. $err = "attribute-value [$attrName] = $attrVal wrong";
  12131. }
  12132. } elsif ($attrName eq "observeErrorAction") {
  12133. if (!defined $attrVal){
  12134. } else {
  12135. my %specials = ("%NAME" => $name,
  12136. "%FAILEDDEV" => '',
  12137. "%TYPE" => '',
  12138. "%EVENT" => ''
  12139. );
  12140. $err = perlSyntaxCheck($attrVal, %specials);
  12141. }
  12142. } elsif ($attrName eq "observeInterval") {
  12143. if (!defined $attrVal){
  12144. } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 1) {
  12145. $err = "attribute-value [$attrName] = $attrVal wrong";
  12146. }
  12147. } elsif ($attrName eq "observeLogic") {
  12148. if (!defined $attrVal){
  12149. } elsif (lc($attrVal) !~ m/^and|or$/) {
  12150. $err = "attribute-value [$attrName] = $attrVal wrong";
  12151. }
  12152. } elsif ($attrName eq "pidActorErrorAction") {
  12153. if (!defined $attrVal){
  12154. } elsif ($attrVal !~ m/^errorPos|freeze$/) {
  12155. $err = "attribute-value [$attrName] = $attrVal wrong";
  12156. }
  12157. } elsif ($attrName eq "pidCtrl") {
  12158. if (!defined $attrVal){
  12159. } elsif (lc($attrVal) eq "on") {
  12160. EnOcean_setPID(undef, $hash, 'start', ReadingsVal($name, "setpoint", ''));
  12161. } elsif (lc($attrVal) eq "off") {
  12162. EnOcean_setPID(undef, $hash, 'stop', '');
  12163. } else {
  12164. $err = "attribute-value [$attrName] = $attrVal wrong";
  12165. }
  12166. } elsif ($attrName eq "pidActorErrorPos") {
  12167. if (!defined $attrVal) {
  12168. } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 0 || $attrVal > 100) {
  12169. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  12170. }
  12171. } elsif ($attrName eq "pidDeltaTreshold") {
  12172. my $reFloatpos = '^([\\+]?\\d+\\.?\d*$)';
  12173. if (!defined $attrVal) {
  12174. } elsif ($attrVal !~ m/$reFloatpos/) {
  12175. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  12176. }
  12177. } elsif ($attrName eq "pidSensorTimeout") {
  12178. if (!defined $attrVal) {
  12179. } elsif ($attrVal !~ m/^\d+?$/) {
  12180. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  12181. }
  12182. } elsif ($attrName =~ m/^pidActorLimitLower|pidActorLimitUpper$/) {
  12183. if (!defined $attrVal) {
  12184. } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 0 || $attrVal > 100) {
  12185. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  12186. }
  12187. } elsif ($attrName =~ m/^pidFactor_.$/) {
  12188. my $reFloatpos = '^([\\+]?\\d+\\.?\d*$)';
  12189. if (!defined $attrVal) {
  12190. } elsif ($attrVal !~ m/$reFloatpos/) {
  12191. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  12192. }
  12193. } elsif ($attrName eq "pollInterval") {
  12194. if (!defined $attrVal) {
  12195. } elsif ($attrVal =~ m/^\d+?$/) {
  12196. if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
  12197. $waitingCmds |= 64;
  12198. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
  12199. }
  12200. } else {
  12201. #RemoveInternalTimer($hash);
  12202. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  12203. }
  12204. } elsif ($attrName eq "productID") {
  12205. if (!defined $attrVal){
  12206. } elsif ($attrVal !~ m/^[\dA-Fa-f]{8}$/) {
  12207. $err = "attribute-value [$attrName] = $attrVal wrong";
  12208. }
  12209. } elsif ($attrName eq "rampTime") {
  12210. if (!defined $attrVal){
  12211. } elsif ($attrVal =~ m/^\d+?$/) {
  12212. if (AttrVal($name, "subType", "") eq "gateway") {
  12213. if ($attrVal < 0 || $attrVal > 255) {
  12214. $err = "attribute-value [$attrName] = $attrVal wrong";
  12215. }
  12216. } elsif (AttrVal($name, "subType", "") eq "lightCtrl.01") {
  12217. if ($attrVal < 0 || $attrVal > 65535) {
  12218. $err = "attribute-value [$attrName] = $attrVal wrong";
  12219. }
  12220. } else {
  12221. $err = "attribute-value [$attrName] = $attrVal wrong";
  12222. }
  12223. } else {
  12224. $err = "attribute $attrName not supported for subType " . AttrVal($name, "subType", "");
  12225. }
  12226. } elsif ($attrName eq "releasedChannel") {
  12227. if (!defined $attrVal){
  12228. } elsif ($attrVal !~ m/^A|B|C|D|I|0|auto$/) {
  12229. $err = "attribute-value [$attrName] = $attrVal wrong";
  12230. }
  12231. } elsif ($attrName eq "rcvRespAction") {
  12232. if (!defined $attrVal){
  12233. } elsif (AttrVal($name, "subType", "") eq "hvac.01") {
  12234. my %specials = ("%ACTUATORSTATE" => '',
  12235. "%BATTERY" => '',
  12236. "%COVER" => '',
  12237. "%ENERGYINPUT" => '',
  12238. "%ENERGYSTORAGE" => '',
  12239. "%MAINTENANCEMODE" => '',
  12240. "%NAME" => $name,
  12241. "%OPERATIONMODE" => '',
  12242. "%ROOMTEMP" => '',
  12243. "%SELFCTRL" => '',
  12244. "%SETPOINT" => '',
  12245. "%SETPOINTTEMP" => '',
  12246. "%SUMMERMODE" => '',
  12247. "%TEMPERATURE" => '',
  12248. "%WINDOW" => ''
  12249. );
  12250. $err = perlSyntaxCheck($attrVal, %specials);
  12251. } elsif (AttrVal($name, "subType", "") eq "hvac.04") {
  12252. my %specials = ("%BATTERY" => '',
  12253. "%FEEDTEMP" => '',
  12254. "%MAINTENANCEMODE" => '',
  12255. "%NAME" => $name,
  12256. "%OPERATIONMODE" => '',
  12257. "%ROOMTEMP" => '',
  12258. "%SETPOINT" => '',
  12259. "%SETPOINTTEMP" => '',
  12260. "%SUMMERMODE" => '',
  12261. "%TEMPERATURE" => ''
  12262. );
  12263. $err = perlSyntaxCheck($attrVal, %specials);
  12264. }
  12265. } elsif ($attrName eq "remoteID") {
  12266. if (!defined $attrVal){
  12267. # delete old pointer
  12268. delete $modules{EnOcean}{defptr}{$attr{$name}{$attrName}} if (exists($attr{$name}{$attrName}) && $attr{$name}{$attrName} ne $hash->{DEF});
  12269. } elsif ($attrVal =~ m/^[\dA-F]{8}$/) {
  12270. # delete old pointer
  12271. delete $modules{EnOcean}{defptr}{$attr{$name}{$attrName}} if (exists($attr{$name}{$attrName}) && $attr{$name}{$attrName} ne $hash->{DEF});
  12272. $modules{EnOcean}{defptr}{$attrVal} = $hash;
  12273. } else {
  12274. $err = "attribute-value [$attrName] = $attrVal wrong";
  12275. }
  12276. } elsif ($attrName eq "remoteManagement") {
  12277. if (!defined $attrVal){
  12278. } elsif ($attrVal !~ m/^client|manager|off$/) {
  12279. $err = "attribute-value [$attrName] = $attrVal wrong";
  12280. }
  12281. } elsif ($attrName eq "reposition") {
  12282. if (!defined $attrVal){
  12283. } elsif ($attrVal !~ m/^directly|opens|closes$/) {
  12284. $err = "attribute-value [$attrName] = $attrVal wrong";
  12285. }
  12286. } elsif ($attrName =~ m/^rlcRcv|rlcSnd$/) {
  12287. if (!defined $attrVal){
  12288. } elsif (AttrVal($name, "rlcAlgo", "") eq "2++" && $attrVal =~ m/^[\dA-Fa-f]{4}$/) {
  12289. } elsif (AttrVal($name, "rlcAlgo", "") eq "3++" && $attrVal =~ m/^[\dA-Fa-f]{6}$/) {
  12290. } else {
  12291. $err = "attribute-value [$attrName] = $attrVal wrong";
  12292. }
  12293. } elsif ($attrName eq "rlcAlgo") {
  12294. if (!defined $attrVal){
  12295. } elsif ($attrVal !~ m/^no|2++|3++$/) {
  12296. $err = "attribute-value [$attrName] = $attrVal wrong";
  12297. }
  12298. } elsif ($attrName eq "rlcTX") {
  12299. if (!defined $attrVal){
  12300. } elsif (lc($attrVal) !~ m/^true|false$/) {
  12301. $err = "attribute-value [$attrName] = $attrVal wrong";
  12302. }
  12303. } elsif ($attrName eq "rltType") {
  12304. if (!defined $attrVal){
  12305. } elsif ($attrVal !~ m/^1BS|4BS$/) {
  12306. $err = "attribute-value [$attrName] = $attrVal wrong";
  12307. }
  12308. } elsif ($attrName eq "rltRepeat") {
  12309. if (!defined $attrVal){
  12310. } elsif ($attrVal =~ m/^\d+?$/ && $attrVal >= 16 && $attrVal <= 256) {
  12311. } else {
  12312. $err = "attribute-value [$attrName] = $attrVal wrong";
  12313. }
  12314. } elsif ($attrName eq "remoteCode") {
  12315. if (!defined $attrVal){
  12316. } elsif ($attrVal !~ m/^[\dA-Fa-f]{8}$/ || $attrVal eq "00000000" || uc($attrVal) eq "FFFFFFFF") {
  12317. $err = "attribute-value [$attrName] = $attrVal wrong";
  12318. }
  12319. } elsif ($attrName eq "secLevel") {
  12320. if (!defined $attrVal){
  12321. } elsif ($attrVal !~ m/^encapsulation|encryption|off$/) {
  12322. $err = "attribute-value [$attrName] = $attrVal wrong";
  12323. }
  12324. } elsif ($attrName eq "secMode") {
  12325. if (!defined $attrVal){
  12326. } elsif ($attrVal !~ m/^rcv|snd|bidir$/) {
  12327. $err = "attribute-value [$attrName] = $attrVal wrong";
  12328. }
  12329. } elsif ($attrName eq "sendDevStatus") {
  12330. if (!defined $attrVal){
  12331. } elsif ($attrVal !~ m/^no|yes$/) {
  12332. $err = "attribute-value [$attrName] = $attrVal wrong";
  12333. }
  12334. } elsif ($attrName eq "serviceOn") {
  12335. if (!defined $attrVal){
  12336. } elsif ($attrVal !~ m/^(no|yes)$/) {
  12337. $err = "attribute-value [$attrName] = $attrVal wrong";
  12338. }
  12339. } elsif ($attrName eq "setCmdTrigger") {
  12340. if (!defined $attrVal){
  12341. } elsif ($attrVal !~ m/^man|refDev$/) {
  12342. $err = "attribute-value [$attrName] = $attrVal wrong";
  12343. }
  12344. } elsif ($attrName eq "setpointSummerMode") {
  12345. if (!defined $attrVal){
  12346. } elsif ($attrVal !~ m/^\d+$/ && $attrVal >= 0 && $attrVal <= 100) {
  12347. $err = "attribute-value [$attrName] = $attrVal wrong";
  12348. }
  12349. } elsif ($attrName eq "settingAccuracy") {
  12350. if (!defined $attrVal){
  12351. } elsif ($attrVal !~ m/^high|low$/) {
  12352. $err = "attribute-value [$attrName] = $attrVal wrong";
  12353. }
  12354. } elsif ($attrName eq "shutTime") {
  12355. my $data;
  12356. if (!defined $attrVal) {
  12357. if (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
  12358. # set shutTime to max
  12359. $data = "7530FF07F5";
  12360. EnOcean_SndRadio(undef, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  12361. }
  12362. } elsif (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
  12363. my @attrVal = split(':', $attrVal);
  12364. for (my $channel = 0; $channel <= $#attrVal && $channel < 4; $channel ++) {
  12365. if ($attrVal[$channel] =~ m/^\d+$/ && $attrVal[$channel] >= 5 && $attrVal[$channel] <= 300) {
  12366. $attrVal[$channel] = int($attrVal[$channel] * 100);
  12367. $data = sprintf "%04XFF07%02X", $attrVal[$channel], $channel << 4 | 5;
  12368. EnOcean_SndRadio(0.2 + $channel * 0.5, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  12369. } else {
  12370. $err = "attribute-value [$attrName] = $attrVal wrong";
  12371. }
  12372. }
  12373. } elsif (AttrVal($name, "subType", "") eq "manufProfile" && AttrVal($name, "manufID", "") eq "00D" &&
  12374. $attrVal =~ m/^[+-]?\d+$/ && $attrVal >= 1 && $attrVal <= 255) {
  12375. } else {
  12376. $err = "attribute-value [$attrName] = $attrVal wrong";
  12377. }
  12378. } elsif ($attrName eq "signal") {
  12379. if (!defined $attrVal){
  12380. } elsif ($attrVal !~ m/^off|on$/) {
  12381. $err = "attribute-value [$attrName] = $attrVal wrong";
  12382. }
  12383. } elsif ($attrName eq "signOfLife") {
  12384. if (!defined $attrVal) {
  12385. } elsif ($attrVal !~ m/^off|on$/) {
  12386. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  12387. }
  12388. } elsif ($attrName eq "signOfLifeInterval") {
  12389. if (!defined $attrVal) {
  12390. } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 1 || $attrVal > 65535) {
  12391. $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
  12392. }
  12393. } elsif ($attrName eq "summerMode") {
  12394. if (!defined $attrVal){
  12395. } elsif ($attrVal eq 'on') {
  12396. if (AttrVal($name, 'subType', '') =~ m/^hvac\.0(1|4)$/ && AttrVal($name, 'summerMode', 'off') eq 'off') {
  12397. readingsBeginUpdate($hash);
  12398. readingsBulkUpdate($hash, 'waitingCmds', 'summerMode');
  12399. readingsBulkUpdate($hash, 'operationModeRestore', ReadingsVal($name, 'operationMode', 'setpoint'));
  12400. readingsBulkUpdate($hash, 'setpointTempRestore', ReadingsVal($name, 'setpointTemp', 20));
  12401. readingsBulkUpdate($hash, 'operationMode', 'setpoint');
  12402. readingsEndUpdate($hash, 0);
  12403. } else {
  12404. # attr not changed
  12405. }
  12406. } elsif ($attrVal eq 'off') {
  12407. if (AttrVal($name, 'subType', '') =~ m/^hvac\.0(1|4)$/ && AttrVal($name, 'summerMode', 'off') eq 'on') {
  12408. readingsBeginUpdate($hash);
  12409. readingsBulkUpdate($hash, 'waitingCmds', 'runInit');
  12410. readingsBulkUpdate($hash, 'operationMode', ReadingsVal($name, 'operationModeRestore', 'setpoint'));
  12411. readingsBulkUpdate($hash, 'setpointTemp', ReadingsVal($name, 'setpointTempRestore', 20));
  12412. readingsEndUpdate($hash, 0);
  12413. CommandDeleteReading(undef, "$name .*Restore");
  12414. } else {
  12415. # attr not changed
  12416. }
  12417. } else {
  12418. $err = "attribute-value [$attrName] = $attrVal wrong";
  12419. }
  12420. } elsif ($attrName =~ m/^subDef.?|postmasterID/) {
  12421. if (!defined $attrVal){
  12422. } elsif ($attrVal eq "getNextID") {
  12423. # actions see EnOcean_Notify, global ATTR
  12424. } elsif ($attrVal !~ m/^[\dA-Fa-f]{8}$/) {
  12425. $err = "attribute-value [$attrName] = $attrVal wrong";
  12426. }
  12427. } elsif ($attrName eq "switchHysteresis") {
  12428. if (!defined $attrVal) {
  12429. } elsif ($attrVal =~ m/^\d+(\.\d+)?$/ && $attrVal >= 0.1) {
  12430. } else {
  12431. #RemoveInternalTimer($hash);
  12432. $err = "attribute-value [$attrName] = $attrVal is not a valid number";
  12433. }
  12434. } elsif ($attrName eq "teachMethod") {
  12435. if (!defined $attrVal){
  12436. } elsif ($attrVal !~ m/^1BS|4BS|confirm|GP|RPS|smartAck|STE|UTE$$/) {
  12437. $err = "attribute-value [$attrName] = $attrVal wrong";
  12438. }
  12439. } elsif ($attrName eq "temperatureScale") {
  12440. if (!defined $attrVal){
  12441. } elsif ($attrVal =~ m/^(C|F|default|no_change)$/) {
  12442. if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
  12443. $waitingCmds |= 64;
  12444. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
  12445. }
  12446. } else {
  12447. $err = "attribute-value [$attrName] = $attrVal wrong";
  12448. }
  12449. } elsif ($attrName eq "timeNotation") {
  12450. if (!defined $attrVal){
  12451. } elsif ($attrVal =~ m/^(12|24|default|no_change)$/) {
  12452. if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
  12453. $waitingCmds |= 64;
  12454. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
  12455. }
  12456. } else {
  12457. $err = "attribute-value [$attrName] = $attrVal wrong";
  12458. }
  12459. } elsif ($attrName =~ m/^timeProgram[1-4]$/) {
  12460. if (!defined $attrVal){
  12461. } elsif ($attrVal =~ m/^(FrMo|FrSu|ThFr|WeFr|TuTh|MoWe|SaSu|MoFr|MoSu|Su|Sa|Fr|Th|We|Tu|Mo)\s+(\d*?):(00|15|30|45)\s+(\d*?):(00|15|30|45)\s+(comfort|economy|preComfort|buildingProtection)$/) {
  12462. if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
  12463. # delete remote and send new time program
  12464. delete $hash->{helper}{4}{telegramWait};
  12465. $hash->{helper}{4}{telegramWait}{substr($attrName,-1,1) + 0} = 1;
  12466. for (my $messagePartCntr = 1; $messagePartCntr <= 4; $messagePartCntr ++) {
  12467. if (defined AttrVal($name, "timeProgram" . $messagePartCntr, undef)) {
  12468. $hash->{helper}{4}{telegramWait}{$messagePartCntr} = 1;
  12469. }
  12470. }
  12471. $waitingCmds |= 528;
  12472. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
  12473. }
  12474. } else {
  12475. $err = "attribute-value [$attrName] = $attrVal wrong";
  12476. }
  12477. } elsif ($attrName eq "trackerWakeUpCycle") {
  12478. if (!defined $attrVal){
  12479. } elsif ($attrVal !~ m/^10|20|30|40|60|120|180|240|3600|86400$/) {
  12480. $err = "attribute-value [$attrName] = $attrVal wrong";
  12481. }
  12482. } elsif ($attrName eq "updateState") {
  12483. if (!defined $attrVal){
  12484. } elsif ($attrVal !~ m/^default|yes|no$/) {
  12485. $err = "attribute-value [$attrName] = $attrVal wrong";
  12486. }
  12487. } elsif ($attrName eq "uteResponseRequest") {
  12488. if (!defined $attrVal){
  12489. } elsif ($attrVal !~ m/^(yes|no)$/) {
  12490. $err = "attribute-value [$attrName] = $attrVal wrong";
  12491. }
  12492. } elsif ($attrName eq "wakeUpCycle") {
  12493. my $wakeUpCycle = join("|", keys %wakeUpCycle);
  12494. if (!defined $attrVal){
  12495. } elsif ($attrVal =~ m/^$wakeUpCycle$/) {
  12496. if ($attrVal >= 1500 && AttrVal($name, 'wakeUpCycle', 300) < 1500) {
  12497. # switch to summer mode
  12498. $attr{$name}{summerMode} = 'on';
  12499. readingsSingleUpdate($hash, 'waitingCmds', 'summerMode', 0);
  12500. } elsif ($attrVal < 1500 && AttrVal($name, 'wakeUpCycle', 300) >= 1500) {
  12501. # runInit necessary before switch to operation mode
  12502. $attr{$name}{summerMode} = 'off';
  12503. readingsSingleUpdate($hash, 'waitingCmds', 'runInit', 0);
  12504. }
  12505. } else {
  12506. $err = "attribute-value [$attrName] = $attrVal wrong";
  12507. }
  12508. }
  12509. return $err;
  12510. }
  12511. sub EnOcean_Notify(@)
  12512. {
  12513. my ($hash, $dev) = @_;
  12514. my $name = $hash->{NAME};
  12515. my $devName = $dev->{NAME};
  12516. return undef if (AttrVal($name ,"disable", 0) > 0);
  12517. my $max = int(@{$dev->{CHANGED}});
  12518. for (my $i = 0; $i < $max; $i++) {
  12519. my $s = $dev->{CHANGED}[$i];
  12520. $s = "" if(!defined($s));
  12521. if ($devName eq $name) {
  12522. my @parts = split(/: | /, $s);
  12523. #####
  12524. if (exists($hash->{helper}{stopped}) && !$hash->{helper}{stopped} && $parts[0] eq "temperature") {
  12525. # PID regulator: calc gradient for delta as base for d-portion calculation
  12526. my $setpointTemp = ReadingsVal($name, "setpointTemp", undef);
  12527. my $temperature = $parts[1];
  12528. # ---- build difference current - old value
  12529. # calc difference of delta/deltaOld
  12530. my $delta = $setpointTemp - $temperature if (defined($setpointTemp));
  12531. my $deltaOld = ($hash->{helper}{deltaOld} + 0) if (defined($hash->{helper}{deltaOld}));
  12532. my $deltaDiff = ($delta - $deltaOld) if (defined($delta) && defined($deltaOld));
  12533. # ----- build difference of timestamps
  12534. my $deltaOldTsStr = $hash->{helper}{deltaOldTS};
  12535. my $deltaOldTsNum = time_str2num($deltaOldTsStr) if (defined($deltaOldTsStr));
  12536. my $nowTsNum = gettimeofday();
  12537. my $tsDiff = ($nowTsNum - $deltaOldTsNum)
  12538. if (defined($deltaOldTsNum) && (($nowTsNum - $deltaOldTsNum) > 0));
  12539. # ----- calculate gradient of delta
  12540. my $deltaGradient = $deltaDiff / $tsDiff
  12541. if (defined($deltaDiff) && defined($tsDiff) && ($tsDiff > 0 ));
  12542. $deltaGradient = 0 if ( !defined($deltaGradient) );
  12543. # ----- store results
  12544. $hash->{helper}{deltaGradient} = $deltaGradient;
  12545. $hash->{helper}{deltaOld} = $delta;
  12546. $hash->{helper}{deltaOldTS} = TimeNow();
  12547. Log3 $name, 5, "EnOcean $name <notify> $devName $s";
  12548. }
  12549. } elsif ($devName eq "global" && $s =~ m/^RENAMED ([^ ]*) ([^ ]*)$/) {
  12550. if (defined AttrVal($name, "temperatureRefDev", undef)) {
  12551. if (AttrVal($name, "temperatureRefDev", undef) eq $1) {
  12552. CommandAttr(undef, "$name temperatureRefDev $2");
  12553. }
  12554. } elsif (defined AttrVal($name, "setpointRefDev", undef)) {
  12555. if (AttrVal($name, "setpointRefDev", undef) eq $1) {
  12556. CommandAttr(undef, "$name setpointRefDev $2");
  12557. }
  12558. } elsif (defined AttrVal($name, "setpointTempRefDev", undef)) {
  12559. if (AttrVal($name, "setpointTempRefDev", undef) eq $1) {
  12560. CommandAttr(undef, "$name setpointTempRefDev $2");
  12561. }
  12562. } elsif (defined AttrVal($name, "humidityRefDev", undef)) {
  12563. if (AttrVal($name, "humidityRefDev", undef) eq $1) {
  12564. CommandAttr(undef, "$name humidityRefDev $2");
  12565. }
  12566. } elsif (defined AttrVal($name, "observeRefDev", undef)) {
  12567. if (AttrVal($name, "observeRefDev", undef) eq $1) {
  12568. CommandAttr(undef, "$name observeRefDev $2");
  12569. }
  12570. } elsif (defined AttrVal($name, "demandRespRefDev", undef)) {
  12571. if (AttrVal($name, "demandRespRefDev", undef) eq $1) {
  12572. CommandAttr(undef, "$name demandRespRefDev $2");
  12573. }
  12574. }
  12575. #Log3($name, 5, "EnOcean $name <notify> RENAMED old: $1 new: $2");
  12576. } elsif ($devName eq "global" && $s =~ m/^DELETED ([^ ]*)$/) {
  12577. # delete attribute *RefDev
  12578. if (defined AttrVal($name, "temperatureRefDev", undef)) {
  12579. if (AttrVal($name, "temperatureRefDev", undef) eq $1) {
  12580. CommandDeleteAttr(undef, "$name temperatureRefDev");
  12581. }
  12582. } elsif (defined AttrVal($name, "setpointRefDev", undef)) {
  12583. if (AttrVal($name, "setpointRefDev", undef) eq $1) {
  12584. CommandDeleteAttr(undef, "$name setpointRefDev");
  12585. }
  12586. } elsif (defined AttrVal($name, "setpointTempRefDev", undef)) {
  12587. if (AttrVal($name, "setpointTempRefDev", undef) eq $1) {
  12588. CommandDeleteAttr(undef, "$name setpointTempRefDev");
  12589. }
  12590. } elsif (defined AttrVal($name, "humidityRefDev", undef)) {
  12591. if (AttrVal($name, "humidityRefDev", undef) eq $1) {
  12592. CommandDeleteAttr(undef, "$name humidityRefDev");
  12593. }
  12594. } elsif (defined AttrVal($name, "observeRefDev", undef)) {
  12595. if (AttrVal($name, "observeRefDev", undef) eq $1) {
  12596. CommandDeleteAttr(undef, "$name observeRefDev");
  12597. }
  12598. } elsif (defined AttrVal($name, "demandRespRefDev", undef)) {
  12599. if (AttrVal($name, "demandRespRefDev", undef) eq $1) {
  12600. CommandDeleteAttr(undef, "$name demandRespRefDev");
  12601. }
  12602. }
  12603. #Log3($name, 5, "EnOcean $name <notify> DELETED $1");
  12604. } elsif ($devName eq "global" && $s =~ m/^DEFINED ([^ ]*)$/) {
  12605. my $definedName = $1;
  12606. if ($name eq $definedName) {
  12607. if (exists($attr{$name}{subType}) && $attr{$name}{subType} =~ m/^hvac\.0(1|4)$/) {
  12608. # control PID regulatior
  12609. if (AttrVal($name, 'pidCtrl', 'on') eq 'on' && ReadingsVal($name, 'maintenanceMode', 'off') eq 'off') {
  12610. EnOcean_setPID(undef, $hash, 'start', ReadingsVal($name, "setpoint", ''));
  12611. } else {
  12612. EnOcean_setPID(undef, $hash, 'stop', '');
  12613. }
  12614. }
  12615. }
  12616. # teach-in response actions
  12617. # delete temporary teach-in response device, see V9333_02
  12618. #Log3($name, 2, "EnOcean $name <notify> DEFINED $definedName");
  12619. } elsif ($devName eq "global" && $s =~ m/^INITIALIZED$/) {
  12620. # assign remote management defptr
  12621. if (exists $attr{$name}{remoteID}) {
  12622. $modules{EnOcean}{defptr}{$attr{$name}{remoteID}} = $hash;
  12623. }
  12624. if (AttrVal($name ,"subType", "") eq "roomCtrlPanel.00") {
  12625. CommandDeleteReading(undef, "$name waitingCmds");
  12626. }
  12627. if (exists($attr{$name}{subType}) && $attr{$name}{subType} =~ m/^hvac\.0(1|4)$/) {
  12628. # control PID regulatior
  12629. if (AttrVal($name, 'pidCtrl', 'on') eq 'on' && ReadingsVal($name, 'maintenanceMode', 'off') eq 'off') {
  12630. EnOcean_setPID(undef, $hash, 'start', ReadingsVal($name, "setpoint", ''));
  12631. } else {
  12632. EnOcean_setPID(undef, $hash, 'stop', '');
  12633. }
  12634. }
  12635. if (exists($attr{$name}{subType}) && $attr{$name}{subType} eq "switch.05") {
  12636. my @getCmd = ($name, 'state');
  12637. EnOcean_Get($hash, @getCmd);
  12638. }
  12639. EnOcean_ReadDevDesc(undef, $hash);
  12640. #Log3($name, 2, "EnOcean $name <notify> INITIALIZED");
  12641. } elsif ($devName eq "global" && $s =~ m/^REREADCFG$/) {
  12642. # assign remote management defptr
  12643. if (exists $attr{$name}{remoteID}) {
  12644. $modules{EnOcean}{defptr}{$attr{$name}{remoteID}} = $hash;
  12645. }
  12646. if (AttrVal($name ,"subType", "") eq "roomCtrlPanel.00") {
  12647. CommandDeleteReading(undef, "$name waitingCmds");
  12648. }
  12649. if (exists($attr{$name}{subType}) && $attr{$name}{subType} =~ m/^hvac\.0(1|4)$/) {
  12650. # control PID regulatior
  12651. if (AttrVal($name, 'pidCtrl', 'on') eq 'on' && ReadingsVal($name, 'maintenanceMode', 'off') eq 'off') {
  12652. EnOcean_setPID(undef, $hash, 'start', ReadingsVal($name, "setpoint", ''));
  12653. } else {
  12654. EnOcean_setPID(undef, $hash, 'stop', '');
  12655. }
  12656. }
  12657. EnOcean_ReadDevDesc(undef, $hash);
  12658. #Log3($name, 2, "EnOcean $name <notify> REREADCFG");
  12659. } elsif ($devName eq "global" && $s =~ m/^ATTR ([^ ]*) ([^ ]*) ([^ ]*)$/) {
  12660. my ($sdev, $attrName, $attrVal) = ($1, $2, $3);
  12661. #Log3 $name, 5, "EnOcean $name <notify> ATTR $1 $2 $3";
  12662. if ($name eq $sdev && $attrName =~ m/^subDef.?/ && $attrVal eq "getNextID") {
  12663. $attr{$name}{$attrName} = '0' x 8;
  12664. $attr{$name}{$attrName} = EnOcean_CheckSenderID("getNextID", $defs{$name}{IODev}{NAME}, "00000000");
  12665. Log3 $name, 2, "EnOcean set $name attribute $attrName to " . $attr{$name}{$attrName};
  12666. } elsif ($attrName eq "devChannel" && $attrVal =~ m/^[\dA-Fa-f]{2}$/) {
  12667. # convert old format
  12668. $attr{$name}{$attrName} = hex $attrVal;
  12669. }
  12670. } elsif ($devName eq "global" && $s =~ m/^DELETEATTR ([^ ]*)$/) {
  12671. #Log3($name, 5, "EnOcean $name <notify> DELETEATTR $1");
  12672. } elsif ($devName eq "global" && $s =~ m/^MODIFIED ([^ ]*)$/) {
  12673. #Log3($name, 5, "EnOcean $name <notify> MODIFIED");
  12674. } elsif ($devName eq "global" && $s =~ m/^SAVE$/) {
  12675. #Log3($name, 5, "EnOcean $name <notify> SAVE");
  12676. } elsif ($devName eq "global" && $s =~ m/^SHUTDOWN$/) {
  12677. #Log3($name, 5, "EnOcean $name <notify> SHUTDOWN");
  12678. } else {
  12679. my @parts = split(/: | /, $s);
  12680. if (defined AttrVal($name, "observeRefDev", undef)) {
  12681. my @observeRefDev = split("[ \t][ \t]*", AttrVal($name, "observeRefDev", undef));
  12682. if (grep /^$devName$/, @observeRefDev) {
  12683. my ($reading, $value) = ("", "");
  12684. $reading = shift @parts;
  12685. if (!defined($parts[0]) || @parts > 1) {
  12686. $value = $s;
  12687. $reading = "state";
  12688. } else {
  12689. $value = $parts[0];
  12690. }
  12691. my @cmdObserve = ($devName, $reading, $value);
  12692. EnOcean_observeParse(2, $hash, @cmdObserve);
  12693. #Log3($name, 5, "EnOcean $name <notify> observeRefDev: $devName $reading: $value");
  12694. }
  12695. }
  12696. if (defined AttrVal($name, "demandRespRefDev", undef)) {
  12697. my @demandRespRefDev = split("[ \t][ \t]*", AttrVal($name, "demandRespRefDev", undef));
  12698. if (grep /^$devName$/, @demandRespRefDev) {
  12699. my @cmdDemandResponse;
  12700. my $actionCmd = AttrVal($name, "demandRespAction", undef);
  12701. if (defined $actionCmd) {
  12702. if ($parts[0] =~ m/^on|off$/) {
  12703. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set $parts[0]");
  12704. my %specials = ("%TARGETNAME" => $name,
  12705. "%NAME" => $devName,
  12706. "%TARGETTYPE" => $hash->{TYPE},
  12707. "%TYPE" => $dev->{TYPE},
  12708. "%LEVEL" => ReadingsVal($devName, "level", 15),
  12709. "%SETPOINT" => ReadingsVal($devName, "setpoint", 255),
  12710. "%POWERUSAGE" => ReadingsVal($devName, "powerUsage", 100),
  12711. "%POWERUSAGESCALE" => ReadingsVal($devName, "powerUsageScale", "max"),
  12712. "%POWERUSAGELEVEL" => ReadingsVal($devName, "powerUsageLevel", "max"),
  12713. "%TARGETSTATE" => ReadingsVal($name, "state", ""),
  12714. "%STATE" => ReadingsVal($devName, "state", "off")
  12715. );
  12716. # action exec
  12717. $actionCmd = EvalSpecials($actionCmd, %specials);
  12718. my $ret = AnalyzeCommandChain(undef, $actionCmd);
  12719. Log3 $name, 2, "EnOcean $name demandRespAction ERROR: $ret" if($ret);
  12720. }
  12721. } elsif (AttrVal($name, "subType", "") =~ m/^switch.*$/ || AttrVal($name, "subTypeSet", "") =~ m/^switch.*$/) {
  12722. if ($parts[0] eq "powerUsageLevel" && $parts[1] eq "max") {
  12723. @cmdDemandResponse = ($name, AttrVal($name, "demandRespMax", "B0"));
  12724. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12725. EnOcean_Set($hash, @cmdDemandResponse);
  12726. } elsif ($parts[0] eq "powerUsageLevel" && $parts[1] eq "min") {
  12727. @cmdDemandResponse = ($name, AttrVal($name, "demandRespMin", "BI"));
  12728. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12729. EnOcean_Set($hash, @cmdDemandResponse);
  12730. }
  12731. } elsif (AttrVal($name, "subType", "") eq "gateway" && AttrVal($name, "gwCmd", "") eq "switching") {
  12732. if ($parts[0] eq "powerUsageLevel" && $parts[1] eq "max") {
  12733. @cmdDemandResponse = ($name, "on");
  12734. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12735. EnOcean_Set($hash, @cmdDemandResponse);
  12736. } elsif ($parts[0] eq "powerUsageLevel" && $parts[1] eq "min") {
  12737. @cmdDemandResponse = ($name, "off");
  12738. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12739. EnOcean_Set($hash, @cmdDemandResponse);
  12740. }
  12741. } elsif ((AttrVal($name, "subType", "") eq "gateway" && AttrVal($name, "gwCmd", "") eq "dimming")
  12742. || AttrVal($name, "subType", "") eq "actuator.01") {
  12743. if ($parts[0] eq "powerUsage") {
  12744. if (ReadingsVal($devName, "powerUsageScale", "max") eq "rel") {
  12745. @cmdDemandResponse = ($name, "dim", $parts[1] * ReadingsVal($name, "dimValueLast", 100) / 100);
  12746. } else {
  12747. @cmdDemandResponse = ($name, "dim", $parts[1]);
  12748. }
  12749. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12750. EnOcean_Set($hash, @cmdDemandResponse);
  12751. }
  12752. } elsif ((AttrVal($name, "subType", "") eq "roomSensorControl.05" && AttrVal($name, "manufID", "") eq "00D")) {
  12753. if ($parts[0] eq "level") {
  12754. @cmdDemandResponse = ($name, "nightReduction", int(5 - 1/3 * $parts[1]));
  12755. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12756. EnOcean_Set($hash, @cmdDemandResponse);
  12757. }
  12758. } elsif (AttrVal($name, "subType", "") eq "lightCtrl.01") {
  12759. if ($parts[0] eq "setpoint") {
  12760. @cmdDemandResponse = ($name, "dim", $parts[1]);
  12761. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12762. EnOcean_Set($hash, @cmdDemandResponse);
  12763. }
  12764. } elsif (AttrVal($name, "subType", "") eq "roomSensorControl.01") {
  12765. if ($parts[0] eq "setpoint") {
  12766. @cmdDemandResponse = ($name, "setpoint", $parts[1]);
  12767. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12768. EnOcean_Set($hash, @cmdDemandResponse);
  12769. }
  12770. } elsif (AttrVal($name, "subType", "") eq "roomSensorControl.05") {
  12771. if ($parts[0] eq "setpoint") {
  12772. @cmdDemandResponse = ($name, $parts[0], $parts[1]);
  12773. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12774. EnOcean_Set($hash, @cmdDemandResponse);
  12775. }
  12776. } elsif (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
  12777. if ($parts[0] eq "powerUsageLevel" && $parts[1] eq "max") {
  12778. @cmdDemandResponse = ($name, "roomCtrlMode", "comfort");
  12779. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12780. EnOcean_Set($hash, @cmdDemandResponse);
  12781. } elsif ($parts[0] eq "powerUsageLevel" && $parts[1] eq "min") {
  12782. @cmdDemandResponse = ($name, "roomCtrlMode", "economy");
  12783. #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
  12784. EnOcean_Set($hash, @cmdDemandResponse);
  12785. }
  12786. }
  12787. }
  12788. }
  12789. if (defined(AttrVal($name, "humidityRefDev", undef)) && AttrVal($name, "setCmdTrigger", "man") eq "refDev") {
  12790. if ($devName eq AttrVal($name, "humidityRefDev", "")) {
  12791. if ($parts[0] eq "humidity") {
  12792. if (AttrVal($name, "subType", "") eq "roomSensorControl.01") {
  12793. my @setCmd = ($name, "setpoint");
  12794. EnOcean_Set($hash, @setCmd);
  12795. }
  12796. }
  12797. }
  12798. #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
  12799. }
  12800. if (defined(AttrVal($name, "temperatureRefDev", undef)) && AttrVal($name, "setCmdTrigger", "man") eq "refDev") {
  12801. # sent a setpoint or setpointTemp telegram
  12802. if ($devName eq AttrVal($name, "temperatureRefDev", "")) {
  12803. if ($parts[0] eq "temperature") {
  12804. if (AttrVal($name, "subType", "") eq "roomSensorControl.05" && AttrVal($name, "manufID", "") eq "00D") {
  12805. my @setCmd = ($name, "setpointTemp");
  12806. EnOcean_Set($hash, @setCmd);
  12807. } elsif (AttrVal($name, "subType", "") eq "fanCtrl.00") {
  12808. my @setCmd = ($name, "setpointTemp");
  12809. EnOcean_Set($hash, @setCmd);
  12810. } elsif (AttrVal($name, "subType", "") eq "roomSensorControl.01") {
  12811. my @setCmd = ($name, "setpoint");
  12812. EnOcean_Set($hash, @setCmd);
  12813. } elsif (AttrVal($name, "subType", "") eq "roomSensorControl.05") {
  12814. my @setCmd = ($name, "setpoint");
  12815. EnOcean_Set($hash, @setCmd);
  12816. }
  12817. }
  12818. }
  12819. #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
  12820. }
  12821. if (defined(AttrVal($name, "temperatureRefDev", undef)) &&
  12822. $devName eq AttrVal($name, "temperatureRefDev", "") &&
  12823. $parts[0] eq "temperature") {
  12824. if (AttrVal($name, "subType", "") eq "hvac.01") {
  12825. readingsSingleUpdate($hash, "temperature", $parts[1], 1);
  12826. #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
  12827. }
  12828. if (AttrVal($name, "subType", "") eq "hvac.04" && AttrVal($name, "measurementCtrl", "enable") eq 'disable') {
  12829. readingsSingleUpdate($hash, "temperature", $parts[1], 1);
  12830. #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
  12831. }
  12832. }
  12833. if (defined(AttrVal($name, "setpointRefDev", undef)) &&
  12834. $devName eq AttrVal($name, "setpointRefDev", "") &&
  12835. $parts[0] eq "setpoint") {
  12836. if (AttrVal($name, "subType", '') =~ m/^hvac\.0(1|4)$/) {
  12837. my @setCmd = ($name, "setpoint", $parts[1]);
  12838. EnOcean_Set($hash, @setCmd);
  12839. #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
  12840. }
  12841. }
  12842. if (defined(AttrVal($name, "setpointTempRefDev", undef)) &&
  12843. $devName eq AttrVal($name, "setpointTempRefDev", "") &&
  12844. $parts[0] eq "setpointTemp") {
  12845. if (AttrVal($name, "subType", '') =~ m/^hvac\.0(1|4)$/) {
  12846. if (ReadingsVal($name, "setpointTemp", 0) != $parts[1]) {
  12847. my @setCmd = ($name, "setpointTemp", $parts[1]);
  12848. EnOcean_Set($hash, @setCmd);
  12849. #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
  12850. }
  12851. }
  12852. }
  12853. }
  12854. }
  12855. return undef;
  12856. }
  12857. # ADT encapsulation
  12858. sub
  12859. EnOcean_Encapsulation($$$$)
  12860. {
  12861. my ($packetType, $rorg, $data, $destinationID) = @_;
  12862. if ($destinationID eq "FFFFFFFF") {
  12863. return ($rorg, $data);
  12864. } else {
  12865. $data = $rorg . $data;
  12866. return ("A6", $data);
  12867. }
  12868. }
  12869. # set PID regulator
  12870. sub EnOcean_setPID($$$) {
  12871. my ($crtl, $hash, $cmd, $adjust) = @_;
  12872. my $name = $hash->{NAME};
  12873. my ($err, $response, $logLevel) = (undef, 'start', 5);
  12874. @{$hash->{helper}{calcPID}} = (undef, $hash, $cmd);
  12875. if ($cmd eq 'stop' || AttrVal($name, 'pidCtrl', 'on') eq 'off') {
  12876. $hash->{helper}{stopped} = 1;
  12877. readingsSingleUpdate($hash, "pidState", 'stopped', 0);
  12878. RemoveInternalTimer($hash->{helper}{calcPID});
  12879. $response = 'stopped';
  12880. } elsif ($cmd eq 'start' || $cmd eq 'actuator') {
  12881. $hash->{helper}{stopped} = 0;
  12882. $hash->{helper}{adjust} = $adjust;
  12883. RemoveInternalTimer($hash->{helper}{calcPID});
  12884. ($err, $logLevel, $response) = EnOcean_calcPID($hash->{helper}{calcPID});
  12885. }
  12886. return ($err, $logLevel, $response);
  12887. }
  12888. # calc valve setpoint (PID regulator)
  12889. sub EnOcean_calcPID($) {
  12890. my ($pidParam) = @_;
  12891. my ($crtl, $hash, $cmd) = @$pidParam;
  12892. my $name = $hash->{NAME};
  12893. my ($err, $response, $logLevel, $setpoint) = (undef, $cmd, 5, 0);
  12894. #####
  12895. my $reUINT = '^([\\+]?\\d+)$'; # uint without whitespaces
  12896. my $re01 = '^([0,1])$'; # only 0,1
  12897. my $reINT = '^([\\+,\\-]?\\d+$)'; # int
  12898. my $reFloatpos = '^([\\+]?\\d+\\.?\d*$)'; # gleitpunkt positiv float
  12899. my $reFloat = '^([\\+,\\-]?\\d+\\.?\d*$)'; # float
  12900. my $sensor = $name;
  12901. my $reading = 'temperature';
  12902. my $regexp = $reFloat;
  12903. my $DEBUG_Sensor = AttrVal( $name, 'pidDebugSensor', '0' ) eq '1';
  12904. my $DEBUG_Actuation = AttrVal( $name, 'pidDebugActuation', '0' ) eq '1';
  12905. my $DEBUG_Delta = AttrVal( $name, 'pidDebugDelta', '0' ) eq '1';
  12906. my $DEBUG_Calc = AttrVal( $name, 'pidDebugCalc', '0' ) eq '1';
  12907. my $DEBUG_Update = AttrVal( $name, 'pidDebugUpdate', '0' ) eq '1';
  12908. my $DEBUG = $DEBUG_Sensor || $DEBUG_Actuation || $DEBUG_Calc || $DEBUG_Delta || $DEBUG_Update;
  12909. my $actuation = "";
  12910. my $actuationDone = ReadingsVal( $name, 'setpointSet', ReadingsVal( $name, 'setpoint', ""));
  12911. my $actuationCalc = ReadingsVal( $name, 'setpointCalc', "" );
  12912. my $actuationCalcOld = $actuationCalc;
  12913. my $actorTimestamp =
  12914. ( $hash->{helper}{actorTimestamp} )
  12915. ? $hash->{helper}{actorTimestamp}
  12916. : FmtDateTime( gettimeofday() - 3600 * 24 );
  12917. my $desired = '';
  12918. my $sensorStr = ReadingsVal($name, 'temperature',"");
  12919. my $sensorValue = "";
  12920. my $sensorTS = ReadingsTimestamp($name, 'temperature', undef);
  12921. my $sensorIsAlive = 0;
  12922. my $iPortion = ReadingsVal( $name, 'p_i', 0 );
  12923. my $pPortion = ReadingsVal( $name, 'p_p', "" );
  12924. my $dPortion = ReadingsVal( $name, 'p_d', "" );
  12925. my $stateStr = "";
  12926. CommandDeleteReading(undef, "$name pidAlarm");
  12927. my $deltaOld = ReadingsVal( $name, 'delta', 0 );
  12928. my $delta = "";
  12929. my $deltaGradient = ( $hash->{helper}{deltaGradient} ) ? $hash->{helper}{deltaGradient} : 0;
  12930. my $calcReq = 0;
  12931. my $readingUpdateReq = '';
  12932. # ---------------- check conditions
  12933. while (1)
  12934. {
  12935. # --------------- retrive values from attributes
  12936. my $wakeUpCycle = AttrVal($name, 'wakeUpCycle', ReadingsVal($name, 'wakeUpCycle', 300));
  12937. my $pidCycle = $wakeUpCycle / 3;
  12938. $pidCycle = 10 if ($pidCycle < 10);
  12939. $hash->{helper}{actorInterval} = 10;
  12940. $hash->{helper}{actorThreshold} = 0;
  12941. $hash->{helper}{actorKeepAlive} = $pidCycle;
  12942. $hash->{helper}{actorValueDecPlaces} = 0;
  12943. $hash->{helper}{actorErrorAction} = AttrVal($name, 'pidActorErrorAction', 'freeze');
  12944. $hash->{helper}{actorErrorPos} = AttrVal($name, 'pidActorErrorPos', 0);
  12945. $hash->{helper}{calcInterval} = $pidCycle;
  12946. $hash->{helper}{deltaTreshold} = AttrVal($name, 'pidDeltaTreshold', 0);
  12947. if (AttrVal($name, 'measurementCtrl', 'enable') eq 'enable') {
  12948. $hash->{helper}{sensorTimeout} = $wakeUpCycle * 4;
  12949. } else {
  12950. $hash->{helper}{sensorTimeout} = AttrVal($name, 'pidSensorTimeout', 3600);
  12951. }
  12952. $hash->{helper}{reverseAction} = 0;
  12953. $hash->{helper}{updateInterval} = $pidCycle;
  12954. $hash->{helper}{actorLimitLower} = AttrVal($name, 'pidActorLimitLower', 0);
  12955. my $actorLimitLower = $hash->{helper}{actorLimitLower};
  12956. $hash->{helper}{actorLimitUpper} = AttrVal($name, 'pidActorLimitUpper', 100);
  12957. my $actorLimitUpper = $hash->{helper}{actorLimitUpper};
  12958. $hash->{helper}{factor_P} = AttrVal($name, 'pidFactor_P', 25);
  12959. $hash->{helper}{factor_I} = AttrVal($name, 'pidFactor_I', 0.25);
  12960. $hash->{helper}{factor_D} = AttrVal($name, 'pidFactor_D', 0);
  12961. if ($hash->{helper}{stopped}) {
  12962. $stateStr = "stopped";
  12963. last;
  12964. }
  12965. $desired = ReadingsVal( $name, 'setpointTempSet', ReadingsVal($name, 'setpointTemp', ""));
  12966. #my $desired = ReadingsVal( $name, $hash->{helper}{desiredName}, "" );
  12967. # sensor found
  12968. #PID20_Log $hash, 2, "--------------------------" if ($DEBUG);
  12969. #PID20_Log $hash, 2, "S1 sensorStr:$sensorStr sensorTS:$sensorTS" if ($DEBUG_Sensor);
  12970. if ( !$sensorStr && !$stateStr ) {
  12971. $stateStr = "alarm";
  12972. $err = 'no_temperature_value';
  12973. }
  12974. # sensor alive
  12975. if ( $sensorStr && $sensorTS )
  12976. {
  12977. my $timeDiff = EnOcean_TimeDiff($sensorTS);
  12978. $sensorIsAlive = 1 if ( $timeDiff <= $hash->{helper}{sensorTimeout} );
  12979. $sensorStr =~ m/$regexp/;
  12980. $sensorValue = $1;
  12981. $sensorValue = "" if ( !defined($sensorValue) );
  12982. #PID20_Log $hash, 2,
  12983. # "S2 timeOfDay:"
  12984. # . gettimeofday()
  12985. # . " timeDiff:$timeDiff sensorTimeout:"
  12986. # . $hash->{helper}{sensorTimeout}
  12987. # . " --> sensorIsAlive:$sensorIsAlive"
  12988. # if ($DEBUG_Sensor);
  12989. }
  12990. # sensor dead
  12991. if (!$sensorIsAlive && !$stateStr) {
  12992. $stateStr = "alarm";
  12993. $err = 'dead_sensor';
  12994. }
  12995. # missing desired
  12996. if ($desired eq "" && !$stateStr) {
  12997. $stateStr = "alarm";
  12998. $err = 'setpoint_device_missing';
  12999. }
  13000. # check delta threshold
  13001. $delta = ( $desired ne "" && $sensorValue ne "" ) ? $desired - $sensorValue : "";
  13002. $calcReq = 1 if ( !$stateStr && $delta ne "" && ( abs($delta) >= abs( $hash->{helper}{deltaTreshold} ) ) );
  13003. #PID20_Log $hash, 2,
  13004. # "D1 desired[" . ( $desired ne "" ) ? sprintf( "%.1f", $desired )
  13005. # : "" . "] - sensorValue: [" . ( $sensorValue ne "" ) ? sprintf( "%.1f", $sensorValue )
  13006. # : "" . "] = delta[" . ( $delta ne "" ) ? sprintf( "%.2f", $delta )
  13007. # : "" . "] calcReq:$calcReq"
  13008. # if ($DEBUG_Delta);
  13009. #request for calculation
  13010. # ---------------- calculation request
  13011. if ($calcReq)
  13012. {
  13013. # reverse action requested
  13014. my $workDelta = ( $hash->{helper}{reverseAction} == 1 ) ? -$delta : $delta;
  13015. my $deltaOld = -$deltaOld if ( $hash->{helper}{reverseAction} == 1 );
  13016. # calc p-portion
  13017. $pPortion = $workDelta * $hash->{helper}{factor_P};
  13018. # calc d-Portion
  13019. $dPortion = ($deltaGradient) * $hash->{helper}{calcInterval} * $hash->{helper}{factor_D};
  13020. # calc i-portion respecting windUp
  13021. # freeze i-portion if windUp is active
  13022. my $isWindup = $actuationCalcOld
  13023. && ( ( $workDelta > 0 && $actuationCalcOld > $actorLimitUpper )
  13024. || ( $workDelta < 0 && $actuationCalcOld < $actorLimitLower ) );
  13025. if ( $hash->{helper}{adjust} ne "" )
  13026. {
  13027. $iPortion = $hash->{helper}{adjust} - ( $pPortion + $dPortion );
  13028. $iPortion = $actorLimitUpper if ( $iPortion > $actorLimitUpper );
  13029. $iPortion = $actorLimitLower if ( $iPortion < $actorLimitLower );
  13030. #PID20_Log $hash, 5, "adjust request with:" . $hash->{helper}{adjust} . " ==> p_i:$iPortion";
  13031. $hash->{helper}{adjust} = "";
  13032. } elsif ( !$isWindup ) # integrate only if no windUp
  13033. {
  13034. # normalize the intervall to minute=60 seconds
  13035. $iPortion = $iPortion + $workDelta * $hash->{helper}{factor_I} * $hash->{helper}{calcInterval} / 60;
  13036. $hash->{helper}{isWindUP} = 0;
  13037. }
  13038. $hash->{helper}{isWindUP} = $isWindup;
  13039. # check callback for iPortion
  13040. my $iportionCallBeforeSetting = AttrVal( $name, 'pidIPortionCallBeforeSetting', undef );
  13041. if ( defined($iportionCallBeforeSetting) && exists &$iportionCallBeforeSetting )
  13042. {
  13043. #PID20_Log $hash, 5, 'start callback ' . $iportionCallBeforeSetting . ' with iPortion:' . $iPortion;
  13044. no strict "refs";
  13045. $iPortion = &$iportionCallBeforeSetting( $name, $iPortion );
  13046. use strict "refs";
  13047. #PID20_Log $hash, 5, 'return value of ' . $iportionCallBeforeSetting . ':' . $iPortion;
  13048. }
  13049. # calc actuation
  13050. $actuationCalc = $pPortion + $iPortion + $dPortion;
  13051. #PID20_Log $hash, 2, "P1 delta:" . sprintf( "%.2f", $delta ) . " isWindup:$isWindup" if ($DEBUG_Calc);
  13052. #PID20_Log $hash, 2,
  13053. # "P2 pPortion:"
  13054. # . sprintf( "%.2f", $pPortion )
  13055. # . " iPortion:"
  13056. # . sprintf( "%.2f", $iPortion )
  13057. # . " dPortion:"
  13058. # . sprintf( "%.2f", $dPortion )
  13059. # . " actuationCalc:"
  13060. # . sprintf( "%.2f", $actuationCalc )
  13061. # if ($DEBUG_Calc);
  13062. }
  13063. $readingUpdateReq = 1; # in each case update readings
  13064. # ---------------- acutation request
  13065. my $noTrouble = ( $desired ne "" && $sensorIsAlive );
  13066. # check actor fallback in case of sensor fault
  13067. if (!$sensorIsAlive && ($hash->{helper}{actorErrorAction} eq "errorPos")) {
  13068. #$stateStr .= "- force pid-output to errorPos";
  13069. $err .= ':actuator_in_errorPos';
  13070. $actuationCalc = $hash->{helper}{actorErrorPos};
  13071. $actuationCalc = "" if ( !defined($actuationCalc) );
  13072. }
  13073. # check acutation diff
  13074. $actuation = $actuationCalc;
  13075. # limit $actuation
  13076. $actuation = $actorLimitUpper if ( $actuation ne "" && ( $actuation > $actorLimitUpper ) );
  13077. $actuation = $actorLimitLower if ( $actuation ne "" && ( $actuation < $actorLimitLower ) );
  13078. # check if round request
  13079. my $fmt = "%." . $hash->{helper}{actorValueDecPlaces} . "f";
  13080. $actuation = sprintf( $fmt, $actuation ) if ( $actuation ne "" );
  13081. my $actuationDiff = abs( $actuation - $actuationDone )
  13082. if ( $actuation ne "" && $actuationDone ne "" );
  13083. #PID20_Log $hash, 2,
  13084. # "A1 act:$actuation actDone:$actuationDone "
  13085. # . " actThreshold:"
  13086. # . $hash->{helper}{actorThreshold}
  13087. # . " actDiff:$actuationDiff"
  13088. # if ($DEBUG_Actuation);
  13089. # check threshold-condition for actuation
  13090. my $rsTS = $actuationDone ne "" && $actuationDiff >= $hash->{helper}{actorThreshold};
  13091. # ...... special handling if acutation is in the black zone between actorLimit and (actorLimit - actorThreshold)
  13092. # upper range
  13093. my $rsUp =
  13094. $actuationDone ne ""
  13095. && $actuation > $actorLimitUpper - $hash->{helper}{actorThreshold}
  13096. && $actuationDiff != 0
  13097. && $actuation >= $actorLimitUpper;
  13098. # low range
  13099. my $rsDown =
  13100. $actuationDone ne ""
  13101. && $actuation < $actorLimitLower + $hash->{helper}{actorThreshold}
  13102. && $actuationDiff != 0
  13103. && $actuation <= $actorLimitLower;
  13104. # upper or lower limit are exceeded
  13105. my $rsLimit = $actuationDone ne "" && ( $actuationDone < $actorLimitLower || $actuationDone > $actorLimitUpper );
  13106. my $actuationByThreshold = ( ( $rsTS || $rsUp || $rsDown ) && $noTrouble );
  13107. #PID20_Log $hash, 2, "A2 rsTS:$rsTS rsUp:$rsUp rsDown:$rsDown noTrouble:$noTrouble"
  13108. # if ($DEBUG_Actuation);
  13109. # check time condition for actuation
  13110. my $actTimeDiff = EnOcean_TimeDiff($actorTimestamp); # $actorTimestamp is valid in each case
  13111. my $actuationByTime = ($noTrouble) && ( $actTimeDiff > $hash->{helper}{actorInterval} );
  13112. #PID20_Log $hash, 2,
  13113. # "A3 actTS:$actorTimestamp"
  13114. # . " actTimeDiff:"
  13115. # . sprintf( "%.2f", $actTimeDiff )
  13116. # . " actInterval:"
  13117. # . $hash->{helper}{actorInterval}
  13118. # . "-->actByTime:$actuationByTime "
  13119. # if ($DEBUG_Actuation);
  13120. # check keep alive condition for actuation
  13121. my $actuationKeepAliveReq = ( $actTimeDiff >= $hash->{helper}{actorKeepAlive} )
  13122. if ( defined($actTimeDiff) && $actuation ne "" );
  13123. # build total actuation request
  13124. my $actuationReq = (
  13125. ( $actuationByThreshold && $actuationByTime )
  13126. || $actuationKeepAliveReq # request by keep alive
  13127. || $rsLimit # upper or lower limit are exceeded
  13128. || $actuationDone eq "" # startup condition
  13129. ) && $actuation ne ""; # acutation is initialized
  13130. #PID20_Log $hash, 2,
  13131. # "A4 (actByTh:$actuationByThreshold && actByTime:$actuationByTime)"
  13132. # . "||actKeepAlive:$actuationKeepAliveReq"
  13133. # . "||rsLimit:$rsLimit=actnReq:$actuationReq"
  13134. # if ($DEBUG_Actuation);
  13135. # ................ perform output to actor
  13136. if ($actuationReq)
  13137. {
  13138. $readingUpdateReq = 1; # update the readings
  13139. # check calback for actuation
  13140. my $actorCallBeforeSetting = AttrVal( $name, 'pidActorCallBeforeSetting', undef );
  13141. if ( defined($actorCallBeforeSetting) && exists &$actorCallBeforeSetting )
  13142. {
  13143. #PID20_Log $hash, 5, 'start callback ' . $actorCallBeforeSetting . ' with actuation:' . $actuation;
  13144. no strict "refs";
  13145. $actuation = &$actorCallBeforeSetting( $name, $actuation );
  13146. use strict "refs";
  13147. #PID20_Log $hash, 5, 'return value of ' . $actorCallBeforeSetting . ':' . $actuation;
  13148. }
  13149. #build command for fhem
  13150. #PID20_Log $hash, 5,
  13151. # "actor:"
  13152. # . $hash->{helper}{actor}
  13153. # . " actorCommand:"
  13154. # . $hash->{helper}{actorCommand}
  13155. # . " actuation:"
  13156. # . $actuation;
  13157. #my $cmd = sprintf( "set %s %s %g", $hash->{helper}{actor}, $hash->{helper}{actorCommand}, $actuation );
  13158. # execute command
  13159. my $ret;
  13160. #$ret = fhem $cmd;
  13161. $setpoint = $actuation;
  13162. $actuationDone = $actuation;
  13163. # note timestamp
  13164. $hash->{helper}{actorTimestamp} = TimeNow();
  13165. my $retStr = "";
  13166. $retStr = " with return-value:" . $ret if ( defined($ret) && ( $ret ne '' ) );
  13167. #PID20_Log $hash, 3, "<$cmd> " . $retStr;
  13168. }
  13169. my $updateAlive = ( $actuation ne "" )
  13170. && EnOcean_TimeDiff( ReadingsTimestamp( $name, 'setpointSet', gettimeofday() ) ) >= $hash->{helper}{updateInterval};
  13171. # my $updateReq = ( ( $actuationReq || $updateAlive ) && $actuation ne "" );
  13172. # PID20_Log $hash, 2, "U1 actReq:$actuationReq updateAlive:$updateAlive --> updateReq:$updateReq" if ($DEBUG_Update);
  13173. # ---------------- update request
  13174. if ($readingUpdateReq)
  13175. {
  13176. readingsBeginUpdate($hash);
  13177. #readingsBulkUpdate( $hash, $hash->{helper}{desiredName}, $desired ) if ( $desired ne "" );
  13178. #readingsBulkUpdate( $hash, $hash->{helper}{measuredName}, $sensorValue ) if ( $sensorValue ne "" );
  13179. readingsBulkUpdate( $hash, 'p_p', $pPortion ) if ( $pPortion ne "" );
  13180. readingsBulkUpdate( $hash, 'p_d', $dPortion ) if ( $dPortion ne "" );
  13181. readingsBulkUpdate( $hash, 'p_i', $iPortion ) if ( $iPortion ne "" );
  13182. readingsBulkUpdate( $hash, 'setpointSet', $actuationDone) if ($actuationDone ne "");
  13183. readingsBulkUpdate( $hash, 'setpointCalc', $actuationCalc ) if ( $actuationCalc ne "" );
  13184. readingsBulkUpdate( $hash, 'delta', $delta ) if ( $delta ne "" );
  13185. readingsEndUpdate( $hash, 1 );
  13186. #PID20_Log $hash, 5, "readings updated";
  13187. }
  13188. last;
  13189. } # end while
  13190. # ........ update statePID.
  13191. $stateStr = 'idle' if ($stateStr eq '' && !$calcReq);
  13192. $stateStr = 'processing' if ($stateStr eq '' && $calcReq);
  13193. #PID20_Log $hash, 2, "C1 stateStr:$stateStr calcReq:$calcReq" if ($DEBUG_Calc);
  13194. #......... timer setup
  13195. #my $next = gettimeofday() + $hash->{helper}{calcInterval};
  13196. #RemoveInternalTimer($name); # prevent multiple timers for same hash
  13197. #InternalTimer( $next, "PID20_Calc", $name, 1 );
  13198. #PID20_Log $hash, 2, "InternalTimer next:".FmtDateTime($next)." PID20_Calc name:$name DEBUG_Calc:$DEBUG_Calc";
  13199. readingsBeginUpdate($hash);
  13200. readingsBulkUpdate($hash, 'pidState', $stateStr);
  13201. readingsBulkUpdate($hash, 'pidAlarm', $err) if (defined $err);
  13202. readingsEndUpdate($hash, 1);
  13203. Log3($name, 5, "EnOcean $name EnOcean_calcPID Cmd: $cmd pidState: $stateStr T: $sensorValue SP: $setpoint SPT: $desired");
  13204. @{$hash->{helper}{calcPID}} = (undef, $hash, 'periodic');
  13205. RemoveInternalTimer($hash->{helper}{calcPID});
  13206. InternalTimer(gettimeofday() + $hash->{helper}{calcInterval} * 1.02, "EnOcean_calcPID", $hash->{helper}{calcPID}, 0);
  13207. return ($err, $logLevel, $response);
  13208. }
  13209. # sent message to Multisnesor Window Handle (EEP D2-06-01)
  13210. sub
  13211. EnOcean_multisensor_01Snd($$$)
  13212. {
  13213. my ($crtl, $hash, $packetType) = @_;
  13214. my $name = $hash->{NAME};
  13215. my ($data, $err, $response, $logLevel);
  13216. EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  13217. return ($err, $response);
  13218. }
  13219. # sent message to Room Control Panel (EEP D2-10-xx)
  13220. sub
  13221. EnOcean_roomCtrlPanel_00Snd($$$$$$$$)
  13222. {
  13223. my ($crtl, $hash, $packetType, $mid, $mcf, $irc, $fbc, $gmt) = @_;
  13224. my $name = $hash->{NAME};
  13225. my ($data, $err, $response, $logLevel);
  13226. my $messagePart = 1;
  13227. if ($mid == 0) {
  13228. # general massage
  13229. ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart);
  13230. EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  13231. if ($err) {
  13232. Log3 $name, $logLevel, "EnOcean $name Error: $err";
  13233. } else {
  13234. Log3 $name, $logLevel, "EnOcean $name $response";
  13235. }
  13236. if (!defined($irc)) {
  13237. } elsif ($irc == 0) {
  13238. # acknowledge request
  13239. } elsif ($irc == 1) {
  13240. # data request
  13241. } elsif ($irc == 2) {
  13242. # configuration request
  13243. } elsif ($irc == 3) {
  13244. # room control request
  13245. } elsif ($irc == 4) {
  13246. # time program request
  13247. }
  13248. if (!defined($fbc)) {
  13249. } elsif ($fbc == 0) {
  13250. # acknowledge / heartbeat
  13251. } elsif ($fbc == 1) {
  13252. # telegram repetition request
  13253. } elsif ($fbc == 2) {
  13254. # message repetition request
  13255. } elsif ($fbc == 3) {
  13256. # reserved
  13257. }
  13258. if (!defined($gmt)) {
  13259. } elsif ($gmt == 0) {
  13260. # information request
  13261. } elsif ($gmt == 1) {
  13262. # feetback
  13263. }
  13264. } elsif ($mid == 1) {
  13265. # data message
  13266. ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart);
  13267. EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  13268. if ($err) {
  13269. Log3 $name, $logLevel, "EnOcean $name Error: $err";
  13270. } else {
  13271. Log3 $name, $logLevel, "EnOcean $name $response";
  13272. }
  13273. } elsif ($mid == 2) {
  13274. # configuration message
  13275. ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart);
  13276. EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  13277. if ($err) {
  13278. Log3 $name, $logLevel, "EnOcean $name Error: $err";
  13279. } else {
  13280. Log3 $name, $logLevel, "EnOcean $name $response";
  13281. }
  13282. } elsif ($mid == 3) {
  13283. # room control setup
  13284. ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart);
  13285. EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  13286. if ($err) {
  13287. Log3 $name, $logLevel, "EnOcean $name Error: $err";
  13288. } else {
  13289. Log3 $name, $logLevel, "EnOcean $name $response";
  13290. }
  13291. } elsif ($mid == 4) {
  13292. # time program setup
  13293. ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart);
  13294. EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
  13295. if ($err) {
  13296. Log3 $name, $logLevel, "EnOcean $name Error: $err";
  13297. } else {
  13298. Log3 $name, $logLevel, "EnOcean $name $response";
  13299. }
  13300. }
  13301. return ($err, $response);
  13302. }
  13303. # generate command to Room Control Panel (EEP D2-10-xx)
  13304. sub
  13305. EnOcean_roomCtrlPanel_00Cmd($$$$)
  13306. {
  13307. my ($crtl, $hash, $mcf, $messagePart) = @_;
  13308. my $name = $hash->{NAME};
  13309. my $data = "0000";
  13310. my $err;
  13311. my $response = "acknowledge send";
  13312. my $logLevel = 4;
  13313. # Waitings Commands (waitingCmds)
  13314. # 1 = sent data request
  13315. # 2 = sent data message
  13316. # 4 = sent configuration message (time)
  13317. # 8 = sent room control setup
  13318. # 16 = sent time program setup
  13319. # 32 = sent configuration request
  13320. # 64 = sent configuration message
  13321. # 128 = sent room control setup request
  13322. # 256 = sent time program request
  13323. # 512 = sent delete time program
  13324. my $waitingCmds = ReadingsVal($name, "waitingCmds", 0);
  13325. if ($mcf == 0) {
  13326. # message complete
  13327. if ($waitingCmds & 8) {
  13328. # room control setup waiting
  13329. my ($db5, $db4, $db3, $db2, $db1, $db0) = (96, 0, 0, 0, 0, 0, 0);
  13330. if (defined ReadingsVal($name, "setpointComfortTempSet", undef)) {
  13331. $db1 = ReadingsVal($name, "setpointComfortTempSet", 0) * 255 / 40;
  13332. $db0 = $db0 | 1;
  13333. CommandDeleteReading(undef, "$name setpointComfortTempSet");
  13334. }
  13335. if (defined ReadingsVal($name, "setpointEconomyTempSet", undef)) {
  13336. $db2 = ReadingsVal($name, "setpointEconomyTempSet", 0) * 255 / 40;
  13337. $db0 = $db0 | 2;
  13338. CommandDeleteReading(undef, "$name setpointEconomyTempSet");
  13339. }
  13340. if (defined ReadingsVal($name, "setpointPreComfortTempSet", undef)) {
  13341. $db3 = ReadingsVal($name, "setpointPreComfortTempSet", 0) * 255 / 40;
  13342. $db0 = $db0 | 4;
  13343. CommandDeleteReading(undef, "$name setpointPreComfortTempSet");
  13344. }
  13345. if (defined ReadingsVal($name, "setpointBuildingProtectionTempSet", undef)) {
  13346. $db4 = ReadingsVal($name, "setpointBuildingProtectionTempSet", 0) * 255 / 40;
  13347. $db0 = $db0 | 8;
  13348. CommandDeleteReading(undef, "$name setpointBuildingProtectionTempSet");
  13349. }
  13350. $data = sprintf "%02X%02X%02X%02X%02X%02X", $db5, $db4, $db3, $db2, $db1, $db0;
  13351. # clear command
  13352. $waitingCmds = $waitingCmds & 247 + 0xFF00;
  13353. $response = "room control setup send $data";
  13354. $logLevel = 2;
  13355. } elsif ($waitingCmds & 64 || $waitingCmds & 4) {
  13356. # configuration message waiting
  13357. my ($sec, $min, $hour, $day, $month, $year) = localtime();
  13358. my ($key, $val);
  13359. $month += 1;
  13360. $year += 1900;
  13361. my ($db7, $db6, $db5, $db4, $db1, $db0) = (64, 0, 0, 0, $min << 2, $hour << 3);
  13362. $db6 |= 1 if (AttrVal($name, "blockFanSpeed", "no") ne "yes" );
  13363. $db6 |= 2 if (AttrVal($name, "blockSetpointTemp", "no") ne "yes" );
  13364. $db6 |= 4 if (AttrVal($name, "blockOccupancy", "no") ne "yes" );
  13365. $db6 |= 8 if (AttrVal($name, "blockTimeProgram", "no") ne "yes" );
  13366. $db6 |= 16 if (AttrVal($name, "blockDateTime", "no") ne "yes" );
  13367. $db6 |= 32 if (AttrVal($name, "blockDisplay", "no") ne "yes" );
  13368. $db6 |= 64 if (AttrVal($name, "blockTemp", "no") ne "yes" );
  13369. $db6 |= 128 if (AttrVal($name, "blockMotion", "no") ne "yes" );
  13370. $db5 = AttrVal($name, "pollInterval", 10);
  13371. if ($db5 > 60 && $db5 <= 180) {
  13372. $db5 = 61;
  13373. } elsif ($db5 > 180 && $db5 <= 720) {
  13374. $db5 = 62;
  13375. } elsif ($db5 > 720) {
  13376. $db5 = 63;
  13377. }
  13378. $db5 = $db5 << 2;
  13379. $db5 |= 2 if (AttrVal($name, "blockKey", "no") ne "yes" );
  13380. my $displayContent = AttrVal($name, "displayContent", "no_change");
  13381. my $displayContentVal = 0;
  13382. my %displayContent = ("humidity" => 7,
  13383. "off" => 6,
  13384. "setpointTemp" => 5,
  13385. "tempertureExtern" => 4,
  13386. "temperatureIntern" => 3,
  13387. "time" => 2,
  13388. "default" => 1,
  13389. "no_change" => 0
  13390. );
  13391. while (($key, $val) = each(%displayContent)) {
  13392. $displayContentVal = $val if ($key eq $displayContent);
  13393. }
  13394. my $temperatureScale = AttrVal($name, "temperatureScale", "no_change");
  13395. my $temperatureScaleVal = 0;
  13396. my %temperatureScale = ("F" => 3,
  13397. "C" => 2,
  13398. "default" => 1,
  13399. "no_change" => 0
  13400. );
  13401. while (($key, $val) = each(%temperatureScale)) {
  13402. $temperatureScaleVal = $val if ($key eq $temperatureScale);
  13403. }
  13404. my $daylightSavingTimeVal = 0;
  13405. $daylightSavingTimeVal = 1 if (AttrVal($name, "daylightSavingTime", "supported") eq "not_supported");
  13406. my $timeNotation = AttrVal($name, "timeNotation", "no_change");
  13407. my $timeNotationVal = 0;
  13408. if ($timeNotation eq "no_change") {
  13409. $timeNotationVal = 0;
  13410. } elsif ($timeNotation eq "default") {
  13411. $timeNotationVal = 1;
  13412. } elsif ($timeNotation == 24) {
  13413. $timeNotationVal = 2;
  13414. } elsif ($timeNotation == 12) {
  13415. $timeNotationVal = 3;
  13416. }
  13417. $db4 = (($displayContentVal << 2 | $temperatureScaleVal) << 1 | $daylightSavingTimeVal) << 2 | $timeNotationVal;
  13418. my $db32 = ($day << 4 | $month) << 7 | $year - 2000;
  13419. if ($waitingCmds & 4) {
  13420. $db0 |= 1;
  13421. # clear time command
  13422. $waitingCmds &= 251 + 0xFF00;
  13423. }
  13424. if ($waitingCmds & 64) {
  13425. # clear config command
  13426. $waitingCmds &= 191 + 0xFF00;
  13427. }
  13428. $data = sprintf "%02X%02X%02X%02X%04X%02X%02X", $db7, $db6, $db5, $db4, $db32, $db1, $db0;
  13429. $response = "configuration message send $data";
  13430. $logLevel = 2;
  13431. } elsif ($waitingCmds & 2) {
  13432. # data message waiting
  13433. my ($db7, $db6, $db5, $db4, $db3, $db2, $db1, $db0) = (32, 0, 0, 0, 0, 0, 0, 0, 0);
  13434. if (defined ReadingsVal($name, "setpointTempSet", undef)) {
  13435. $db1 = ReadingsVal($name, "setpointTempSet", 0) * 255 / 40;
  13436. $db2 |= 2;
  13437. CommandDeleteReading(undef, "$name setpointTempSet");
  13438. }
  13439. my $heatingSet;
  13440. if (defined ReadingsVal($name, "heatingSet", undef)) {
  13441. $heatingSet = ReadingsVal($name, "heatingSet", 0);
  13442. } else {
  13443. $heatingSet = ReadingsVal($name, "heating", 0);
  13444. }
  13445. if ($heatingSet eq "no_change") {
  13446. $heatingSet = 0;
  13447. } elsif ($heatingSet eq "on") {
  13448. $heatingSet = 1;
  13449. } elsif ($heatingSet eq "off") {
  13450. $heatingSet = 2;
  13451. } elsif ($heatingSet eq "auto") {
  13452. $heatingSet = 3;
  13453. }
  13454. $db2 |= $heatingSet << 4;
  13455. CommandDeleteReading(undef, "$name heatingSet");
  13456. my $coolingSet;
  13457. if (defined ReadingsVal($name, "coolingSet", undef)) {
  13458. $coolingSet = ReadingsVal($name, "coolingSet", 0);
  13459. } else {
  13460. $coolingSet = ReadingsVal($name, "cooling", 0);
  13461. }
  13462. if ($coolingSet eq "no_change") {
  13463. $coolingSet = 0;
  13464. } elsif ($coolingSet eq "on") {
  13465. $coolingSet = 1;
  13466. } elsif ($coolingSet eq "off") {
  13467. $coolingSet = 2;
  13468. } elsif ($coolingSet eq "auto") {
  13469. $coolingSet = 3;
  13470. }
  13471. $db2 |= $coolingSet << 6;
  13472. CommandDeleteReading(undef, "$name coolingSet");
  13473. my $roomCtrlModeSet;
  13474. if (defined ReadingsVal($name, "roomCtrlModeSet", undef)) {
  13475. $roomCtrlModeSet = ReadingsVal($name, "roomCtrlModeSet", 0);
  13476. } else {
  13477. $roomCtrlModeSet = ReadingsVal($name, "roomCtrlMode", 0);
  13478. }
  13479. if ($roomCtrlModeSet eq "comfort") {
  13480. $roomCtrlModeSet = 0;
  13481. } elsif ($roomCtrlModeSet eq "economy") {
  13482. $roomCtrlModeSet = 1;
  13483. } elsif ($roomCtrlModeSet eq "preComfort") {
  13484. $roomCtrlModeSet = 2;
  13485. } elsif ($roomCtrlModeSet eq "buildingProtection") {
  13486. $roomCtrlModeSet = 3;
  13487. }
  13488. $db2 |= $roomCtrlModeSet << 2;
  13489. CommandDeleteReading(undef, "$name roomCtrlModeSet");
  13490. my $windowSet;
  13491. if (defined ReadingsVal($name, "windowSet", undef)) {
  13492. $windowSet = ReadingsVal($name, "windowSet", 0);
  13493. } else {
  13494. $windowSet = ReadingsVal($name, "window", 0);
  13495. }
  13496. if ($windowSet eq "no_change") {
  13497. $windowSet = 0;
  13498. } elsif ($windowSet eq "closed") {
  13499. $windowSet = 1;
  13500. } elsif ($windowSet eq "open") {
  13501. $windowSet = 2;
  13502. } elsif ($windowSet eq "reserved") {
  13503. $windowSet = 3;
  13504. }
  13505. $db4 |= $windowSet;
  13506. CommandDeleteReading(undef, "$name windowSet");
  13507. my $fanSpeedModeSet;
  13508. if (defined ReadingsVal($name, "fanSpeedModeSet", undef)) {
  13509. $fanSpeedModeSet = ReadingsVal($name, "fanSpeedModeSet", 0);
  13510. } else {
  13511. $fanSpeedModeSet = ReadingsVal($name, "fanSpeedMode", 0);
  13512. }
  13513. $db4 |= 64 if ($fanSpeedModeSet eq "local");
  13514. CommandDeleteReading(undef, "$name fanSpeedModeSet");
  13515. if (defined ReadingsVal($name, "fanSpeedSet", undef)) {
  13516. $db5 = ReadingsVal($name, "fanSpeedSet", 0);
  13517. $db4 |= 128;
  13518. CommandDeleteReading(undef, "$name fanSpeedSet");
  13519. }
  13520. $data = sprintf "%02X%02X%02X%02X%02X%02X%02X%02X", $db7, $db6, $db5, $db4, $db3, $db2, $db1, $db0;
  13521. # clear command
  13522. $waitingCmds = $waitingCmds & 253 + 0xFF00;
  13523. $response = "data message send";
  13524. $logLevel = 2;
  13525. } elsif ($waitingCmds & 512) {
  13526. # delete time program command waiting
  13527. $data = "800000000001";
  13528. # clear command
  13529. $waitingCmds = $waitingCmds & 0xFDFF;
  13530. $response = "delete time program send";
  13531. $logLevel = 2;
  13532. } elsif ($waitingCmds & 16) {
  13533. # time program setup waiting
  13534. my ($db5, $endMinute, $endHour, $startMinute, $startHour, $db0) = (128, 0, 0, 0, 0, 0, 0);
  13535. my ($key, $val);
  13536. my $messagePartCntr;
  13537. for ($messagePartCntr = 4; $messagePartCntr >= 1; $messagePartCntr --) {
  13538. if ($hash->{helper}{4}{telegramWait}{$messagePartCntr}) {
  13539. $hash->{helper}{4}{telegramWait}{$messagePartCntr} = 0;
  13540. my $timeProgram = AttrVal($name, "timeProgram" . $messagePartCntr, undef);
  13541. my @timeProgram = split("[ \t][ \t]*", $timeProgram);
  13542. my ($period, $roomCtrlMode) = ($timeProgram[0], $timeProgram[3]);
  13543. ($startHour, $startMinute) = split(':', $timeProgram[1]);
  13544. ($endHour, $endMinute) = split(':', $timeProgram[2]);
  13545. my $periodVal = 0;
  13546. my %period = ("FrMo" => 15,
  13547. "FrSu" => 14,
  13548. "ThFr" => 13,
  13549. "WeFr" => 12,
  13550. "TuTh" => 11,
  13551. "MoWe" => 10,
  13552. "Su" => 9,
  13553. "Sa" => 8,
  13554. "Fr" => 7,
  13555. "Th" => 6,
  13556. "We" => 5,
  13557. "Tu" => 4,
  13558. "Mo" => 3,
  13559. "SaSu" => 2,
  13560. "MoFr" => 1,
  13561. "MoSu" => 0
  13562. );
  13563. while (($key, $val) = each(%period)) {
  13564. $periodVal = $val if ($key eq $period);
  13565. }
  13566. if ($roomCtrlMode eq "buildingProtection") {
  13567. $roomCtrlMode = 3;
  13568. } elsif ($roomCtrlMode eq "preComfort") {
  13569. $roomCtrlMode = 2;
  13570. } elsif ($roomCtrlMode eq "economy") {
  13571. $roomCtrlMode = 1;
  13572. } else{
  13573. $roomCtrlMode = 0;
  13574. }
  13575. if ($messagePartCntr > 1) {
  13576. # set mcf flag
  13577. $db5 |= 1;
  13578. } else {
  13579. # clear command
  13580. $waitingCmds = $waitingCmds & 239 + 0xFF00;
  13581. }
  13582. $data = sprintf "%02X%02X%02X%02X%02X%02X", $db5, $endMinute, $endHour, $startMinute, $startHour, $periodVal << 4 | $roomCtrlMode << 2;
  13583. $response = "time program setup send";
  13584. $logLevel = 2;
  13585. last;
  13586. }
  13587. }
  13588. } elsif ($waitingCmds & 1) {
  13589. # data request waiting
  13590. $data = "0009";
  13591. # clear command
  13592. $waitingCmds = $waitingCmds & 254 + 0xFF00;
  13593. $response = "data request send";
  13594. $logLevel = 2;
  13595. } elsif ($waitingCmds & 32) {
  13596. # configuration request waiting
  13597. $data = "0011";
  13598. # clear command
  13599. $waitingCmds = $waitingCmds & 223 + 0xFF00;
  13600. $response = "configuration request send";
  13601. $logLevel = 2;
  13602. } elsif ($waitingCmds & 128) {
  13603. # room control setup request waiting
  13604. $data = "0019";
  13605. # clear command
  13606. $waitingCmds = $waitingCmds & 127 + 0xFF00;
  13607. $response = "room control setup request send";
  13608. $logLevel = 2;
  13609. } elsif ($waitingCmds & 256) {
  13610. # time program request waiting
  13611. $data = "0021";
  13612. # clear command
  13613. $waitingCmds = $waitingCmds & 0xFEFF;
  13614. $response = "time program request send";
  13615. $logLevel = 2;
  13616. }
  13617. if ($waitingCmds == 0) {
  13618. CommandDeleteReading(undef, "$name waitingCmds");
  13619. } else {
  13620. readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
  13621. }
  13622. } elsif ($mcf == 1) {
  13623. # message incomplete
  13624. $response = "acknowledge send, wating for next part of the message";
  13625. $logLevel = 2;
  13626. } elsif ($mcf == 2) {
  13627. # automatic message control
  13628. $response = "acknowledge send, automatic message control";
  13629. $logLevel = 2;
  13630. } elsif ($mcf == 3) {
  13631. # reserved
  13632. }
  13633. return ($err, $response, $data, $logLevel);
  13634. }
  13635. # create SVG devices
  13636. sub EnOcean_CreateSVG($$$)
  13637. {
  13638. my ($ctrl, $hash, $eepSVG) = @_;
  13639. my $name = $hash->{NAME};
  13640. my ($autocreateHash, $autocreateName, $autocreateDeviceRoom, $autocreateWeblinkRoom) =
  13641. (undef, 'autocreate', 'EnOcean', 'Plots');
  13642. my $filelogName = "FileLog_$name";
  13643. my ($cmd, $eep, $weblinkName, $weblinkHash, $ret);
  13644. if (defined($eepSVG) && $eepSVG =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i) {
  13645. $eep = uc("$1.$2.$3");
  13646. } elsif (exists($attr{$name}{eep}) && $attr{$name}{eep} =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i) {
  13647. $eep = uc("$1.$2.$3");
  13648. } else {
  13649. return undef;
  13650. }
  13651. # find autocreate device
  13652. while (($autocreateName, $autocreateHash) = each(%defs)) {
  13653. last if ($defs{$autocreateName}{TYPE} eq "autocreate");
  13654. }
  13655. # delete old SVG devices
  13656. if (defined($ctrl) && $ctrl eq 'del' || !exists($defs{$filelogName})) {
  13657. while (($weblinkName, $weblinkHash) = each(%defs)) {
  13658. if ($weblinkName =~ /^SVG_$name.*/) {
  13659. CommandDelete(undef, $weblinkName);
  13660. Log3 $hash->{NAME}, 5, "EnOcean_CreateSVG: device $weblinkName deleted";
  13661. }
  13662. }
  13663. }
  13664. if (!defined(AttrVal($autocreateName, "disable", undef)) && exists($defs{$filelogName})) {
  13665. if (exists $EnO_eepConfig{$eep}{GPLOT}) {
  13666. # add GPLOT parameters
  13667. $attr{$filelogName}{logtype} = $EnO_eepConfig{$eep}{GPLOT} . $attr{$filelogName}{logtype}
  13668. if (!exists($attr{$filelogName}{logtype}) || $attr{$filelogName}{logtype} eq 'text');
  13669. if (AttrVal($autocreateName, "weblink", 1)) {
  13670. $autocreateWeblinkRoom = $attr{$autocreateName}{weblink_room} if (exists $attr{$autocreateName}{weblink_room});
  13671. $autocreateWeblinkRoom = 'EnOcean' if ($autocreateWeblinkRoom eq '%TYPE');
  13672. $autocreateWeblinkRoom = $name if ($autocreateWeblinkRoom eq '%NAME');
  13673. $autocreateWeblinkRoom = $attr{$name}{room} if (exists $attr{$name}{room});
  13674. my $wnr = 1;
  13675. #create SVG devices
  13676. foreach my $wdef (split(/,/, $EnO_eepConfig{$eep}{GPLOT})) {
  13677. next if(!$wdef);
  13678. my ($gplotfile, $stuff) = split(/:/, $wdef);
  13679. next if(!$gplotfile);
  13680. $weblinkName = "SVG_$name";
  13681. $weblinkName .= "_$wnr" if($wnr > 1);
  13682. $wnr++;
  13683. next if (exists $defs{$weblinkName});
  13684. $cmd = "$weblinkName SVG $filelogName:$gplotfile:CURRENT";
  13685. Log3 $weblinkName, 2, "EnOcean define $cmd";
  13686. $ret = CommandDefine(undef, $cmd);
  13687. if($ret) {
  13688. Log3 $weblinkName, 2, "EnOcean ERROR: define $cmd: $ret";
  13689. last;
  13690. }
  13691. $attr{$weblinkName}{room} = $autocreateWeblinkRoom;
  13692. $attr{$weblinkName}{title} = '"' . $name . ' Min $data{min1}, Max $data{max1}, Last $data{currval1}"';
  13693. $ret = CommandSet(undef, "$weblinkName copyGplotFile");
  13694. if($ret) {
  13695. Log3 $weblinkName, 2, "EnOcean ERROR: set $weblinkName copyGplotFile: $ret";
  13696. last;
  13697. }
  13698. }
  13699. }
  13700. }
  13701. }
  13702. return undef;
  13703. }
  13704. #CommandSave
  13705. sub EnOcean_CommandSave($$)
  13706. {
  13707. my ($ctrl, $param) = @_;
  13708. # find autocreate device
  13709. my ($autocreateHash, $autocreateName);
  13710. while (($autocreateName, $autocreateHash) = each(%defs)) {
  13711. last if ($defs{$autocreateName}{TYPE} eq "autocreate");
  13712. }
  13713. my $autosave = AttrVal($autocreateName, "autosave", undef);
  13714. if (!defined $autosave) {
  13715. CommandSave($ctrl, $param) if (AttrVal("global", "autosave", 1));
  13716. } elsif ($autosave) {
  13717. CommandSave($ctrl, $param);
  13718. }
  13719. return;
  13720. }
  13721. sub EnOcean_readingsSingleUpdate($) {
  13722. my ($readingParam) = @_;
  13723. my ($hash, $readingName, $readingVal, $ctrl, $log) = @$readingParam;
  13724. if (defined $hash) {
  13725. readingsSingleUpdate($hash, $readingName, $readingVal, $ctrl) ;
  13726. Log3 $hash->{NAME}, $log, "EnOcean " . $hash->{NAME} . " EVENT $readingName: $readingVal" if ($log);
  13727. }
  13728. return;
  13729. }
  13730. sub EnOcean_4BSRespWait($$$) {
  13731. my ($ctrl, $hash, $subDef) = @_;
  13732. my $IODev = $hash->{IODev}{NAME};
  13733. my $IOHash = $defs{$IODev};
  13734. $hash->{IODev}{helper}{"4BSRespWait"}{$subDef}{teachInReq} = "out";
  13735. $hash->{IODev}{helper}{"4BSRespWait"}{$subDef}{hash} = $hash;
  13736. if (!exists($hash->{IODev}{Teach})) {
  13737. # enable teach-in receiving for 3 sec
  13738. $hash->{IODev}{Teach} = 1;
  13739. #####
  13740. #my %timeoutHash = (hash => $IOHash, function => "4BSRespTimeout", helper => "4BSRespWait");
  13741. #RemoveInternalTimer(\%timeoutHash);
  13742. #InternalTimer(gettimeofday() + 3, "EnOcean_RespTimeout", \%timeoutHash, 0);
  13743. RemoveInternalTimer($hash->{helper}{timer}{"4BSRespTimeout"}) if(exists $hash->{helper}{timer}{"4BSRespTimeout"});
  13744. $hash->{helper}{timer}{"4BSRespTimeout"} = {hash => $IOHash, function => "4BSRespTimeout", helper => "4BSRespWait"};
  13745. InternalTimer(gettimeofday() + 3, 'EnOcean_RespTimeout', $hash->{helper}{timer}{"4BSRespTimeout"}, 0);
  13746. }
  13747. return;
  13748. }
  13749. # Check SenderIDs
  13750. sub EnOcean_CheckSenderID($$$)
  13751. {
  13752. my ($ctrl, $IODev, $senderID) = @_;
  13753. if (!defined $IODev) {
  13754. my (@listIODev, %listIODev);
  13755. foreach my $dev (keys %defs) {
  13756. next if ($defs{$dev}{TYPE} ne "EnOcean");
  13757. push(@listIODev, $defs{$dev}{IODev}{NAME});
  13758. }
  13759. @listIODev = sort grep(!$listIODev{$_}++, @listIODev);
  13760. if (@listIODev == 1) {
  13761. $IODev = $listIODev[0];
  13762. }
  13763. }
  13764. my $unusedID = 0;
  13765. $unusedID = hex($defs{$IODev}{BaseID}) if ($defs{$IODev}{BaseID});
  13766. my $IDCntr1;
  13767. my $IDCntr2;
  13768. if ($unusedID == 0) {
  13769. $IDCntr1 = 0;
  13770. $IDCntr2 = 0;
  13771. } else {
  13772. $IDCntr1 = $unusedID + 1;
  13773. $IDCntr2 = $unusedID + 127;
  13774. }
  13775. if ($ctrl eq "getBaseID") {
  13776. # get TCM BaseID of the EnOcean device
  13777. if ($defs{$IODev}{BaseID}) {
  13778. $senderID = $defs{$IODev}{BaseID}
  13779. } else {
  13780. $senderID = "0" x 8;
  13781. }
  13782. } elsif ($ctrl eq "getUsedID") {
  13783. # find and sort used SenderIDs
  13784. my @listID;
  13785. my %listID;
  13786. foreach my $dev (keys %defs) {
  13787. next if ($defs{$dev}{TYPE} ne "EnOcean");
  13788. push(@listID, grep(hex($_) >= $IDCntr1 && hex($_) <= $IDCntr2, $defs{$dev}{DEF}));
  13789. push(@listID, $attr{$dev}{subDef}) if ($attr{$dev}{subDef});
  13790. push(@listID, $attr{$dev}{subDefA}) if ($attr{$dev}{subDefA});
  13791. push(@listID, $attr{$dev}{subDefB}) if ($attr{$dev}{subDefB});
  13792. push(@listID, $attr{$dev}{subDefC}) if ($attr{$dev}{subDefC});
  13793. push(@listID, $attr{$dev}{subDefD}) if ($attr{$dev}{subDefD});
  13794. push(@listID, $attr{$dev}{subDefI}) if ($attr{$dev}{subDefI});
  13795. push(@listID, $attr{$dev}{subDefH}) if ($attr{$dev}{subDefH});
  13796. push(@listID, $attr{$dev}{subDefW}) if ($attr{$dev}{subDefW});
  13797. push(@listID, $attr{$dev}{subDef0}) if ($attr{$dev}{subDef0});
  13798. }
  13799. $senderID = join(" ", sort grep(!$listID{$_}++, @listID));
  13800. } elsif ($ctrl eq "getFreeID") {
  13801. # find and sort free SenderIDs
  13802. my (@freeID, @listID, %listID, @intersection, @difference, %count, $element);
  13803. for (my $IDCntr = $IDCntr1; $IDCntr <= $IDCntr2; $IDCntr++) {
  13804. push(@freeID, sprintf "%08X", $IDCntr);
  13805. }
  13806. foreach my $dev (keys %defs) {
  13807. next if ($defs{$dev}{TYPE} ne "EnOcean");
  13808. push(@listID, grep(hex($_) >= $IDCntr1 && hex($_) <= $IDCntr2, $defs{$dev}{DEF}));
  13809. push(@listID, $attr{$dev}{subDef}) if ($attr{$dev}{subDef} && $attr{$dev}{subDef} ne "00000000");
  13810. push(@listID, $attr{$dev}{subDefA}) if ($attr{$dev}{subDefA} && $attr{$dev}{subDefA} ne "00000000");
  13811. push(@listID, $attr{$dev}{subDefB}) if ($attr{$dev}{subDefB} && $attr{$dev}{subDefB} ne "00000000");
  13812. push(@listID, $attr{$dev}{subDefC}) if ($attr{$dev}{subDefC} && $attr{$dev}{subDefC} ne "00000000");
  13813. push(@listID, $attr{$dev}{subDefD}) if ($attr{$dev}{subDefD} && $attr{$dev}{subDefD} ne "00000000");
  13814. push(@listID, $attr{$dev}{subDefI}) if ($attr{$dev}{subDefI} && $attr{$dev}{subDefI} ne "00000000");
  13815. push(@listID, $attr{$dev}{subDefH}) if ($attr{$dev}{subDefH} && $attr{$dev}{subDefH} ne "00000000");
  13816. push(@listID, $attr{$dev}{subDefW}) if ($attr{$dev}{subDefW} && $attr{$dev}{subDefW} ne "00000000");
  13817. push(@listID, $attr{$dev}{subDef0}) if ($attr{$dev}{subDef0} && $attr{$dev}{subDef0} ne "00000000");
  13818. }
  13819. @listID = sort grep(!$listID{$_}++, @listID);
  13820. foreach $element (@listID, @freeID) {
  13821. $count{$element}++
  13822. }
  13823. foreach $element (keys %count) {
  13824. push @{$count{$element} > 1 ? \@intersection : \@difference }, $element;
  13825. }
  13826. $senderID = ':' . join(" ", sort @difference);
  13827. } elsif ($ctrl eq "getNextID") {
  13828. # get next free SenderID
  13829. my (@freeID, @listID, %listID, @intersection, @difference, %count, $element);
  13830. for (my $IDCntr = $IDCntr1; $IDCntr <= $IDCntr2; $IDCntr++) {
  13831. push(@freeID, sprintf "%08X", $IDCntr);
  13832. }
  13833. foreach my $dev (keys %defs) {
  13834. next if ($defs{$dev}{TYPE} ne "EnOcean");
  13835. push(@listID, grep(hex($_) >= $IDCntr1 && hex($_) <= $IDCntr2, $defs{$dev}{DEF}));
  13836. push(@listID, $attr{$dev}{subDef}) if ($attr{$dev}{subDef} && $attr{$dev}{subDef} ne "00000000");
  13837. push(@listID, $attr{$dev}{subDefA}) if ($attr{$dev}{subDefA} && $attr{$dev}{subDefA} ne "00000000");
  13838. push(@listID, $attr{$dev}{subDefB}) if ($attr{$dev}{subDefB} && $attr{$dev}{subDefB} ne "00000000");
  13839. push(@listID, $attr{$dev}{subDefC}) if ($attr{$dev}{subDefC} && $attr{$dev}{subDefC} ne "00000000");
  13840. push(@listID, $attr{$dev}{subDefD}) if ($attr{$dev}{subDefD} && $attr{$dev}{subDefD} ne "00000000");
  13841. push(@listID, $attr{$dev}{subDefI}) if ($attr{$dev}{subDefI} && $attr{$dev}{subDefI} ne "00000000");
  13842. push(@listID, $attr{$dev}{subDefH}) if ($attr{$dev}{subDefH} && $attr{$dev}{subDefH} ne "00000000");
  13843. push(@listID, $attr{$dev}{subDefW}) if ($attr{$dev}{subDefW} && $attr{$dev}{subDefW} ne "00000000");
  13844. push(@listID, $attr{$dev}{subDef0}) if ($attr{$dev}{subDef0} && $attr{$dev}{subDef0} ne "00000000");
  13845. }
  13846. @listID = sort grep(!$listID{$_}++, @listID);
  13847. foreach $element (@listID, @freeID) {
  13848. $count{$element}++
  13849. }
  13850. foreach $element (keys %count) {
  13851. push @{$count{$element} > 1 ? \@intersection : \@difference }, $element;
  13852. }
  13853. @difference = sort @difference;
  13854. if (defined $difference[0]) {
  13855. $senderID = $difference[0];
  13856. } else {
  13857. $senderID = "0" x 8;
  13858. Log3 $IODev, 2, "EnOcean $IODev no free senderIDs available";
  13859. }
  13860. } else {
  13861. }
  13862. return $senderID;
  13863. }
  13864. # assign next free SenderID
  13865. sub EnOcean_AssignSenderID($$$$)
  13866. {
  13867. my ($ctrl, $hash, $attrName, $comMode) = @_;
  13868. my $def = $hash->{DEF};
  13869. my $err;
  13870. my $name = $hash->{NAME};
  13871. my $IODev = $hash->{IODev}{NAME};
  13872. my $senderID = AttrVal($name, $attrName, "");
  13873. # SenderID valid
  13874. return ($err, $senderID) if ($senderID =~ m/^[\dA-Fa-f]{8}$/);
  13875. return ("no IODev", $def) if (!defined $IODev);
  13876. # DEF is SenderID
  13877. if (hex($def) >= hex($defs{$IODev}{BaseID}) && hex($def) <= hex($defs{$IODev}{BaseID}) + 127) {
  13878. if ($comMode eq "biDir") {
  13879. $attr{$name}{comMode} = $comMode;
  13880. } else {
  13881. $attr{$name}{comMode} = "uniDir";
  13882. }
  13883. return ($err, $def);
  13884. } else {
  13885. if ($comMode eq "biDir") {
  13886. $attr{$name}{comMode} = $comMode;
  13887. } else {
  13888. $attr{$name}{comMode} = "confirm";
  13889. }
  13890. $senderID = EnOcean_CheckSenderID("getNextID", $IODev, "00000000");
  13891. }
  13892. #Log3 $name, 2, "EnOcean $name SenderID: $senderID assigned";
  13893. #CommandAttr(undef, "$name $attrName $senderID");
  13894. $attr{$name}{$attrName} = $senderID;
  13895. return ($err, $senderID);
  13896. }
  13897. # split chained data message
  13898. sub EnOcean_SndCdm($$$$$$$$)
  13899. {
  13900. my ($ctrl, $hash, $packetType, $rorg, $data, $senderID, $status, $destinationID) = @_;
  13901. if (!defined $data) {
  13902. Log3 $hash->{NAME}, 5, "EnOcean $hash->{NAME} EnOcean_SndCDM SenderID: $senderID DestinationID: $destinationID " .
  13903. "PacketType: $packetType RORG: $rorg DATA: undef STATUS: $status";
  13904. return;
  13905. }
  13906. my ($seq, $idx, $len, $dataPart, $dataPartLen) = (0, 0, length($data) / 2, undef, 14);
  13907. # split telelegram with optional data
  13908. $dataPartLen = 9 if ($destinationID ne "FFFFFFFF");
  13909. if ($packetType == 1 && $len > $dataPartLen) {
  13910. # first CDM telegram
  13911. if ($dataPartLen == 14) {
  13912. $data =~ m/^(....................)(.*)$/;
  13913. $dataPart = (sprintf "%02X", $seq << 6 | $idx) . (sprintf "%04X", $len) . $rorg . $1;
  13914. $data = $2;
  13915. } else {
  13916. $data =~ m/^(..........)(.*)$/;
  13917. $dataPart = (sprintf "%02X", $seq << 6 | $idx) . (sprintf "%04X", $len) . $rorg . $1;
  13918. $data = $2;
  13919. }
  13920. $idx ++;
  13921. $len -= $dataPartLen - 5;
  13922. EnOcean_SndRadio($ctrl, $hash, $packetType, "40", $dataPart, $senderID, $status, $destinationID);
  13923. while ($len > 0) {
  13924. if ($len > $dataPartLen - 2) {
  13925. if ($dataPartLen == 14) {
  13926. $data =~ m/^(..........................)(.*)$/;
  13927. $dataPart = (sprintf "%02X", $seq << 6 | $idx) . $1;
  13928. $data = $2;
  13929. } else {
  13930. $data =~ m/^(................)(.*)$/;
  13931. $dataPart = (sprintf "%02X", $seq << 6 | $idx) . $1;
  13932. $data = $2;
  13933. }
  13934. $idx ++;
  13935. $len -= $dataPartLen - 2;
  13936. } else {
  13937. $dataPart = (sprintf "%02X", $seq << 6 | $idx) . $data;
  13938. $len = 0;
  13939. }
  13940. EnOcean_SndRadio($ctrl, $hash, $packetType, "40", $dataPart, $senderID, $status, $destinationID);
  13941. }
  13942. } else {
  13943. # not necessary to split
  13944. EnOcean_SndRadio($ctrl, $hash, $packetType, $rorg, $data, $senderID, $status, $destinationID);
  13945. }
  13946. return;
  13947. }
  13948. # send ESP3 Packet Type Radio
  13949. sub EnOcean_SndRadio($$$$$$$$)
  13950. {
  13951. my ($ctrl, $hash, $packetType, $rorg, $data, $senderID, $status, $destinationID) = @_;
  13952. if (!defined $data) {
  13953. Log3 $hash->{NAME}, 5, "EnOcean $hash->{NAME} EnOcean_SndRadio SenderID: $senderID DestinationID: $destinationID " .
  13954. "PacketType: $packetType RORG: $rorg DATA: undef STATUS: $status";
  13955. return;
  13956. }
  13957. my ($err, $response, $loglevel);
  13958. my $header;
  13959. my $odata = "";
  13960. my $odataLength = 0;
  13961. if ($packetType == 1) {
  13962. ($err, $rorg, $data, $response, $loglevel) = EnOcean_sec_convertToSecure($hash, $packetType, $rorg, $data);
  13963. if (defined $err) {
  13964. Log3 $hash->{NAME}, $loglevel, "EnOcean $hash->{NAME} Error: $err";
  13965. return "";
  13966. }
  13967. if (AttrVal($hash->{NAME}, "repeatingAllowed", "yes") eq "no") {
  13968. $status = substr($status, 0, 1) . "F";
  13969. }
  13970. if ($rorg eq "A6") {
  13971. # ADT telegram
  13972. $data .= $destinationID;
  13973. } elsif ($destinationID ne "FFFFFFFF") {
  13974. # SubTelNum = 03, DestinationID:8, RSSI = FF, secLevel = 00
  13975. $odata = sprintf "03%sFF00", $destinationID;
  13976. $odataLength = 7;
  13977. }
  13978. # Data Length:4 Optional Length:2 Packet Type:2
  13979. $header = sprintf "%04X%02X%02X", (length($data) / 2 + 6), $odataLength, $packetType;
  13980. Log3 $hash->{NAME}, 4, "EnOcean $hash->{NAME} sent PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status ODATA: $odata";
  13981. $data = $rorg . $data . $senderID . $status . $odata;
  13982. } elsif ($packetType == 2 || $packetType == 6) {
  13983. # smart ack commands
  13984. # Data Length:4 Optional Length:2 Packet Type:2
  13985. $header = sprintf "%04X%02X%02X", length($data) / 2, 0, $packetType;
  13986. Log3 $hash->{NAME}, 4, "EnOcean $hash->{NAME} sent PacketType: $packetType DATA: $data";
  13987. } elsif ($packetType == 7) {
  13988. my $delay = 0;
  13989. $delay = 1 if ($destinationID eq "FFFFFFFF");
  13990. $senderID = "00000000";
  13991. #$senderID = "00000000" if ($destinationID eq "FFFFFFFF");
  13992. $odata = sprintf "%s%sFF%02X", $destinationID, $senderID, $delay;
  13993. $odataLength = 10;
  13994. # Data Length:4 Optional Length:2 Packet Type:2
  13995. $header = sprintf "%04X%02X%02X", (length($data) / 2), $odataLength, $packetType;
  13996. Log3 $hash->{NAME}, 4, "EnOcean $hash->{NAME} sent PacketType: $packetType DATA: $data ODATA: $odata";
  13997. $data .= $odata;
  13998. }
  13999. if (defined $ctrl) {
  14000. # sent telegram delayed
  14001. my @param = ($hash, $header, $data);
  14002. InternalTimer(gettimeofday() + $ctrl, 'EnOcean_IOWriteTimer', \@param, 0);
  14003. } else {
  14004. IOWrite($hash, $header, $data);
  14005. }
  14006. return;
  14007. }
  14008. #
  14009. sub EnOcean_IOWriteTimer($)
  14010. {
  14011. my ($ioParam) = @_;
  14012. my ($hash, $header, $data) = @$ioParam;
  14013. IOWrite($hash, $header, $data);
  14014. return;
  14015. }
  14016. # Scale Readings
  14017. sub EnOcean_ReadingScaled($$$$)
  14018. {
  14019. my ($hash, $readingVal, $readingMin, $readingMax) = @_;
  14020. my $name = $hash->{NAME};
  14021. my $valScaled;
  14022. my $scaleDecimals = AttrVal($name, "scaleDecimals", undef);
  14023. my $scaleMin = AttrVal($name, "scaleMin", undef);
  14024. my $scaleMax = AttrVal($name, "scaleMax", undef);
  14025. if (defined $scaleMax && defined $scaleMin &&
  14026. $scaleMax =~ m/^[+-]?\d+(\.\d+)?$/ && $scaleMin =~ m/^[+-]?\d+(\.\d+)?$/) {
  14027. $valScaled = ($readingMin*$scaleMax-$scaleMin*$readingMax)/
  14028. ($readingMin-$readingMax)+
  14029. ($scaleMin-$scaleMax)/($readingMin-$readingMax)*$readingVal;
  14030. }
  14031. if (defined $scaleDecimals && $scaleDecimals =~ m/^[0-9]?$/) {
  14032. $scaleDecimals = "%0." . $scaleDecimals . "f";
  14033. $valScaled = sprintf "$scaleDecimals", $valScaled;
  14034. }
  14035. return $valScaled;
  14036. }
  14037. # Reorganize Strings
  14038. sub EnOcean_ReorgList($)
  14039. {
  14040. my ($list) = @_;
  14041. my @list = split("[ \t][ \t]*", $list);
  14042. my %list;
  14043. @list = sort grep(!$list{$_}++, @list);
  14044. $list = join(" ", @list) . " ";
  14045. return $list;
  14046. }
  14047. # EnOcean_Set called from sub InternalTimer()
  14048. sub EnOcean_TimerSet($)
  14049. {
  14050. my ($par) = @_;
  14051. EnOcean_Set($par->{hash}, @{$par->{timerCmd}});
  14052. }
  14053. #
  14054. sub EnOcean_InternalTimer($$$$$)
  14055. {
  14056. my ($modifier, $tim, $callback, $hash, $waitIfInitNotDone) = @_;
  14057. my $mHash = {};
  14058. my $timerName = "$hash->{NAME}_$modifier";
  14059. if ($modifier eq "") {
  14060. $mHash = $hash;
  14061. } else {
  14062. if (exists ($hash->{helper}{timer}{$timerName})) {
  14063. $mHash = $hash->{helper}{timer}{$timerName};
  14064. Log3 $hash->{NAME}, 5, "EnOcean_InternalTimer setting mHash with stored $timerName";
  14065. } else {
  14066. $mHash = {HASH => $hash, NAME => $timerName, MODIFIER => $modifier};
  14067. $hash->{helper}{timer}{$timerName} = $mHash;
  14068. Log3 $hash->{NAME}, 5, "EnOcean_InternalTimer setting mHash with $timerName";
  14069. }
  14070. }
  14071. InternalTimer($tim, $callback, $mHash, $waitIfInitNotDone);
  14072. Log3 $hash->{NAME}, 5, "EnOcean setting timer $timerName at " . strftime("%Y-%m-%d %H:%M:%S", localtime($tim));
  14073. return;
  14074. }
  14075. #
  14076. sub EnOcean_RemoveInternalTimer($$)
  14077. {
  14078. my ($modifier, $hash) = @_;
  14079. my $mHash = {};
  14080. my $timerName = "$hash->{NAME}_$modifier";
  14081. if ($modifier eq "") {
  14082. RemoveInternalTimer($hash);
  14083. } else {
  14084. $mHash = $hash->{helper}{timer}{$timerName};
  14085. if (defined($mHash)) {
  14086. delete $hash->{helper}{timer}{$timerName};
  14087. RemoveInternalTimer($mHash);
  14088. }
  14089. }
  14090. Log3 $hash->{NAME}, 5, "EnOcean removing timer $timerName";
  14091. return;
  14092. }
  14093. #
  14094. sub EnOcean_observeInit($$@)
  14095. {
  14096. #init observe
  14097. my ($ctrl, $hash, @cmdValue) = @_;
  14098. my ($err, $name) = (undef, $hash->{NAME});
  14099. return (undef, $ctrl) if (lc(AttrVal($name, "observe", "off")) eq "off");
  14100. return (undef, $ctrl) if (defined($hash->{helper}{observeCntr}) && $hash->{helper}{observeCntr} > 0);
  14101. $hash->{helper}{observeCntr} = 1;
  14102. #my @observeExeptions = split("[ \t][ \t]*", AttrVal($name, "observeExeptions", ""));
  14103. my @observeRefDev = split("[ \t][ \t]*", AttrVal($name, "observeRefDev", $name));
  14104. $hash->{helper}{observeRefDev} = \@observeRefDev;
  14105. Log3 $name, 4, "EnOcean $name < observeRefDev " . join(" ", @{$hash->{helper}{observeRefDev}}) . " (init)";
  14106. $hash->{helper}{lastCmdFunction} = "set";
  14107. # @cmdValue = (<name>, <cmd>, <param1>, <param2>, ...)
  14108. $hash->{helper}{lastCmdValue} = \@cmdValue;
  14109. my $observeCmds = AttrVal($name, "observeCmds", undef);
  14110. my @observeCmds;
  14111. #my %observeCmds;
  14112. my ($cmdPair, $cmdSent, $cmdReceived);
  14113. if (defined $observeCmds) {
  14114. @observeCmds = split("[ \t][ \t]*", $observeCmds);
  14115. foreach my $cmdPair (@observeCmds) {
  14116. ($cmdSent, $cmdReceived) = split(':', $cmdPair);
  14117. $hash->{helper}{observeCmds}{$cmdSent} = $cmdReceived;
  14118. }
  14119. } else {
  14120. $hash->{helper}{observeCmds}{$cmdValue[1]} = $cmdValue[1];
  14121. }
  14122. $hash->{helper}{observeCntr} = 1;
  14123. #CommandDeleteReading(undef, "$name observeFailedDev");
  14124. readingsSingleUpdate($hash, "observeFailedDev", "", 0);
  14125. #####
  14126. #my %functionHash = (hash => $hash, function => "observe");
  14127. #RemoveInternalTimer(\%functionHash);
  14128. #InternalTimer(gettimeofday() + AttrVal($name, "observeInterval", 1), "EnOcean_observeRepeat", \%functionHash, 0);
  14129. RemoveInternalTimer($hash->{helper}{timer}{observe}) if(exists $hash->{helper}{timer}{observe});
  14130. $hash->{helper}{timer}{observe} = {hash => $hash, function => "observe"};
  14131. InternalTimer(gettimeofday() + AttrVal($name, "observeInterval", 1), "EnOcean_observeRepeat", $hash->{helper}{timer}{observe}, 0);
  14132. Log3 $name, 4, "EnOcean set " . join(" ", @cmdValue) . " observing started";
  14133. return ($err, $ctrl);
  14134. }
  14135. #
  14136. sub EnOcean_observeParse($$@)
  14137. {
  14138. # observe acknowledge
  14139. my ($ctrl, $hash, @cmdValue) = @_;
  14140. my ($err, $name) = (undef, $hash->{NAME});
  14141. return (undef, $ctrl) if (lc(AttrVal($name, "observe", "off")) eq "off");
  14142. # observing disabled or ignore second and following acknowledgment telegrams
  14143. return (undef, $ctrl) if (!exists($hash->{helper}{observeRefDev}) || @{$hash->{helper}{observeRefDev}} == 0);
  14144. my $devName = shift @cmdValue;
  14145. my $cmd = shift @cmdValue;
  14146. $cmd = shift @cmdValue if ($cmd eq "state");
  14147. my ($observeRefDevIdx) = grep{$hash->{helper}{observeRefDev}[$_] eq $devName} 0..$#{$hash->{helper}{observeRefDev}};
  14148. # device not observed
  14149. return (undef, $ctrl) if (!defined $observeRefDevIdx);
  14150. Log3 $name, 4, "EnOcean $name < observeRefDev " . join(" ", @{$hash->{helper}{observeRefDev}}) . " parsed";
  14151. Log3 $name, 4, "EnOcean $name < $devName $cmd " . join(" ", @cmdValue) . " received";
  14152. if (@{$hash->{helper}{observeRefDev}} == 1) {
  14153. # last acknowledgment telegram stops observing
  14154. delete $hash->{helper}{observeCmds};
  14155. delete $hash->{helper}{observeCntr};
  14156. delete $hash->{helper}{observeRefDev};
  14157. delete $hash->{helper}{lastCmdFunction};
  14158. delete $hash->{helper}{lastCmdValue};
  14159. #####
  14160. #my %functionHash = (hash => $hash, function => "observe");
  14161. #RemoveInternalTimer(\%functionHash);
  14162. RemoveInternalTimer($hash->{helper}{timer}{observe}) if(exists $hash->{helper}{timer}{observe});
  14163. Log3 $name, 4, "EnOcean $name < $devName $cmd " . join(" ", @cmdValue) . " observing stopped";
  14164. } else {
  14165. # remove the device that has sent a telegram
  14166. Log3 $name, 4, "EnOcean $name < observeRefDev " . $hash->{helper}{observeRefDev}[$observeRefDevIdx] . " removed";
  14167. splice(@{$hash->{helper}{observeRefDev}}, $observeRefDevIdx, 1);
  14168. if (lc(AttrVal($name, "observeLogic", "or")) eq "and") {
  14169. # AND logic: remove the device that has sent a telegram and await further telegrams
  14170. Log3 $name, 4, "EnOcean $name < observeRefDev " . join(" ", @{$hash->{helper}{observeRefDev}}) . " observing continued";
  14171. } else {
  14172. # OR logic: last acknowledgment telegram stops observing
  14173. #shift @{$hash->{helper}{observeRefDev}};
  14174. delete $hash->{helper}{observeCmds};
  14175. delete $hash->{helper}{observeCntr};
  14176. delete $hash->{helper}{observeRefDev};
  14177. delete $hash->{helper}{lastCmdFunction};
  14178. delete $hash->{helper}{lastCmdValue};
  14179. #####
  14180. #my %functionHash = (hash => $hash, function => "observe");
  14181. #RemoveInternalTimer(\%functionHash);
  14182. RemoveInternalTimer($hash->{helper}{timer}{observe}) if(exists $hash->{helper}{timer}{observe});
  14183. Log3 $name, 4, "EnOcean $name < $devName $cmd " . join(" ", @cmdValue) . " observing stopped";
  14184. }
  14185. }
  14186. # if (defined($hash->{helper}{lastCmdValue}[1]) && $hash->{helper}{lastCmdValue}[1] eq $cmd) {
  14187. # acknowledgment telegram ok, cancel the repetition of the command
  14188. # } else {
  14189. # acknowledgment telegram does not match the telegram sent, cancel the repetition of the command
  14190. # }
  14191. return ($err, $ctrl);
  14192. }
  14193. #
  14194. sub EnOcean_observeRepeat($)
  14195. {
  14196. #timer expires without acknowledgment telegram, repeat command
  14197. my ($functionHash) = @_;
  14198. my $hash = $functionHash->{hash};
  14199. my $name = $hash->{NAME};
  14200. return if (AttrVal($name, "observe", "off") eq "off" || !defined($hash->{helper}{observeCntr}));
  14201. #my @observeExeptions = split("[ \t][ \t]*", AttrVal($name, "observeExeptions", ""));
  14202. if ($hash->{helper}{observeCntr} <= AttrVal($name, "observeCmdRepetition", 2)) {
  14203. #repeat last command
  14204. $hash->{helper}{observeCntr} += 1;
  14205. Log3 $name, 4, "EnOcean set " . join(" ", @{$hash->{helper}{lastCmdValue}}) . " repeated";
  14206. EnOcean_Set($hash, @{$hash->{helper}{lastCmdValue}});
  14207. RemoveInternalTimer($functionHash);
  14208. InternalTimer(gettimeofday() + AttrVal($name, "observeInterval", 1), "EnOcean_observeRepeat", $functionHash, 0);
  14209. } else {
  14210. # reached the maximum number of retries, clear last command
  14211. Log3 $name, 2, "EnOcean set " . join(" ", @{$hash->{helper}{lastCmdValue}}) .
  14212. " observing " . join(" ", @{$hash->{helper}{observeRefDev}}) . " failed";
  14213. #splice(@{$hash->{helper}{observeRefDev}}, 0);
  14214. my $actionCmd = AttrVal($name, "observeErrorAction", undef);
  14215. if (defined $actionCmd) {
  14216. # error action exec
  14217. my %specials = ("%NAME" => shift(@{$hash->{helper}{lastCmdValue}}),
  14218. "%FAILEDDEV" => join(" ", @{$hash->{helper}{observeRefDev}}),
  14219. "%TYPE" => $hash->{TYPE},
  14220. "%EVENT" => ReplaceEventMap($name, join(" ", @{$hash->{helper}{lastCmdValue}}), 1)
  14221. );
  14222. $actionCmd = EvalSpecials($actionCmd, %specials);
  14223. my $ret = AnalyzeCommandChain(undef, $actionCmd);
  14224. Log3 $name, 2, "EnOcean $name observeErrorAction ERROR: $ret" if($ret);
  14225. }
  14226. readingsSingleUpdate($hash, "observeFailedDev", join(" ", @{$hash->{helper}{observeRefDev}}), 1);
  14227. delete $hash->{helper}{observeCmds};
  14228. delete $hash->{helper}{observeCntr};
  14229. delete $hash->{helper}{observeRefDev};
  14230. delete $hash->{helper}{lastCmdFunction};
  14231. delete $hash->{helper}{lastCmdValue};
  14232. RemoveInternalTimer($functionHash);
  14233. }
  14234. return;
  14235. }
  14236. sub EnOcean_RLT($) {
  14237. # Radio Link Test
  14238. my ($rltParam) = @_;
  14239. my ($ctrl, $hash, $dataRx, $subDef, $rltMode, $rssiMaster) = @$rltParam;
  14240. my $name = $hash->{NAME};
  14241. my $def = $hash->{DEF};
  14242. my ($err, $logLevel, $response, $dataTx, $rorg, $msgID) = (undef, 5, undef, undef, undef, 0);
  14243. my $rltCntrMax = AttrVal($name, 'rltRepeat', 16);
  14244. my $rltType = AttrVal($name, 'rltType', '4BS');
  14245. if ($rltType eq '1BS') {
  14246. $msgID = $hash->{helper}{rlt}{cntr} & 0x3F if (exists($hash->{helper}{rlt}{cntr}));
  14247. $dataTx = sprintf("%02X", ($msgID & 0x3C) << 2 | 8 | ($msgID & 3) << 1);
  14248. $rorg = 'D5';
  14249. } else {
  14250. $dataTx = '0000000C';
  14251. $rorg = 'A5';
  14252. }
  14253. if ($ctrl eq 'start') {
  14254. RemoveInternalTimer($hash->{helper}{rlt}{param});
  14255. $hash->{helper}{rlt}{cntr} = 0;
  14256. $hash->{helper}{rlt}{param}[0] = 'periodic';
  14257. readingsSingleUpdate($hash, 'state', 'active', 1);
  14258. EnOcean_SndRadio(undef, $hash, 1, $rorg, $dataTx, $subDef, "00", $def);
  14259. InternalTimer(gettimeofday() + 0.15, "EnOcean_RLT", $hash->{helper}{rlt}{param}, 0);
  14260. } elsif ($ctrl eq 'parse') {
  14261. $hash->{helper}{rlt}{param}[0] = 'periodic';
  14262. if ($hash->{helper}{rlt}{cntr} < $rltCntrMax) {
  14263. # store received RLT data from slave
  14264. $hash->{helper}{rlt}{dataRx}[$hash->{helper}{rlt}{cntr}] = $dataRx;
  14265. $hash->{helper}{rlt}{rssiMaster}[$hash->{helper}{rlt}{cntr}] = $rssiMaster;
  14266. if ($hash->{helper}{rlt}{cntr} < $rltCntrMax - 1) {
  14267. RemoveInternalTimer($hash->{helper}{rlt}{param});
  14268. $hash->{helper}{rlt}{cntr} ++;
  14269. EnOcean_SndRadio(undef, $hash, 1, $rorg, $dataTx, $subDef, "00", $def);
  14270. InternalTimer(gettimeofday() + 0.15, "EnOcean_RLT", $hash->{helper}{rlt}{param}, 0);
  14271. }
  14272. } else {
  14273. RemoveInternalTimer($hash->{helper}{rlt}{param});
  14274. readingsSingleUpdate($hash, 'state', 'stopped', 1);
  14275. EnOcean_RLTResult(undef, $hash, $rltType, $rltCntrMax);
  14276. if (exists $hash->{helper}{rlt}{oldDev}) {
  14277. # activate old device subType
  14278. my $oldHash = $hash->{helper}{rlt}{oldDev}[0];
  14279. my $oldName = $hash->{helper}{rlt}{oldDev}[1];
  14280. my $oldDef = $hash->{helper}{rlt}{oldDev}[2];
  14281. CommandModify(undef, "$oldName $oldDef");
  14282. $modules{EnOcean}{defptr}{$oldDef} = $oldHash;
  14283. }
  14284. delete $hash->{helper}{rlt};
  14285. # delete deviceID
  14286. CommandModify(undef, "$name 00000000");
  14287. delete $modules{EnOcean}{defptr}{$def};
  14288. }
  14289. } elsif ($ctrl eq 'periodic') {
  14290. RemoveInternalTimer($hash->{helper}{rlt}{param});
  14291. if ($hash->{helper}{rlt}{cntr} < $rltCntrMax - 1) {
  14292. # no RLT_SlaveTest telegram received from slave
  14293. $hash->{helper}{rlt}{param}[0] = 'periodic';
  14294. $hash->{helper}{rlt}{cntr} ++;
  14295. EnOcean_SndRadio(undef, $hash, 1, $rorg, $dataTx, $subDef, "00", $def);
  14296. InternalTimer(gettimeofday() + 0.11, "EnOcean_RLT", $hash->{helper}{rlt}{param}, 0);
  14297. } else {
  14298. # waiting for last RLT_SlaveTest telegram
  14299. $hash->{helper}{rlt}{param}[0] = 'waiting';
  14300. InternalTimer(gettimeofday() + 0.15, "EnOcean_RLT", $hash->{helper}{rlt}{param}, 0);
  14301. }
  14302. } elsif ($ctrl eq 'waiting') {
  14303. readingsSingleUpdate($hash, 'state', 'stopped', 1);
  14304. EnOcean_RLTResult(undef, $hash, $rltType, $rltCntrMax);
  14305. if (exists $hash->{helper}{rlt}{oldDev}) {
  14306. # activate old device subType
  14307. my $oldHash = $hash->{helper}{rlt}{oldDev}[0];
  14308. my $oldName = $hash->{helper}{rlt}{oldDev}[1];
  14309. my $oldDef = $hash->{helper}{rlt}{oldDev}[2];
  14310. CommandModify(undef, "$oldName $oldDef");
  14311. $modules{EnOcean}{defptr}{$oldDef} = $oldHash;
  14312. }
  14313. delete $hash->{helper}{rlt};
  14314. # delete deviceID
  14315. CommandModify(undef, "$name 00000000");
  14316. delete $modules{EnOcean}{defptr}{$def};
  14317. } elsif ($ctrl eq 'standby') {
  14318. RemoveInternalTimer($hash->{helper}{rlt}{param});
  14319. delete $hash->{helper}{rlt};
  14320. readingsSingleUpdate($hash, 'state', 'standby', 1);
  14321. # delete deviceID
  14322. CommandModify(undef, "$name 00000000");
  14323. delete $modules{EnOcean}{defptr}{$def};
  14324. } elsif ($ctrl eq 'stop') {
  14325. RemoveInternalTimer($hash->{helper}{rlt}{param});
  14326. if (exists $hash->{helper}{rlt}{oldDev}) {
  14327. # activate old device subType
  14328. my $oldHash = $hash->{helper}{rlt}{oldDev}[0];
  14329. my $oldName = $hash->{helper}{rlt}{oldDev}[1];
  14330. my $oldDef = $hash->{helper}{rlt}{oldDev}[2];
  14331. CommandModify(undef, "$oldName $oldDef");
  14332. $modules{EnOcean}{defptr}{$oldDef} = $oldHash;
  14333. }
  14334. delete $hash->{helper}{rlt};
  14335. readingsSingleUpdate($hash, 'state', 'stopped', 1);
  14336. # delete deviceID
  14337. CommandModify(undef, "$name 00000000");
  14338. delete $modules{EnOcean}{defptr}{$def};
  14339. }
  14340. return ($err, $logLevel, $response);
  14341. }
  14342. sub EnOcean_RLTResult($$$$) {
  14343. # show RLT results
  14344. my ($ctrl, $hash, $rltType, $rltCntrMax) = @_;
  14345. my $name = $hash->{NAME};
  14346. my ($err, $logLevel, $response, $data, $msgCntr, $msgID, $subTelNum, $rssiMaster, $rssiMasterAvg, $rssiSlave, $rssiLevel1, $rssiLevel2, $rssiNonEnOcean) =
  14347. (undef, 5, undef, undef, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  14348. my %rssi = (0 => '-', 1 => '>= -31', 2 => '-32', 0x3F => '<= -93');
  14349. my %rssiNonEnOcean =
  14350. (0 => '-',
  14351. 1 => '>= -31',
  14352. 2 => '-32 ... -37',
  14353. 3 => '-38 ... -43',
  14354. 4 => '-44 ... -49',
  14355. 5 => '-50 ... -55',
  14356. 6 => '-56 ... -61',
  14357. 7 => '-62 ... -67',
  14358. 8 => '-68 ... -73',
  14359. 9 => '-74 ... -79',
  14360. 10 => '-80 ... -85',
  14361. 11 => '<= -92');
  14362. for (my $cntr = 0; $cntr < $rltCntrMax; $cntr++) {
  14363. $data = $hash->{helper}{rlt}{dataRx}[$cntr];
  14364. if (defined $data) {
  14365. $msgCntr ++;
  14366. $rssiMaster = -$hash->{helper}{rlt}{rssiMaster}[$cntr];
  14367. $rssiMasterAvg += $rssiMaster;
  14368. if ($rltType eq '1BS') {
  14369. $msgID = (hex($data) & 0xF0) >> 6 | (hex($data) & 6) >> 1;
  14370. Log3 $name, 2, "EnOcean RLT DeviceID: " . $hash->{DEF} . " msgCntr: ". ($cntr + 1) . " msgID: $msgID RSSI Master: $rssiMaster response received";
  14371. } else {
  14372. $subTelNum = (hex(substr($data, 0, 2)) & 0xC0) >> 6;
  14373. $rssiLevel2 = hex(substr($data, 0, 2)) & 0x3F;
  14374. $rssiLevel2 = $rssi{$rssiLevel2} if (exists $rssi{$rssiLevel2});
  14375. $rssiLevel1 = hex(substr($data, 2, 2));
  14376. $rssiLevel1 = $rssi{$rssiLevel1} if (exists $rssi{$rssiLevel1});
  14377. $rssiSlave = hex(substr($data, 4, 2));
  14378. $rssiSlave = $rssi{$rssiSlave} if (exists $rssi{$rssiSlave});
  14379. $rssiNonEnOcean = (hex(substr($data, 6, 2)) & 0xF0) >> 4;
  14380. $rssiNonEnOcean = $rssiNonEnOcean{$rssiNonEnOcean} if (exists $rssiNonEnOcean{$rssiNonEnOcean});
  14381. $msgID = (hex(substr($data, 6, 2)) & 6) >> 1;
  14382. Log3 $name, 2, "EnOcean RLT DeviceID: " . $hash->{DEF} . " msgCntr: ". ($cntr + 1) . " subTelNum: $subTelNum " .
  14383. "RSSI Master: $rssiMaster RSSI Slave: $rssiSlave RSSI Level 1: $rssiLevel1 RSSI Level 2: $rssiLevel2 " .
  14384. "RSSI non EnOcean: $rssiNonEnOcean";
  14385. }
  14386. } else {
  14387. Log3 $name, 2, "EnOcean RLT DeviceID: " . $hash->{DEF} . " msgCntr: ". ($cntr + 1) . " no answer";
  14388. }
  14389. }
  14390. if ($msgCntr > 0) {
  14391. readingsBeginUpdate($hash);
  14392. readingsBulkUpdate($hash, 'msgLost', (sprintf "%0.1f", ($rltCntrMax - $msgCntr) / $rltCntrMax * 100));
  14393. readingsBulkUpdate($hash, 'rssiMasterAvg', (sprintf "%0.1f", $rssiMasterAvg / $msgCntr));
  14394. readingsEndUpdate($hash, 1);
  14395. } else {
  14396. CommandDeleteReading(undef, "$name msgLost");
  14397. CommandDeleteReading(undef, "$name rssiMasterAvg");
  14398. }
  14399. return ($err, $logLevel, $response);
  14400. }
  14401. #
  14402. sub EnOcean_energyManagement_01Parse($@)
  14403. {
  14404. my ($hash, @db) = @_;
  14405. my $name = $hash->{NAME};
  14406. # [drLevel] = 15 : no requests for reduction in power consumptions
  14407. my $drLevel = ($db[0] & 0xF0) >> 4;
  14408. my $powerUsage = $db[2] & 0x7F;
  14409. my $powerUsageLevel = $db[0] & 1 ? "max" : "min";
  14410. my $powerUsageScale = $db[2] & 0x80 ? "rel" : "max";
  14411. my $randomStart = $db[0] & 4 ? "yes" : "no";
  14412. my $randomEnd = $db[0] & 2 ? "yes" : "no";
  14413. my $randomTime = rand(AttrVal($name, "demandRespRandomTime", 1));
  14414. my $setpoint = $db[3];
  14415. my $timeout = $db[1] * 15 * 60;
  14416. $randomTime = $randomTime > $timeout && $timeout > 0 ? rand($timeout) : rand($randomTime);
  14417. readingsBeginUpdate($hash);
  14418. readingsBulkUpdate($hash, "randomEnd", $randomEnd);
  14419. readingsBulkUpdate($hash, "randomStart", $randomStart);
  14420. #my %timeoutHash = (hash => $hash, function => "demandResponseTimeout");
  14421. #my %functionHash = (hash => $hash,
  14422. # function => "demandResponseExec",
  14423. # drLevel => $drLevel,
  14424. # powerUsage => $powerUsage,
  14425. # powerUsageLevel => $powerUsageLevel,
  14426. # powerUsageScale => $powerUsageScale,
  14427. # setpoint => $setpoint
  14428. # );
  14429. $hash->{helper}{timer}{demandResponseTimeout} = {hash => $hash, function => "demandResponseTimeout"};
  14430. $hash->{helper}{demandResponseExec} = {hash => $hash,
  14431. function => "demandResponseExec",
  14432. drLevel => $drLevel,
  14433. powerUsage => $powerUsage,
  14434. powerUsageLevel => $powerUsageLevel,
  14435. powerUsageScale => $powerUsageScale,
  14436. setpoint => $setpoint
  14437. };
  14438. RemoveInternalTimer($hash->{helper}{timer}{demandResponseTimeout}) if (exists $hash->{helper}{timer}{demandResponseTimeout});
  14439. RemoveInternalTimer($hash->{helper}{demandResponseExec}) if (exists $hash->{helper}{demandResponseExec});
  14440. if ($timeout > 0 && $drLevel < 15) {
  14441. # timeout timer
  14442. InternalTimer(gettimeofday() + $timeout, "EnOcean_demandResponseTimeout", $hash->{helper}{timer}{demandResponseTimeout}, 0);
  14443. my ($sec, $min, $hour, $day, $month, $year) = localtime(time + $timeout);
  14444. $month += 1;
  14445. $year += 1900;
  14446. $min = $min < 10 ? $min = "0" . $min : $min;
  14447. $hour = $hour < 10 ? $hour = "0" . $hour : $hour;
  14448. $day = $day < 10 ? $day = "0" . $day : $day;
  14449. $month = $month < 10 ? $ month = "0". $month : $month;
  14450. readingsBulkUpdate($hash, "timeout", "$year-$month-$day $hour:$min:$sec");
  14451. } else {
  14452. CommandDeleteReading(undef, "$name timeout");
  14453. }
  14454. if ($randomStart eq "yes" && ReadingsVal($name, "level", 15) == 15) {
  14455. readingsBulkUpdate($hash, "state", "waiting_for_start");
  14456. Log3 $name, 3, "EnOcean set $name demand response waiting for start";
  14457. InternalTimer(gettimeofday() + $randomTime, "EnOcean_demandResponseExec", $hash->{helper}{demandResponseExec}, 0);
  14458. } elsif ($randomEnd eq "yes" && ReadingsVal($name, "level", 15) < 15) {
  14459. readingsBulkUpdate($hash, "state", "waiting_for_stop");
  14460. Log3 $name, 3, "EnOcean set $name demand response waiting for stop";
  14461. InternalTimer(gettimeofday() + $randomTime, "EnOcean_demandResponseExec", $hash->{helper}{demandResponseExec}, 0);
  14462. } else {
  14463. EnOcean_demandResponseExec($hash->{helper}{demandResponseExec});
  14464. }
  14465. readingsEndUpdate($hash, 1);
  14466. return;
  14467. }
  14468. #
  14469. sub EnOcean_demandResponseExec($)
  14470. {
  14471. my ($functionHash) = @_;
  14472. my $function = $functionHash->{function};
  14473. my $hash = $functionHash->{hash};
  14474. my $drLevel = $functionHash->{drLevel};
  14475. my $powerUsage = $functionHash->{powerUsage};
  14476. my $powerUsageLevel = $functionHash->{powerUsageLevel};
  14477. my $powerUsageScale = $functionHash->{powerUsageScale};
  14478. my $setpoint = $functionHash->{setpoint};
  14479. my $name = $hash->{NAME};
  14480. my $actionCmd = AttrVal($name, "demandRespAction", undef);
  14481. # save old values
  14482. $hash->{helper}{drLevel} = ReadingsVal($name, "level", 15);
  14483. $hash->{helper}{powerUsage} = ReadingsVal($name, "powerUsage", 100);
  14484. $hash->{helper}{powerUsageLevel} = ReadingsVal($name, "powerUsageLevel", "max");
  14485. $hash->{helper}{powerUsageScale} = ReadingsVal($name, "powerUsageScale", "max");
  14486. $hash->{helper}{setpoint} = ReadingsVal($name, "setpoint", 255);
  14487. readingsBeginUpdate($hash);
  14488. readingsBulkUpdate($hash, "level", $drLevel);
  14489. readingsBulkUpdate($hash, "powerUsage", $powerUsage);
  14490. readingsBulkUpdate($hash, "powerUsageLevel", $powerUsageLevel);
  14491. readingsBulkUpdate($hash, "powerUsageScale", $powerUsageScale);
  14492. readingsBulkUpdate($hash, "setpoint", $setpoint);
  14493. if ($drLevel == 15) {
  14494. readingsBulkUpdate($hash, "state", "off");
  14495. Log3 $name, 3, "EnOcean set $name demand response off";
  14496. } else {
  14497. readingsBulkUpdate($hash, "state", "on");
  14498. Log3 $name, 3, "EnOcean set $name demand response on";
  14499. }
  14500. readingsEndUpdate($hash, 1);
  14501. if (defined $actionCmd) {
  14502. # action exec
  14503. my %specials= ("%NAME" => $name,
  14504. "%TYPE" => $hash->{TYPE},
  14505. "%LEVEL" => $drLevel,
  14506. "%SETPOINT" => $setpoint,
  14507. "%POWERUSAGE" => $powerUsage,
  14508. "%POWERUSAGESCALE" => $powerUsageScale,
  14509. "%POWERUSAGELEVEL" => $powerUsageLevel,
  14510. "%STATE" => ReadingsVal($name, "state", "off")
  14511. );
  14512. $actionCmd = EvalSpecials($actionCmd, %specials);
  14513. my $ret = AnalyzeCommandChain(undef, $actionCmd);
  14514. Log3 $name, 2, "EnOcean $name demandRespAction ERROR: $ret" if($ret);
  14515. }
  14516. return;
  14517. }
  14518. #
  14519. sub EnOcean_demandResponseTimeout($)
  14520. {
  14521. my ($functionHash) = @_;
  14522. my $function = $functionHash->{function};
  14523. my $hash = $functionHash->{hash};
  14524. my $name = $hash->{NAME};
  14525. my $actionCmd = AttrVal($name, "demandRespAction", undef);
  14526. my $data;
  14527. my $timeoutLevel = AttrVal($name, "demandRespTimeoutLevel", "max");
  14528. RemoveInternalTimer($functionHash);
  14529. CommandDeleteReading(undef, "$name timeout");
  14530. my $drLevel = 15;
  14531. my $powerUsage = 100;
  14532. my $powerUsageLevel = "max";
  14533. my $powerUsageScale = "max";
  14534. my $setpoint = 255;
  14535. my $timeout = 0;
  14536. if ($timeoutLevel eq "last" && defined($hash->{helper}{drLevel})) {
  14537. # restore old values
  14538. $drLevel = $hash->{helper}{drLevel};
  14539. $powerUsage = $hash->{helper}{powerUsage};
  14540. $powerUsageScale = $hash->{helper}{powerUsageLevel};
  14541. $powerUsageLevel = $hash->{helper}{powerUsageScale};
  14542. $setpoint = $hash->{helper}{setpoint};
  14543. }
  14544. readingsBeginUpdate($hash);
  14545. readingsBulkUpdate($hash, "level", $drLevel);
  14546. readingsBulkUpdate($hash, "powerUsage", $powerUsage);
  14547. readingsBulkUpdate($hash, "powerUsageLevel", $powerUsageLevel);
  14548. readingsBulkUpdate($hash, "powerUsageScale", $powerUsageScale);
  14549. readingsBulkUpdate($hash, "setpoint", $setpoint);
  14550. if ($drLevel == 15) {
  14551. readingsBulkUpdate($hash, "state", "off");
  14552. Log3 $name, 3, "EnOcean set $name demand response off";
  14553. } else {
  14554. readingsBulkUpdate($hash, "state", "on");
  14555. Log3 $name, 3, "EnOcean set $name demand response on";
  14556. }
  14557. readingsEndUpdate($hash, 1);
  14558. my %specials= ("%NAME" => $name,
  14559. "%TYPE" => $hash->{TYPE},
  14560. "%LEVEL" => $drLevel,
  14561. "%SETPOINT" => $setpoint,
  14562. "%POWERUSAGE" => $powerUsage,
  14563. "%POWERUSAGESCALE" => $powerUsageScale,
  14564. "%POWERUSAGELEVEL" => $powerUsageLevel,
  14565. "%STATE" => ReadingsVal($name, "state", "off")
  14566. );
  14567. $powerUsageLevel = $powerUsageLevel eq "max" ? 1 : 0;
  14568. $powerUsageScale = $powerUsageScale eq "rel" ? 0x80 : 0;
  14569. my $randomStart = ReadingsVal($name, "randomStart", "no") eq "yes" ? 4 : 0;
  14570. my $randomEnd = ReadingsVal($name, "randomEnd", "no") eq "yes" ? 2 : 0;
  14571. $data = sprintf "%02X%02X%02X%02X", $setpoint, $powerUsageScale | $powerUsage, $timeout,
  14572. $drLevel << 4 | $randomStart | $randomEnd | $powerUsageLevel | 8;
  14573. EnOcean_SndRadio(undef, $hash, 1, "A5", $data, AttrVal($name, "subDef", $hash->{DEF}), "00", "FFFFFFFF");
  14574. if (defined $actionCmd) {
  14575. # action exec
  14576. $actionCmd = EvalSpecials($actionCmd, %specials);
  14577. my $ret = AnalyzeCommandChain(undef, $actionCmd);
  14578. Log3 $name, 2, "EnOcean $name demandRespAction ERROR: $ret" if($ret);
  14579. }
  14580. return;
  14581. }
  14582. # Send UTE Teach-In Telegrams
  14583. sub EnOcean_sndUTE($$$$$$$) {
  14584. my ($ctrl, $hash, $comMode, $responseRequest, $teachInReq, $devChannel, $eep) = @_;
  14585. my $name = $hash->{NAME};
  14586. my ($err, $data) = (undef, "");
  14587. my $IODev = $hash->{IODev}{NAME};
  14588. my $IOHash = $defs{$IODev};
  14589. my @db = (undef, undef, undef, "07", "FF", $devChannel);
  14590. if ($eep =~ m/^(..)-(..)-(..)$/) {
  14591. ($db[0], $db[1], $db[2]) = ($1, $2, $3);
  14592. } else {
  14593. return (1, undef, undef);
  14594. }
  14595. # set unidir/bidir operation
  14596. $db[6] = $comMode eq "biDir" ? 0x80 : 0;
  14597. $attr{$name}{comMode} = $comMode;
  14598. # set teach mode
  14599. if ($teachInReq eq "out") {
  14600. $db[6] |= 0x10;
  14601. } elsif ($teachInReq eq "inout") {
  14602. $db[6] |= 0x20;
  14603. }
  14604. # set response message mode
  14605. if ($responseRequest eq "no") {
  14606. $db[6] |= 0x40;
  14607. readingsSingleUpdate($hash, "teach", "EEP $eep UTE query sent", 1);
  14608. } else {
  14609. # set flag for response request,
  14610. if ($teachInReq eq "in") {
  14611. $hash->{IODev}{helper}{UTERespWait}{$hash->{DEF}}{teachInReq} = $teachInReq;
  14612. $hash->{IODev}{helper}{UTERespWait}{$hash->{DEF}}{hash} = $hash;
  14613. } elsif ($teachInReq eq "out") {
  14614. $hash->{IODev}{helper}{UTERespWait}{AttrVal($name, "subDef", $hash->{DEF})}{teachInReq} = $teachInReq;
  14615. $hash->{IODev}{helper}{UTERespWait}{AttrVal($name, "subDef", $hash->{DEF})}{hash} = $hash;
  14616. } elsif ($teachInReq eq "inout") {
  14617. $hash->{IODev}{helper}{UTERespWait}{$hash->{DEF}}{teachInReq} = $teachInReq;
  14618. $hash->{IODev}{helper}{UTERespWait}{$hash->{DEF}}{hash} = $hash;
  14619. }
  14620. readingsSingleUpdate($hash, "teach", "EEP $eep UTE query sent, response requested", 1);
  14621. if (!exists($hash->{IODev}{Teach})) {
  14622. # enable teach-in receiving for 3 sec
  14623. $hash->{IODev}{Teach} = 1;
  14624. #####
  14625. #my %timeoutHash = (hash => $IOHash, function => "UTERespTimeout", helper => "UTERespWait");
  14626. #RemoveInternalTimer(\%timeoutHash);
  14627. #InternalTimer(gettimeofday() + 3, "EnOcean_RespTimeout", \%timeoutHash, 0);
  14628. RemoveInternalTimer($hash->{helper}{timer}{UTERespTimeout}) if(exists $hash->{helper}{timer}{UTERespTimeout});
  14629. $hash->{helper}{timer}{UTERespTimeout} = {hash => $IOHash, function => "UTERespTimeout", helper => "UTERespWait"};
  14630. InternalTimer(gettimeofday() + 3, 'EnOcean_RespTimeout', $hash->{helper}{timer}{UTERespTimeout}, 0);
  14631. }
  14632. }
  14633. $attr{$name}{devChannel} = $devChannel;
  14634. $attr{$name}{eep} = $eep;
  14635. $attr{$name}{manufID} = "7FF";
  14636. $data = sprintf "%02X%02X%s%s%s%s%s", $db[6], $db[5], $db[4], $db[3], $db[2], $db[1], $db[0];
  14637. return ($err, "D4", $data);
  14638. }
  14639. #
  14640. sub EnOcean_RespTimeout($) {
  14641. my ($functionHash) = @_;
  14642. my $function = $functionHash->{function};
  14643. my $hash = $functionHash->{hash};
  14644. my $helper = $functionHash->{helper};
  14645. my $name = $hash->{NAME};
  14646. delete $hash->{helper}{$helper};
  14647. delete $hash->{Teach};
  14648. return;
  14649. }
  14650. #
  14651. sub EnOcean_setTeachConfirmWaitHash($) {
  14652. my ($ctrl, $hash) = @_;
  14653. if (AttrVal($hash->{NAME}, "teachMethod", "") eq 'confirm') {
  14654. $hash->{IODev}{helper}{teachConfirmWaitHash} = $hash;
  14655. #####
  14656. #my %functionHash = (hash => $hash->{IODev}, function => "teachConfirmWaitHash");
  14657. #RemoveInternalTimer(\%functionHash);
  14658. #InternalTimer(gettimeofday() + 5, "EnOcean_helperClear", \%functionHash, 0);
  14659. RemoveInternalTimer($hash->{helper}{timer}{teachConfirmWaitHash}) if(exists $hash->{helper}{timer}{teachConfirmWaitHash});
  14660. $hash->{helper}{timer}{teachConfirmWaitHash} = {hash => $hash->{IODev}, function => "teachConfirmWaitHash"};
  14661. InternalTimer(gettimeofday() + 5, 'EnOcean_helperClear', $hash->{helper}{timer}{teachConfirmWaitHash}, 0);
  14662. }
  14663. return;
  14664. }
  14665. #
  14666. sub EnOcean_ReadDevDesc($$) {
  14667. # read xml device description to $hash->{helper}
  14668. my ($ctrl, $hash) = @_;
  14669. my $name = $hash->{NAME};
  14670. if ($xmlFunc == 0) {
  14671. Log3 $name, 2, "EnOcean $name XML functions are not available";
  14672. return;
  14673. }
  14674. if (exists($hash->{TYPE}) && $hash->{TYPE} eq 'EnOcean' && exists($attr{$name}{model})) {
  14675. if (exists $EnO_models{$attr{$name}{model}}) {
  14676. if (exists $EnO_models{$attr{$name}{model}}{xml}{xmlDescrLocation}) {
  14677. my $xmlFile = $attr{global}{modpath} . $EnO_models{$attr{$name}{model}}{xml}{xmlDescrLocation};
  14678. if (-e -f -r $xmlFile) {
  14679. my $xmlData = $xml->XMLin($xmlFile);
  14680. $hash->{helper} = $xmlData;
  14681. if (exists $xmlData->{Device}) {
  14682. Log3 $name, 5, "EnOcean $name Beginn Device Description";
  14683. Log3 $name, 5, "###";
  14684. Log3 $name, 5, Dumper($xmlData);
  14685. Log3 $name, 5, "###";
  14686. Log3 $name, 5, "EnOcean $name End Device Description";
  14687. } else {
  14688. Log3 $name, 2, "EnOcean $name Device Description not defined";
  14689. }
  14690. } else {
  14691. Log3 $name, 2, "EnOcean $name Device Description file $xmlFile not exists";
  14692. }
  14693. }
  14694. }
  14695. }
  14696. return;
  14697. }
  14698. #
  14699. sub EnOcean_helperClear($) {
  14700. my ($functionHash) = @_;
  14701. my $function = $functionHash->{function};
  14702. my $hash = $functionHash->{hash};
  14703. delete $hash->{helper}{$function};
  14704. return;
  14705. }
  14706. #
  14707. sub EnOcean_cdmClearRemoteWait($) {
  14708. my ($functionHash) = @_;
  14709. my $hash = $functionHash->{hash};
  14710. my $param = $functionHash->{param};
  14711. delete $hash->{IODev}{helper}{remoteAnswerWait}{$param}{hash};
  14712. #Log3 $hash->{NAME}, 3, "EnOcean $hash->{NAME} EnOcean_cdmClearRemoteWait executed.";
  14713. return;
  14714. }
  14715. #
  14716. sub EnOcean_cdmClearHashVal($) {
  14717. my ($functionHash) = @_;
  14718. my $hash = $functionHash->{hash};
  14719. my $param = $functionHash->{param};
  14720. delete $hash->{$param};
  14721. #Log3 $hash->{NAME}, 3, "EnOcean $hash->{NAME} EnOcean_cdmClearHashVal executed.";
  14722. return;
  14723. }
  14724. #
  14725. sub EnOcean_CommandDelete($) {
  14726. my ($functionHash) = @_;
  14727. my $deleteDevice = $functionHash->{deleteDevice};
  14728. my $function = $functionHash->{function};
  14729. my $hash = $functionHash->{hash};
  14730. my $name = $hash->{NAME};
  14731. my $oldDevice = $functionHash->{oldDevice};
  14732. CommandDelete(undef, $deleteDevice);
  14733. if (defined $oldDevice) {
  14734. Log3 $name, 2, "EnOcean $name: $oldDevice renamed to $deleteDevice";
  14735. CommandRename(undef, "$oldDevice $deleteDevice");
  14736. CommandSave(undef, undef);
  14737. } else {
  14738. Log3 $name, 2, "EnOcean $name: $deleteDevice deleted";
  14739. CommandSave(undef, undef);
  14740. }
  14741. return;
  14742. }
  14743. #
  14744. sub EnOcean_convBitToHex($) {
  14745. # convert bit string to hex string
  14746. my ($bitStr) = @_;
  14747. my $hexStr = '';
  14748. while(length($bitStr) > 0) {
  14749. $bitStr =~ m/^(.*)(.{8})$/;
  14750. $bitStr = $1;
  14751. $hexStr = unpack('H2', pack('B8', $2)) . $hexStr;
  14752. }
  14753. return uc($hexStr);
  14754. }
  14755. #
  14756. sub EnOcean_convHexToBit($) {
  14757. # convert unsign hex string to bit string
  14758. my ($hexstr) = @_;
  14759. my $bitstr = '';
  14760. while (length($hexstr) > 0) {
  14761. $hexstr =~ m/^(.*)(..)$/;
  14762. $hexstr = $1;
  14763. $bitstr = unpack('B8', pack('H2', $2)) . $bitstr;
  14764. }
  14765. return $bitstr;
  14766. }
  14767. #
  14768. sub EnOcean_convIntToBit($$) {
  14769. # convert unsign number to bitstring
  14770. my ($data, $resolution) = @_;
  14771. Log3 undef, 3, "EnOcean EnOcean_convIntToBitstr input: $data";
  14772. if ($data > 0xFFFF) {
  14773. # unsigned long (32 bit)
  14774. Log3 undef, 3, "EnOcean EnOcean_convIntToBitstr pack L: " . pack('L', $data);
  14775. #$data = unpack('B32', pack('L', $data));
  14776. $data = unpack('B32', $data);
  14777. } elsif ($data > 0xFF) {
  14778. # unsigned short (16 bit)
  14779. Log3 undef, 3, "EnOcean EnOcean_convIntToBitstr pack S: " . pack('S', $data);
  14780. #$data = '0' x 16 . unpack('B16', pack('S', $data));
  14781. $data = '0' x 16 . unpack('B16', $data);
  14782. } else {
  14783. # unsigned char (8 bit)
  14784. Log3 undef, 3, "EnOcean EnOcean_convIntToBitstr pack C: " . pack('C', $data);
  14785. $data = '0' x 24 . unpack('B8', pack('C', $data));
  14786. }
  14787. Log3 undef, 3, "EnOcean EnOcean_convIntToBitstr pack B32: " . $data;
  14788. $data = substr($data, 32 - $resolution);
  14789. return $data;
  14790. }
  14791. sub EnOcean_gpConvSelDataToSndData($$$$) {
  14792. # Generic Profiles, make selective data in hex
  14793. my ($header, $channel, $resolution, $data) = @_;
  14794. my $resolutionPattern = '%04B%06B%0' . $resolution . 'B';
  14795. $data = sprintf "$resolutionPattern", $header, $channel, $data;
  14796. if (($resolution + 10) % 8) {
  14797. # fill with trailing zeroes to x bytes
  14798. $data = $data . '0' x (8 - (10 + $resolution) % 8);
  14799. }
  14800. #Log3 undef, 3, "EnOcean EnOcean_gpConvSelDataToSndData header: $header channel: $channel data: $data";
  14801. $data = EnOcean_convBitToHex($data);
  14802. #Log3 undef, 3, "EnOcean EnOcean_gpConvSelDataToSndData header: $header channel: $channel data: $data";
  14803. return $data;
  14804. }
  14805. #
  14806. sub EnOcean_gpConvDataToValue($$$$$) {
  14807. # Generic Profiles, convert data to value
  14808. my ($ctrl, $hash, $channel, $data, $dataDescr) = @_;
  14809. my $name = $hash->{NAME};
  14810. my ($err, $logLevel, $msg, $readingFormat, $readingType, $readingUnit, $readingValue, $valueType) =
  14811. (undef, 5, 'ok', '%d', 'data', 'N/A', $data, 'value');
  14812. my @channelTypeList = ("teachIn", "data", "flag", "enum");
  14813. my @signalTypeList;
  14814. my @valueTypeList = ("res", "value", "setpointAbs", "setpointRel");
  14815. # extract channel definition
  14816. my ($channelName, $channelDir, $channelType, $signalType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax);
  14817. ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
  14818. split(':', $dataDescr);
  14819. my $readingName = sprintf('%02d', $channel) . '-' . $channelName;
  14820. $readingType = $channelTypeList[$channelType];
  14821. $valueType = $valueTypeList[$valueType];
  14822. if ($channelType == 3) {
  14823. # enumeration
  14824. if (defined $EnO_gpValueEnum{$signalType}{enum}{$data}) {
  14825. $readingValue = $EnO_gpValueEnum{$signalType}{enum}{$data};
  14826. $readingFormat = '%s';
  14827. }
  14828. } elsif ($channelType == 2) {
  14829. # flag
  14830. if (defined $EnO_gpValueFlag{$signalType}{flag}{$data}) {
  14831. $readingValue = $EnO_gpValueFlag{$signalType}{flag}{$data};
  14832. $readingFormat = '%s';
  14833. }
  14834. } elsif ($channelType == 1) {
  14835. # data
  14836. if (defined $EnO_gpValueData{$signalType}{unit}) {
  14837. $readingUnit = $EnO_gpValueData{$signalType}{unit};
  14838. }
  14839. my @decimalDigits = (0, 1, 1, 2, 2, 2, 3, 4 , 4, 5, 7, 8, 10);
  14840. $readingValue = $data / 2**$EnO_resolution[$resolution] *
  14841. ($engMax * $EnO_scaling[$scalingMax] - $engMin * $EnO_scaling[$scalingMin]) + $engMin * $EnO_scaling[$scalingMin];
  14842. if ($readingValue =~ m/^[+-]?\d+$/) {
  14843. } elsif ($readingValue < 1000) {
  14844. $readingFormat = '%0.' . ($decimalDigits[$resolution] - 1) . 'f';
  14845. } else {
  14846. $readingFormat = '%.' . ($decimalDigits[$resolution] - 1) . 'g';
  14847. }
  14848. } else {
  14849. # teach-in info ... not used
  14850. my @valueList = ("res", "channelsDescription", "productID");
  14851. if (defined $valueList[$signalType]) {
  14852. $readingValue = $valueList[$signalType];
  14853. $readingFormat = '%s';
  14854. }
  14855. }
  14856. return ($err, $logLevel, $msg, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType);
  14857. }
  14858. # Parse Secure Teach-In Telegrams
  14859. sub EnOcean_sec_parseTeachIn($$$$) {
  14860. my ($hash, $telegram, $subDef, $destinationID) = @_;
  14861. my $name = $hash->{NAME};
  14862. my ($err, $response, $logLevel);
  14863. my $rlc; # Rolling code
  14864. my $key1; # First part of private key
  14865. my $key2; # Second part of private key
  14866. # Extract byte fields from telegram
  14867. # TEACH_IN_INFO, SLF, RLC/KEY/variable
  14868. $telegram =~ /^(..)(..)(.*)/; # TODO Parse error handling?
  14869. my $teach_bin = unpack('B8',pack('H2', $1)); # Parse as ASCII HEX, unpack to bitstring
  14870. my $slf_bin = unpack('B8',pack('H2', $2)); # Parse as ASCII HEX, unpack to bitstring
  14871. my $crypt = $3;
  14872. # Extract bit fields from teach-in info field
  14873. # IDX, CNT, PSK, TYPE, INFO
  14874. $teach_bin =~ /(..)(..)(.)(.)(..)/; # TODO Parse error handling?
  14875. my $idx = unpack('C',pack('B8', '000000'.$1)); # Padd to byte, parse as unsigned char
  14876. my $cnt = unpack('C',pack('B8', '000000'.$2)); # Padd to byte, parse as unsigned char
  14877. my $psk = $3;
  14878. my $type = $4;
  14879. my $info = unpack('C',pack('B8', '000000'.$5)); # Padd to byte, parse as unsigned char
  14880. # Extract bit fields from SLF field
  14881. # RLC_ALGO, RLC_TX, MAC_ALGO, DATA_ENC
  14882. $slf_bin =~ /(..)(.)(..)(...)/; # TODO Parse error handling?
  14883. my $rlc_algo = unpack('C',pack('B8', '000000'.$1)); # Padd to byte, parse as unsigned char
  14884. my $rlc_tx = $2;
  14885. my $mac_algo = unpack('C',pack('B8', '000000'.$3)); # Padd to byte, parse as unsigned char
  14886. my $data_enc = unpack('C',pack('B8', '00000'.$4)); # Padd to byte, parse as unsigned char
  14887. # The teach-in information is split in two telegrams due to the ERP1 limitations on telegram length
  14888. # So we should get a telegram with index 0 and count 2 with the first half of the infos needed
  14889. if ($idx == 0 && $cnt == 2) {
  14890. # First part of the teach in message
  14891. # Decode teach in type
  14892. if ($type == 0) {
  14893. # 1BS, 4BS, UTE or GP teach-in expected
  14894. if ($info == 0) {
  14895. $attr{$name}{comMode} = "uniDir";
  14896. $attr{$name}{secMode} = "rcv";
  14897. } else {
  14898. $attr{$name}{comMode} = "biDir";
  14899. $attr{$name}{secMode} = "biDir";
  14900. }
  14901. $hash->{helper}{teachInWait} = "STE";
  14902. } else {
  14903. # switch teach-in
  14904. $attr{$name}{teachMethod} = 'STE';
  14905. if ($info == 0) {
  14906. $attr{$name}{comMode} = "uniDir";
  14907. $attr{$name}{eep} = "D2-03-00";
  14908. $attr{$name}{manufID} = "7FF";
  14909. $attr{$name}{secMode} = "rcv";
  14910. foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) {
  14911. $attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr};
  14912. }
  14913. readingsSingleUpdate($hash, "teach", "STE teach-in accepted EEP D2-03-00 Manufacturer: " . $EnO_manuf{"7FF"}, 1);
  14914. Log3 $name, 2, "EnOcean $name STE teach-in accepted EEP D2-03-00 Rocker A Manufacturer: " . $EnO_manuf{"7FF"};
  14915. } else {
  14916. $attr{$name}{comMode} = "uniDir";
  14917. $attr{$name}{eep} = "D2-03-00";
  14918. $attr{$name}{manufID} = "7FF";
  14919. $attr{$name}{secMode} = "rcv";
  14920. foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) {
  14921. $attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr};
  14922. }
  14923. readingsSingleUpdate($hash, "teach", "STE teach-in accepted EEP D2-03-00 Manufacturer: " . $EnO_manuf{"7FF"}, 1);
  14924. Log3 $name, 2, "EnOcean $name STE teach-in accepted EEP D2-03-00 Rocker B Manufacturer: " . $EnO_manuf{"7FF"};
  14925. }
  14926. }
  14927. # Decode RLC algorithm and extract RLC and private key (only first part most likely)
  14928. if ($rlc_algo == 0) {
  14929. # No RLC used in telegram or internally in memory, use case untested
  14930. return ("Secure modes without RLC not tested or supported", undef);
  14931. } elsif ($rlc_algo == 1) {
  14932. # "RLC= 2-byte long. RLC algorithm consists on incrementing in +1 the previous RLC value
  14933. # Extract RLC and KEY fields from data trailing SLF field
  14934. # RLC, KEY, ID, STATUS
  14935. $crypt =~ /^(....)(.*)$/;
  14936. $rlc = $1;
  14937. $key1 = $2;
  14938. #print "RLC: $rlc\n";
  14939. #print "Part 1 of KEY: $key1\n";
  14940. # Store in device hash
  14941. $attr{$name}{rlcAlgo} = '2++';
  14942. readingsSingleUpdate($hash, ".rlcRcv", $rlc, 0);
  14943. # storing backup copy
  14944. $attr{$name}{rlcRcv} = $rlc;
  14945. $attr{$name}{keyRcv} = $key1;
  14946. } elsif ($rlc_algo == 2) {
  14947. # RLC= 3-byte long. RLC algorithm consists on incrementing in +1 the previous RLC value
  14948. # Extract RLC and KEY fields from data trailing SLF field
  14949. # RLC, KEY, ID, STATUS
  14950. $crypt =~ /^(......)(.*)$/;
  14951. $rlc = $1;
  14952. $key1 = $2;
  14953. #print "RLC: $rlc\n";
  14954. #print "Part 1 of KEY: $key1\n";
  14955. # Store in device hash
  14956. $attr{$name}{rlcAlgo} = '3++';
  14957. readingsSingleUpdate($hash, ".rlcRcv", $rlc, 0);
  14958. # storing backup copy
  14959. $attr{$name}{rlcRcv} = $rlc;
  14960. $attr{$name}{keyRcv} = $key1;
  14961. } else {
  14962. # Undefined RLC algorithm
  14963. return ("Undefined RLC algorithm $rlc_algo", undef);
  14964. }
  14965. # RLC Transmission
  14966. if ($rlc_tx == 0 ) {
  14967. # Secure operation mode telegrams do not contain RLC, we store and track it ourself
  14968. $attr{$name}{rlcTX} = 'false';
  14969. } else {
  14970. # Secure operation mode messages contain RLC, CAUTION untested
  14971. $attr{$name}{rlcTX} = 'true';
  14972. }
  14973. # Decode MAC Algorithm
  14974. if ($mac_algo == 0) {
  14975. # No MAC included in the secure telegram
  14976. # Doesn't make sense for RLC senders like the PTM215, as we can't verify the RLC then...
  14977. #$attr{$name}{macAlgo} = 'no';
  14978. return ("Secure mode without MAC algorithm unsupported", undef);
  14979. } elsif ($mac_algo == 1) {
  14980. # CMAC is a 3-byte-long code
  14981. $attr{$name}{macAlgo} = '3';
  14982. } elsif ($mac_algo == 2) {
  14983. # MAC is a 4-byte-long code
  14984. $attr{$name}{macAlgo} = '4';
  14985. } else {
  14986. # Undefined MAC algorith;
  14987. # Nothing we can do either...
  14988. #$attr{$name}{macAlgo} = 'no';
  14989. return ("Undefined MAC algorithm $mac_algo", undef);
  14990. }
  14991. # Decode data encryption algorithm
  14992. if ($data_enc == 0) {
  14993. # Data not encrypted? Right now we will handle this like an error, concrete use case untested
  14994. #$attr{$name}{secLevel} = 'encapsulation';
  14995. return ("Secure mode message without data encryption unsupported", undef);
  14996. } elsif ($data_enc == 1) {
  14997. # Unspecified
  14998. return ("Undefined data encryption algorithm $data_enc", undef);
  14999. } elsif ($data_enc == 2) {
  15000. # Unspecified
  15001. return ("Undefined data encryption algorithm $data_enc", undef);
  15002. } elsif ($data_enc == 3) {
  15003. # Data will be encrypted/decrypted XORing with a string obtained from a AES128 encryption
  15004. $attr{$name}{dataEnc} = 'VAES';
  15005. $attr{$name}{secLevel} = 'encryption';
  15006. } elsif ($data_enc == 4) {
  15007. # Data will be encrypted/decrypted using the AES128 algorithm in CBC mode
  15008. # Might be used in the future right now untested
  15009. #$attr{$name}{dataEnc} = 'AES-CBC';
  15010. #$attr{$name}{secLevel} = 'encryption';
  15011. return ("Secure mode message with AES-CBC data encryption unsupported", undef);
  15012. } else {
  15013. # Something went horribly wrong
  15014. return ("Could not parse data encryption information, $data_enc", undef);
  15015. }
  15016. $hash->{helper}{teachInSTE} = $cnt - 1;
  15017. # Ok we got a lots of infos and the first part of the private key
  15018. return (undef, "part 1 received Rlc: $rlc Key1: $key1");
  15019. } elsif ($idx == 1 && exists($hash->{helper}{teachInSTE})) {
  15020. # Second part of the teach-in telegrams
  15021. # Extract byte fields from telegram
  15022. # Don't care about info fields, KEY, ID, don't care about status
  15023. $telegram =~ /^..(.*)$/; # TODO Parse error handling?
  15024. $key2 = $1;
  15025. # We already should have gathered the infos from the first teach-in telegram
  15026. if (!defined($attr{$name}{keyRcv})) {
  15027. # We have missed the first telegram
  15028. return ("Missing first teach-in telegram", undef);
  15029. }
  15030. # Append second part of private key to first part of private key
  15031. $attr{$name}{keyRcv} .= $key2;
  15032. if ($attr{$name}{secMode} eq "biDir") {
  15033. # bidirectional secure teach-in
  15034. if (!defined $subDef) {
  15035. $subDef = EnOcean_CheckSenderID("getNextID", $defs{$name}{IODev}{NAME}, "00000000");
  15036. $attr{$name}{subDef} = $subDef;
  15037. }
  15038. ($err, $response, $logLevel) = EnOcean_sec_createTeachIn(undef, $hash, $attr{$name}{comMode},
  15039. $attr{$name}{dataEnc}, $attr{$name}{eep},
  15040. $attr{$name}{macAlgo}, $attr{$name}{rlcAlgo},
  15041. $attr{$name}{rlcTX}, $attr{$name}{secLevel},
  15042. $subDef, $destinationID);
  15043. if ($err) {
  15044. Log3 $name, $logLevel, "EnOcean $name Error: $err";
  15045. return $err;
  15046. } else {
  15047. Log3 $name, $logLevel, "EnOcean $name $response";
  15048. }
  15049. }
  15050. delete $hash->{helper}{teachInSTE};
  15051. return (undef, "part 2 received Key2: $key2");
  15052. }
  15053. # Sequence error?
  15054. return ("teach-in sequence error IDC: $idx CNT: $cnt", undef);
  15055. }
  15056. # Do VAES decyrption
  15057. # All parameters need to be passed as byte strings
  15058. #
  15059. # Parameter 1: Current rolling code, 16 bytes
  15060. # Parameter 2: Private key, 16bytes
  15061. # Paremeter 3: Encrypted data, 16bytes
  15062. #
  15063. # Returns: Decrypted data, 16 bytes
  15064. #
  15065. # Decryption of more than 16bytes of data is currently unsupported
  15066. #
  15067. sub EnOcean_sec_decodeVAES($$$) {
  15068. my $rlc = $_[0];
  15069. my $private_key = $_[1];
  15070. my $data_enc = $_[2];
  15071. # Public key according to EnOcean Security specification
  15072. my $public_key = pack('H32', '3410de8f1aba3eff9f5a117172eacabd');
  15073. # Input for VAES
  15074. my $aes_in = $public_key ^ $rlc;
  15075. #print "--\n";
  15076. #print "Public Key ".unpack('H32', $public_key)."\n";
  15077. #print "RLC ".unpack('H32', $rlc)."\n";
  15078. #print "AES input ".unpack('H32', $aes_in)."\n";
  15079. #print "--\n";
  15080. #print "Private Key ".unpack('H32', $private_key)."\n";
  15081. my $cipher = Crypt::Rijndael->new( $private_key );
  15082. my $aes_out = $cipher->encrypt($aes_in);
  15083. #print "AES output ".unpack('H32', $aes_out)."\n";
  15084. #print "Data_enc: ".unpack('H32', $data_enc)."\n";
  15085. my $data_dec = $data_enc ^ $aes_out;
  15086. #print "Data_dec: ".unpack('H32', $data_dec)."\n";
  15087. return $data_dec;
  15088. }
  15089. # Returns current RLC in hex format and increments the stored RLC
  15090. # Checks the boundaries of the RLC for roll-over
  15091. #
  15092. # Parameter 1: Sender ID in hexadecimal format for lookup in receivers hash
  15093. #
  15094. # Affects: receivers hash
  15095. #
  15096. # Returns: RLC in hexadecimal format
  15097. #
  15098. sub EnOcean_sec_getRLC($$) {
  15099. my $hash = $_[0];
  15100. my $rlcVar = $_[1];
  15101. my $name = $hash->{NAME};
  15102. # Fetch newest RLC from receiver hash
  15103. my $old_rlc = ReadingsVal($name, "." . $rlcVar, $attr{$name}{$rlcVar});
  15104. if (hex($old_rlc) < hex($attr{$name}{$rlcVar})) {
  15105. $old_rlc = $attr{$name}{$rlcVar};
  15106. }
  15107. Log3 $name, 5, "EnOcean $name EnOcean_sec_getRLC RLC old: $old_rlc " . hex($old_rlc);
  15108. # Advance RLC by one
  15109. my $new_rlc = hex($old_rlc) + 1;
  15110. # Boundary check
  15111. if ($attr{$name}{rlcAlgo} eq '2++') {
  15112. if ($new_rlc > 65535) {
  15113. #print "RLC rollover\n";
  15114. Log3 $name, 5, "EnOcean $name EnOcean_sec_getRLC RLC rollover";
  15115. $new_rlc = 0;
  15116. $attr{$name}{$rlcVar} = "0000";
  15117. EnOcean_CommandSave(undef, undef);
  15118. }
  15119. readingsSingleUpdate($hash, "." . $rlcVar, uc(unpack('H4',pack('n', $new_rlc))), 0);
  15120. $attr{$name}{$rlcVar} = uc(unpack('H4',pack('n', $new_rlc)));
  15121. } elsif ($attr{$name}{rlcAlgo} eq '3++') {
  15122. if ($new_rlc > 16777215) {
  15123. #print "RLC rollover\n";
  15124. Log3 $name, 5, "EnOcean $name EnOcean_sec_getRLC RLC rollover";
  15125. $new_rlc = 0;
  15126. $attr{$name}{$rlcVar} = "000000";
  15127. EnOcean_CommandSave(undef, undef);
  15128. }
  15129. readingsSingleUpdate($hash, "." . $rlcVar, sprintf("%06X", $new_rlc), 0);
  15130. $attr{$name}{$rlcVar} = sprintf("%06X", $new_rlc);
  15131. }
  15132. Log3 $name, 5, "EnOcean $name EnOcean_sec_getRLC RLC new: $attr{$name}{$rlcVar} $new_rlc";
  15133. return $old_rlc;
  15134. }
  15135. # Generate MAC of data
  15136. #
  15137. # Parameter 1: private key as byte string, 16bytes
  15138. # Parameter 2: data fro which mac should be calculated in hexadecimal format, len variable
  15139. # Parameter 3: length of MAC to be generated in bytes
  15140. #
  15141. # Returns: MAC in hexadecimal format
  15142. #
  15143. # This function currently supports data with lentgh of less then 16bytes,
  15144. # MAC for longer data is untested but specified
  15145. #
  15146. sub EnOcean_sec_generateMAC($$$) {
  15147. my $private_key = $_[0];
  15148. my $data = $_[1];
  15149. my $cmac_len = $_[2];
  15150. #print "Calculating MAC for data $data\n";
  15151. Log3 undef, 5, "EnOcean_sec_generateMAC Calculating MAC for data $data";
  15152. Log3 undef, 5, "EnOcean_sec_generateMAC private key ".unpack('H32', $private_key);
  15153. # Pack data to 16byte byte string, padd with 10..0 binary
  15154. my $data_expanded = pack('H32', $data.'80');
  15155. #print "Exp. data ".unpack('H32', $data_expanded)."\n";
  15156. # Constants according to specification
  15157. my $const_zero = pack('H32','00');
  15158. my $const_rb = pack('H32', '00000000000000000000000000000087');
  15159. # Encrypt zero data with private key to get L
  15160. my $cipher = Crypt::Rijndael->new($private_key);
  15161. my $l = $cipher->encrypt($const_zero);
  15162. #print "L ".unpack('H32', $l)."\n";
  15163. #print "L ".unpack('B128', $l)."\n";
  15164. # Expand L to 128bit string
  15165. my $l_bit = unpack('B128', $l);
  15166. # K1 and K2 stored as 128bit string
  15167. my $k1_bit;
  15168. my $k2_bit;
  15169. # K1 and K2 as binary
  15170. my $k1;
  15171. my $k2;
  15172. # Store L << 1 in K1
  15173. $l_bit =~ /^.(.{127})/;
  15174. $k1_bit = $1.'0';
  15175. $k1 = pack('B128', $k1_bit);
  15176. # If MSB of L == 1, K1 = K1 XOR const_Rb
  15177. if($l_bit =~ m/^1/) {
  15178. #print "MSB of L is set\n";
  15179. $k1 = $k1 ^ $const_rb;
  15180. $k1_bit = unpack('B128', $k1);
  15181. } else {
  15182. #print "MSB of L is unset\n";
  15183. }
  15184. # Store K1 << 1 in K2
  15185. $k1_bit =~ /^.(.{127})/;
  15186. $k2_bit = $1.'0';
  15187. $k2 = pack('B128', $k2_bit);
  15188. # If MSB of K1 == 1, K2 = K2 XOR const_Rb
  15189. if($k1_bit =~ m/^1/) {
  15190. #print "MSB of K1 is set\n";
  15191. $k2 = $k2 ^ $const_rb;
  15192. } else {
  15193. #print "MSB of K1 is unset\n";
  15194. }
  15195. # XOR data with K2
  15196. $data_expanded ^= $k2;
  15197. # Encrypt data
  15198. my $cmac = $cipher->encrypt($data_expanded);
  15199. #print "CMAC ".unpack('H32', $cmac)."\n";
  15200. Log3 undef, 5, "EnOcean_sec_generateMAC CMAC ".unpack('H32', $cmac);
  15201. # Extract specified len of MAC
  15202. my $cmac_pattern = '^(.{'.($cmac_len * 2).'})';
  15203. unpack('H32', $cmac) =~ /$cmac_pattern/;
  15204. Log3 undef, 5, "EnOcean_sec_generateMAC cutted CMAC ".unpack('H32', $1);
  15205. # Return MAC in hexadecimal format
  15206. return uc($1);
  15207. }
  15208. # Verify (MAC) and decode/decrypt secure mode message
  15209. #
  15210. # Parameter 1: content of radio telegram in hexadecimal format
  15211. #
  15212. # Returns: "ERROR-" + error description, "OK-" + EEP D2-03-00 telegram in hexadecimal format
  15213. #
  15214. # Right now we only decode PTM215 telegrams which are transmitted as RORG 30 and without
  15215. # encapsulation. Encapsulation of other telegrams is possible and specified but untested due to the
  15216. # lack of hardware suporting this.
  15217. #
  15218. sub EnOcean_sec_convertToNonsecure($$$) {
  15219. my ($hash, $rorg, $crypt_data) = @_;
  15220. my $name = $hash->{NAME};
  15221. if ($cryptFunc == 0) {
  15222. return ("Cryptographic functions are not available", undef, undef);
  15223. }
  15224. my $private_key;
  15225. # Prefix of pattern to extract the different cryptographic infos
  15226. my $crypt_pattern = "^(.*)";;
  15227. # Flags and infos for fields to expect
  15228. my $expect_rlc = 0;
  15229. my $expect_mac = 0;
  15230. my $mac_len;
  15231. my $expect_enc = 0;
  15232. # Check if RLC is transmitted and when, which length to expect
  15233. if($attr{$name}{rlcTX} eq 'true') {
  15234. # Message should contain RLC
  15235. if ($attr{$name}{rlcAlgo} eq '2++') {
  15236. $crypt_pattern .= "(....)";
  15237. $expect_rlc = 1;
  15238. } elsif ($attr{$name}{rlcAlgo} eq '3++') {
  15239. $crypt_pattern .= "(......)";
  15240. $expect_rlc = 1;
  15241. } else {
  15242. # RLC_TX but no info on RLC length
  15243. return ("RLC_TX and RLC_ALGO inconsistent", undef, undef);
  15244. }
  15245. }
  15246. # Check what length of MAC to expect
  15247. if($attr{$name}{macAlgo} eq '3') {
  15248. $crypt_pattern .= "(......)";
  15249. $mac_len = 3;
  15250. $expect_mac = 1;
  15251. } elsif ($attr{$name}{macAlgo} eq '4') {
  15252. $crypt_pattern .= "(........)";
  15253. $mac_len = 4;
  15254. $expect_mac = 1;
  15255. } else {
  15256. # According to the specification it's possible to transmit no MAC, bt we don't implement this for now
  15257. return ("Secure mode messages without MAC unsupported", undef, undef);
  15258. }
  15259. # Suffix for crypt pattern
  15260. $crypt_pattern .= '$';
  15261. # Extract byte fields from message payload
  15262. $crypt_data =~ /$crypt_pattern/;
  15263. my $data_enc = $1;
  15264. my $dataLength = length($data_enc);
  15265. return ("Telegrams with a length of more than 16 bytes are not supported", undef, undef) if ($dataLength > 32);
  15266. my $rlc;
  15267. my $mac;
  15268. if ($expect_rlc == 1 && $expect_mac == 1) {
  15269. $rlc = $2;
  15270. $mac = $3;
  15271. } elsif ($expect_rlc == 0 && $expect_mac == 1) {
  15272. $mac = $2;
  15273. }
  15274. Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RORG: $rorg DATA_ENC: $data_enc";
  15275. if ($expect_rlc == 1) {
  15276. Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RLC: $rlc";
  15277. };
  15278. Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure MAC: $mac";
  15279. # TODO RLC could be transmitted with data, could not test this
  15280. #if(!defined($rlc)) {
  15281. # print "No RLC in message, using stored value\n";
  15282. # $rlc = getRLC($senderID);
  15283. #}
  15284. # Maximum RLC search window is 128
  15285. foreach my $rlc_window (0..128) {
  15286. #print "Trying RLC offset $rlc_window\n";
  15287. # Fetch stored RLC
  15288. $rlc = EnOcean_sec_getRLC($hash, "rlcRcv");
  15289. # Fetch private Key for VAES
  15290. if ($attr{$name}{keyRcv} =~ /[\dA-F]{32}/) {
  15291. $private_key = pack('H32',$attr{$name}{keyRcv});
  15292. } else {
  15293. return ("private key wrong, please teach-in the device new", undef, undef);
  15294. }
  15295. # Generate and check MAC over RORG+DATA+RLC fields
  15296. if ($mac eq EnOcean_sec_generateMAC($private_key, $rorg.$data_enc.$rlc, $mac_len)) {
  15297. #print "RLC verfified\n";
  15298. # Expand RLC to 16byte
  15299. my $rlc_expanded = pack('H32',$rlc);
  15300. # Expand data to 16byte
  15301. my $data_expanded = pack('H32',$data_enc);
  15302. # Decode data using VAES
  15303. my $data_dec = EnOcean_sec_decodeVAES($rlc_expanded, $private_key, $data_expanded);
  15304. my $data_end = unpack('H32', $data_dec);
  15305. if ($rorg eq '30') {
  15306. $data_end =~ /^(.{$dataLength})/;
  15307. return (undef, '32', uc($1));
  15308. #if ($dataLength == 2) {
  15309. # Extract one nibble of data
  15310. # $data_end =~ /^.(.)/;
  15311. # return (undef, '32', "0" . uc($1));
  15312. #} else {
  15313. # $data_end =~ /^(.{$dataLength})/;
  15314. # return (undef, '32', uc($1));
  15315. #}
  15316. } else {
  15317. $dataLength -= 2;
  15318. $data_end =~ /^(..)(.{$dataLength})/;
  15319. Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RORG: " . uc($1) . " DATA: " . uc($2);
  15320. return (undef, uc($1), uc($2));
  15321. }
  15322. }
  15323. }
  15324. # Couldn't verify or decrypt message in RLC window
  15325. return ("Can't verify or decrypt telegram", undef, undef);
  15326. }
  15327. #
  15328. sub EnOcean_sec_createTeachIn($$$$$$$$$$$)
  15329. {
  15330. my ($ctrl, $hash, $comMode, $dataEnc, $eep, $macAlgo, $rlcAlgo, $rlcTX, $secLevel, $subDef, $destinationID) = @_;
  15331. my $name = $hash->{NAME};
  15332. my ($data, $err, $response, $loglevel);
  15333. # THIS IS A BASIC IMPLEMENTATION WITH HARDCODED VALUES FOR
  15334. # THE SECURITY PARAMETERS, WILL BE CUSTOMIZABLE IN FUTURE
  15335. if ($cryptFunc == 0) {
  15336. return ("Cryptographic functions are not available", undef, 2);
  15337. }
  15338. # generate random private key
  15339. my $pKey;
  15340. for (my $i = 1; $i < 5; $i++) {
  15341. $pKey .= uc(unpack('H8', pack('L', makerandom(Size => 32, Strength => 1))));
  15342. }
  15343. $attr{$name}{keySnd} = AttrVal($name, "keySnd", $pKey);
  15344. #generate random rlc, save to fhem.cfg and update readings
  15345. my $rlc = ReadingsVal($name, ".rlcSnd", uc(unpack('H4', pack('n', makerandom(Size => 16, Strength => 1)))));
  15346. readingsSingleUpdate($hash, ".rlcSnd", $rlc, 0);
  15347. $attr{$name}{rlcSnd} = $rlc;
  15348. $attr{$name}{comMode} = AttrVal($name, "comMode", $comMode);
  15349. $attr{$name}{dataEnc} = AttrVal($name, "dataEnc", $dataEnc);
  15350. $attr{$name}{eep} = $eep;
  15351. $attr{$name}{macAlgo} = AttrVal($name, "macAlgo", $macAlgo);
  15352. $attr{$name}{manufID} = "7FF";
  15353. $attr{$name}{rlcAlgo} = AttrVal($name, "rlcAlgo", $rlcAlgo);
  15354. $attr{$name}{rlcTX} = AttrVal($name, "rlcTX", $rlcTX);
  15355. $attr{$name}{secLevel} = AttrVal($name, "secLevel", $secLevel);
  15356. if (AttrVal($name, "secMode", "") =~ m/^rcv|bidir$/) {
  15357. $attr{$name}{secMode} = "biDir";
  15358. } else {
  15359. $attr{$name}{secMode} = "snd";
  15360. }
  15361. # prepare 1st telegram
  15362. #RORG = 35, TEACH_IN_INFO_0, SLF, RLC, KEY, ID, STATUS as defined in Security_of_EnOcean_Radio_Networks.pdf page 17
  15363. #set TEACH_IN_INFO = 25 -> 0001.0101 -> IDX =0, CNT = 2, PSK = 0, TYPE = 1, INFO = 1
  15364. #set SLF = 4B -> 0100.1011 -> RLC_ALGO=16bit, RLC-TX=0, MAC-ALGO = AES3BYTE, DATA_ENC = VAES128
  15365. #save the fixed security parameters to fhem.cfg
  15366. #get first 5 bytes of private key
  15367. #data 1: 25 4B r1 r2 k1 k2 k3 k4 k5
  15368. $data = "254B" . $rlc . substr($attr{$name}{keySnd}, 0, 5*2);
  15369. EnOcean_SndRadio(undef, $hash, 1, "35", $data, $subDef, "00", $destinationID);
  15370. # prepare 2nd telegram
  15371. #RORG = 35, TEACH_IN_INFO_1, KEY, ID, STATUS as defined in Security_of_EnOcean_Radio_Networks.pdf page 17
  15372. #set TEACH_IN_INFO = 40 -> 0100.0000 -> IDX =1, CNT = 0, PSK = 0, TYPE = 0, INFO = 0
  15373. #get 2nd 11 bytes of private key
  15374. #data 2: 40 k6 k7 k8 k9 k10 k11 k12 k13 k14 k15 k16
  15375. $data = "40" . substr($attr{$name}{keySnd}, 10, 11*2);
  15376. EnOcean_SndRadio(undef, $hash, 1, "35", $data, $subDef, "00", $destinationID);
  15377. return (undef, "secure teach-in", 2);
  15378. }
  15379. #
  15380. sub EnOcean_sec_convertToSecure($$$$)
  15381. {
  15382. my ($hash, $packetType, $rorg, $data) = @_;
  15383. my ($err, $response, $loglevel);
  15384. my $name = $hash->{NAME};
  15385. my $secLevel = AttrVal($name, "secLevel", "off");
  15386. # encryption needed?
  15387. return ($err, $rorg, $data, $response, 5) if ($rorg =~ m/^F6|35$/ || $secLevel !~ m/^encapsulation|encryption$/);
  15388. return ("Cryptographic functions are not available", undef, undef, $response, 2) if ($cryptFunc == 0);
  15389. my $dataEnc = AttrVal($name, "dataEnc", undef);
  15390. my $subType = AttrVal($name, "subType", "");
  15391. # subType specific actions
  15392. if ($subType eq "switch.00" || $subType eq "windowHandle.10") {
  15393. # securemode for D2-03-00 and D2-03-10
  15394. if (hex($data) > 15) {
  15395. return("wrong data byte", $rorg, $data, $response, 2);
  15396. }
  15397. # set rorg to secure telegram
  15398. $rorg = "30";
  15399. } else {
  15400. return("Cryptographic functions for $subType not available", $rorg, $data, $response, 2);
  15401. }
  15402. #Get and update RLC
  15403. my $rlc = EnOcean_sec_getRLC($hash, "rlcSnd");
  15404. #Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: Got actual RLC: $rlc";
  15405. #Get key of device
  15406. my $pKey = AttrVal($name, "keySnd", undef);
  15407. return("private key not defined", $rorg, $data, $response, 2) if (!defined $pKey);
  15408. $pKey = pack('H32', $pKey);
  15409. #Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: key: " . AttrVal($name, "keySnd", "");
  15410. #prepare data
  15411. my $rlc_expanded = pack('H32', $rlc);
  15412. my $data_expanded = pack('H32', $data);
  15413. my $data_dec;
  15414. if ($dataEnc eq "VAES") {
  15415. $data_dec = EnOcean_sec_decodeVAES($rlc_expanded, $pKey, $data_expanded);
  15416. } else {
  15417. return("Cryptographic functions not available", $rorg, $data, $response, 2);
  15418. }
  15419. my $data_end = unpack('H32', $data_dec);
  15420. #get the correct nibble
  15421. $data_end =~ /^.(.)/;
  15422. $data_end = uc("0$1");
  15423. #Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: Crypted Data: $data_end";
  15424. # calc MAC
  15425. my $macAlgo = AttrVal($name, "macAlgo", undef);
  15426. return("MAC Algorithm not defined", $rorg, $data, $response, 2) if (!defined $macAlgo);
  15427. my $mac = EnOcean_sec_generateMAC($pKey, $rorg . $data_end . $rlc, $macAlgo);
  15428. # combine message
  15429. $data = $data_end . uc($mac);
  15430. #Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: Crypted Payload: $data";
  15431. return(undef, $rorg, $data, $response, 5);
  15432. }
  15433. #
  15434. sub
  15435. EnOcean_NumericSort
  15436. {
  15437. if ($a < $b) {
  15438. return -1;
  15439. } elsif ($a == $b) {
  15440. return 0;
  15441. } else {
  15442. return 1;
  15443. }
  15444. }
  15445. sub EnOcean_TimeDiff($)
  15446. {
  15447. my ($strTS) = @_;
  15448. if (defined $strTS) {
  15449. my $timeDiff = gettimeofday() - ($strTS eq "" ? gettimeofday() : time_str2num($strTS));
  15450. $timeDiff = 0 if ($timeDiff < 0);
  15451. return $timeDiff;
  15452. } else {
  15453. return 0;
  15454. }
  15455. }
  15456. # Undef
  15457. sub
  15458. EnOcean_Undef($$)
  15459. {
  15460. my ($hash, $name) = @_;
  15461. delete $hash->{helper};
  15462. delete $modules{EnOcean}{defptr}{uc($hash->{DEF})};
  15463. delete $modules{EnOcean}{defptr}{uc($attr{$name}{remoteID})} if (exists $attr{$name}{remoteID});
  15464. if (AttrVal($name, "remoteManagement", "off") eq "client") {
  15465. delete $hash->{RemoteClientUnlock};
  15466. #####
  15467. #my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
  15468. #RemoveInternalTimer(\%functionHash);
  15469. RemoveInternalTimer($hash->{helper}{timer}{RemoteClientUnlock}) if(exists $hash->{helper}{timer}{RemoteClientUnlock});
  15470. }
  15471. return undef;
  15472. }
  15473. # Delete
  15474. sub
  15475. EnOcean_Delete($$)
  15476. {
  15477. my ($hash, $name) = @_;
  15478. my $logName = "FileLog_$name";
  15479. my ($count, $gplotFile, $logFile, $weblinkName, $weblinkHash);
  15480. Log3 $name, 2, "EnOcean $name deleted";
  15481. # delete FileLog device and log files
  15482. if (exists $defs{$logName}) {
  15483. $logFile = $defs{$logName}{logfile};
  15484. $logFile =~ /^(.*)($name).*\.(.*)$/;
  15485. $logFile = $1 . $2 . "*." . $3;
  15486. CommandDelete(undef, "FileLog_$name");
  15487. Log3 $name, 2, "EnOcean FileLog_$name deleted";
  15488. $count = unlink glob $logFile;
  15489. Log3 $name, 2, "EnOcean $logFile >> $count files deleted";
  15490. }
  15491. # delete SVG devices and gplot files
  15492. while (($weblinkName, $weblinkHash) = each(%defs)) {
  15493. if ($weblinkName =~ /^SVG_$name.*/) {
  15494. $gplotFile = "./www/gplot/" . $defs{$weblinkName}{GPLOTFILE} . "*.gplot";
  15495. CommandDelete(undef, $weblinkName);
  15496. Log3 $name, 2, "EnOcean $weblinkName deleted";
  15497. $count = unlink glob $gplotFile;
  15498. Log3 $name, 2, "EnOcean $gplotFile >> $count files deleted";
  15499. }
  15500. }
  15501. return undef;
  15502. }
  15503. 1;
  15504. =pod
  15505. =item device
  15506. =item summary EnOcean Gateway and Actor
  15507. =item summary_DE EnOcean Gateway und Aktor
  15508. =begin html
  15509. <a name="EnOcean"></a>
  15510. <h3>EnOcean</h3>
  15511. <ul><br>
  15512. <b>Quick Links</b>
  15513. <ul>
  15514. <li><a href="#EnOceanget">Get Commands</a></li>
  15515. <li><a href="#EnOceanset">Set Commands</a></li>
  15516. <li><a href="#EnOceanattr">Attributes</a></li>
  15517. <li><a href="#EnOceanevents">Generated Events</a></li>
  15518. </ul><br><br>
  15519. EnOcean devices are sold by numerous hardware vendors (e.g. Eltako, Peha, etc),
  15520. using the RF Protocol provided by the EnOcean Alliance.<br><br>
  15521. Depending on the function of the device an specific device profile is used, called
  15522. EnOcean Equipment Profile (EEP). The specific definition of a device is referenced by
  15523. the EEP (RORG-FUNC-TYPE). Basically four groups (RORG) will be differed, e. g.
  15524. RPS (switches), 1BS (contacts), 4BS, VLD (sensors and controller). Some manufacturers use
  15525. additional proprietary extensions. RORG MSC is not supported except for few exceptions.
  15526. Further technical information can be found at the
  15527. <a href="http://www.enocean-alliance.org/de/enocean_standard/">EnOcean Alliance</a>,
  15528. see in particular the
  15529. <a href="http://www.enocean-alliance.org/eep/">EnOcean Equipment Profiles (EEP)</a>
  15530. <br><br>
  15531. The supplementary Generic Profiles approach instead defines a language to communicate the
  15532. transmitted data types and ranges. The devices becomes self describing on their data
  15533. structures in communication. The Generic Profiles include a language definition with
  15534. a parameter selection that covers every possible measured value to be transmitted.
  15535. Therefore, the approach does not only define parameters for the value recalculation algorithm
  15536. but also includes specific signal definition. (e.g. physical units). Further technical
  15537. information can be found at the
  15538. <a href="https://www.enocean-alliance.org/fileadmin/redaktion/enocean_alliance/pdf/GenericProfiles_V1_Extract.pdf">Generic Profiles 1.0 Abstract</a>
  15539. <br><br>
  15540. Smart Acknowledge (Smart Ack) enables a special bidirectional communication. The communication is managed by a
  15541. Controller that responds to the devices telegrams with acknowledges. Smart Ack is a bidirectional communication
  15542. protocol between two actors. At least one actor must be an energy autarkic Sensor, and at least one must be a line
  15543. powered Controller (Fhem). A sensor sends its data and expects the answer telegram in a predefined very short
  15544. time slot. In this time Sensors receiver is active. For this purpose we declare a Post Master with Mail Boxes.
  15545. A Mail Box is like a letter box for a Sensor and it specific to a single sender. Telegrams from Fhem are collected
  15546. into the Mail Box. A Sensor can reclaim telegrams that are in his Mail Box.
  15547. <br><br>
  15548. Fhem recognizes a number of devices automatically. In order to teach-in, for
  15549. some devices the sending of confirmation telegrams has to be turned on.
  15550. Some equipment types and/or device models must be manually specified.
  15551. Do so using the <a href="#EnOceanattr">attributes</a>
  15552. <a href="#subType">subType</a> and <a href="#model">model</a>, see chapter
  15553. <a href="#EnOceanset">Set</a> and
  15554. <a href="#EnOceanevents">Generated events</a>. With the help of additional
  15555. <a href="#EnOceanattr">attributes</a>, the behavior of the devices can be
  15556. changed separately.
  15557. <br><br>
  15558. Fhem and the EnOcean devices must be trained with each other. To this, Fhem
  15559. must be in the learning mode, see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>,
  15560. <a href="#EnOcean_smartAck">Smart Ack Learning</a> and <a href="#TCM_learningMode">learningMode</a>.
  15561. The teach-in procedure depends on the type of the devices.
  15562. <br><br>
  15563. Switches (EEP RPS) and contacts (EEP 1BS) are recognized when receiving the first message.
  15564. Contacts can also send a teach-in telegram. Fhem not need this telegram.
  15565. Sensors (EEP 4BS) has to send a teach-in telegram. The profile-less
  15566. 4BS teach-in procedure transfers no EEP profile identifier and no manufacturer
  15567. ID. In this case Fhem does not recognize the device automatically. The proper
  15568. device type must be set manually, use the <a href="#EnOceanattr">attributes</a>
  15569. <a href="#subType">subType</a>, <a href="#manufID">manufID</a> and/or
  15570. <a href="#model">model</a>. If the EEP profile identifier and the manufacturer
  15571. ID are sent the device is clearly identifiable. Fhem automatically assigns
  15572. these devices to the correct profile.
  15573. <br><br>
  15574. 4BS devices can also be taught in special cases by using of confirmation telegrams. This method
  15575. is used for the EnOcean Tipp-Funk devices. The function is activated via the attribute [<a href="#EnOcean_teachMethod">teachMethod</a>] = confirm.<br>
  15576. For example the remote device Eltako TF100D can be learned as follows
  15577. <ul><br>
  15578. <code>define &lt;name&gt; EnOcean H5-38-08</code><br>
  15579. set TF100D in learning mode<br>
  15580. <code>set &lt;name&gt; teach</code>
  15581. </ul>
  15582. <br>
  15583. Some 4BS, VLD or MSC devices must be paired bidirectional,
  15584. see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.
  15585. <br><br>
  15586. Devices that communicate encrypted, has to taught-in through specific procedures.
  15587. <br><br>
  15588. Smart Ack Learning is a futher process where devices exchange information about each
  15589. other in order to create the logical links in the EnOcean network and a Post Master Mail Box.
  15590. It can result in Learn In or Learn Out, see <a href="#EnOcean_smartAck">Smart Ack Learning</a>.
  15591. <br><br>
  15592. Fhem supports many of most common EnOcean profiles and manufacturer-specific
  15593. devices. Additional profiles and devices can be added if required.
  15594. <br><br>
  15595. In order to enable communication with EnOcean remote stations a
  15596. <a href="#TCM">TCM</a> module is necessary.
  15597. <br><br>
  15598. Please note that EnOcean repeaters also send Fhem data telegrams again.
  15599. Use the TCM <code>attr &lt;name&gt; <a href="#blockSenderID">blockSenderID</a> own</code>
  15600. to block receiving telegrams with a TCM SenderIDs.
  15601. <br><br>
  15602. <b>Observing Functions</b><br>
  15603. <ul>
  15604. Interference or overloading of the radio transmission can prevent the reception of Fhem
  15605. commands at the receiver. With the help of the observing function Fhem checks the reception
  15606. of the acknowledgment telegrams of the actuator. If within one second no acknowledgment
  15607. telegram is received, the last set command is sent again.
  15608. The set command is repeated a maximum of 5 times. The maximum number can be specified in the attribute
  15609. <a href="#EnOcean_observeCmdRepetition">observeCmdRepetition</a>.<br>
  15610. The function can only be used if the actuator immediately after the reception of
  15611. the set command sends an acknowledgment message.<br>
  15612. The observing function is turned on by the Attribute <a href="#EnOcean_observe">observe.</a>
  15613. In addition, further devices can be monitored. The names of this devices can be entered in the
  15614. <a href="#EnOcean_observeRefDev">observeRefDev</a> attribute. If additional device are specified,
  15615. the monitoring is stopped as soon as the first acknowledgment telegram of one of the devices was received (OR logic).
  15616. If the <a href="#EnOcean_observeLogic">observeLogic</a> attribute is set to "and", the monitoring is stopped when a telegram
  15617. was received by all devices (AND logic). Please note that the name of the own device has also to be entered in the
  15618. <a href="#EnOcean_observeRefDev">observeRefDev</a> if required.<br>
  15619. If the maximum number of retries is reached and still no all acknowledgment telegrams has been received, the reading
  15620. "observeFailedDev" shows the faulty devices and the command can be executed, that is stored in the
  15621. <a href="#EnOcean_observeErrorAction">observeErrorAction</a> attribute.
  15622. <br><br>
  15623. </ul>
  15624. <b>Energy Management</b><br>
  15625. <ul>
  15626. <li><a href="#demand_response">Demand Response</a> (EEP A5-37-01)</li>
  15627. Demand Response (DR) is a standard to allow utility companies to send requests for reduction in power
  15628. consumption during peak usage times. It is also used as a means to allow users to reduce overall power
  15629. comsumption as energy prices increase. The EEP was designed with a very flexible setting for the level
  15630. (0...15) as well as a default level whereby the transmitter can specify a specific level for all
  15631. controllers to use (0...100 % of either maximum or current power output, depending on the load type).
  15632. The profile also includes a timeout setting to indicate how long the DR event should last if the
  15633. DR transmitting device does not send heartbeats or subsequent new DR levels.<br>
  15634. The DR actor controls the target actuators such as switches, dimmers etc. The DR actor
  15635. is linked to the FHEM target actors via the attribute <a href="#EnOcean_demandRespRefDev">demandRespRefDev</a>.<br>
  15636. <ul>
  15637. <li>Standard actions are available for the following profiles:</li>
  15638. <ul>
  15639. <li>switch (setting the switching command for min, max by the attribute <a href="#EnOcean_demandRespMin">demandRespMin</a>,
  15640. <a href="#EnOcean_demandRespMax">demandRespMax</a>)</li>
  15641. <li>gateway/switching (on, off)</li>
  15642. <li>gateway/dimming (dim 0...100, relative to the max or current set value)</li>
  15643. <li>lightCtrl.01 (dim 0...255)</li>
  15644. <li>actuator.01 (dim 0...100)</li>
  15645. <li>roomSensorControl.01 (setpoint 0...255)</li>
  15646. <li>roomSensorControl.05 (setpoint 0...255 or nightReduction 0...5 for Eltako devices)</li>
  15647. <li>roomCtrlPanel.00 (roomCtrlMode comfort|economy)</li>
  15648. </ul>
  15649. <li>On the target actuator can be specified alternatively a freely definable command.
  15650. The command sequence is stored in the attribute <a href="#EnOcean_demandRespAction">demandRespAction</a>.
  15651. The command sequence can be designed similar to "notify". For the command sequence predefined variables can be used,
  15652. eg. $LEVEL. This actions can be executed very flexible depending on the given energy
  15653. reduction levels.
  15654. </li>
  15655. <li>Alternatively or additionally, a custom command sequence in the DR profile itself
  15656. can be stored.
  15657. </li>
  15658. </ul>
  15659. The profile has a master and slave mode.
  15660. <ul>
  15661. <li>In slave mode, demand response data telegrams received eg a control unit of the power utility,
  15662. evaluated and the corresponding commands triggered on the linked target actuators. The behavior in
  15663. slave mode can be changed by multiple attributes.
  15664. </li>
  15665. <li>In master mode, the demand response level is set by set commands and thus sends a corresponding
  15666. data telegram and the associated target actuators are controlled. The demand response control
  15667. value are specified by "level", "power", "setpoint" "max" or "min". Each other settings are
  15668. calculated proportionally. In normal operation, ie without power reduction, the control value (level)
  15669. is 15. Through the optional parameters "powerUsageScale", "randomStart", "randomEnd" and "timeout"
  15670. the control behavior can be customized. The threshold at which the reading "powerUsageLevel"
  15671. between "min" and "max" switch is specified with the attribute
  15672. <a href="#EnOcean_demandRespThreshold">demandRespThreshold</a>.
  15673. </li>
  15674. </ul>
  15675. Additional information about the profile itself can be found in the EnOcean EEP documentation.
  15676. <br><br>
  15677. </ul>
  15678. <b>Remote Management</b><br>
  15679. <ul>
  15680. Remote Management allows EnOcean devices to be configured and maintained over the air.
  15681. Thanks to Remote Management, sensors or switches IDs, for instance, can be stored or deleted from
  15682. already installed actuators or gateways which are hard to access. Remote Management also allows querying
  15683. debug information from the Remote Device and calling some manufacturer implemented functions.<br>
  15684. Remote Management is performed by the Remote Manager, operated by the actor, on the
  15685. managed Remote Device (Sensor, Gateway). The management is done through a series of
  15686. commands and responding answers. Actor sends the commands to the Remote Device. Remote
  15687. Device sends answers to the actor. The commands indicate the Remote Device what to do.
  15688. Remote Device answers if requested by the command. The commands belong to one of the
  15689. main use case categories, which are:
  15690. <ul>
  15691. <li>Security</li>
  15692. <li>Locate / indentify remote device</li>
  15693. <li>Get status</li>
  15694. <li>Extended function</li>
  15695. </ul>
  15696. The management is often done with a group of Remote Devices. Commands are sent as
  15697. addressed unicast telegrams, usually. In special cases broadcast transmission is also available.
  15698. To avoid telegram collisions the Remote Devices respond to broadcast commands with a
  15699. random delay.<br>
  15700. The Security, Locate, and Get Status options provide to the actor basic operability of Remote
  15701. management. Their purpose is to ensure the proper work of Remote Management when
  15702. operating with several Remote Devices. These functions behave in the same way on every
  15703. Remote Device. Every product that supports Remote Management provides these options.<br>
  15704. Extended functions provide the real benefit of Remote Management. They vary from Remote
  15705. Device to Remote Device. They depend on how and where the Remote Device is used.
  15706. Therefore, not every Remote Device provides every extended function. It depends on the
  15707. programmer / customer what extended functions he wants to add. There is a list of specified
  15708. commands, but the manufacturer can also add manufacturer specific extended functions. These
  15709. functions are identified by the manufacturer ID.<br>
  15710. More information can be found on the <a href="http://www.enocean.com">EnOcean websites</a>.<br><ber>
  15711. Fhem operates primarily as a remote manager. For tests but also a client device can be created.
  15712. <br><br>
  15713. The remote manager function must be activated for the desired device by
  15714. <ul><br>
  15715. <code>attr &lt;remote device name&gt; remote manager</code><br>
  15716. </ul>
  15717. <br><br>
  15718. The remote client device must be defined as follows<br>
  15719. <ul><br>
  15720. <code>define &lt;client name&gt; EnOcean C5-00-00</code><br>
  15721. </ul><br>
  15722. and has to by unlocked for t seconds
  15723. <ul><br>
  15724. <code>set &lt;client name&gt; unlock &lt;t/s&gt;</code><br>
  15725. </ul><br>
  15726. Only one remote management client device should be defined.<br><br>
  15727. For security reasons the remote management commands can only be accessed in the unlock
  15728. period. The period can be entered in two cases:
  15729. <ul>
  15730. <li>Within 30min after device power-up if no CODE is set</li>
  15731. <li>Within 30min after an unlock command with a correct 32bit security code is received</li>
  15732. </ul>
  15733. The unlock/lock period can be accessed only with the security code. The security code can be
  15734. set whenever the Remote Device accepts remote management commands.<br>
  15735. When the Remote Device is locked it does not respond to any command, but unlock and ping.
  15736. When a wrong security code is received the Remote Device does not process unlock commands
  15737. for a security period of 30 seconds.<br>
  15738. Security code=0x000000 is the default value and has to be interpreted as: no CODE has been
  15739. set. The actor can also set the security code to 0x000000 from a previously set value. If no
  15740. security code is set, unlock after the unlock period is not processed. Only ping will be
  15741. processed. Remote Management is not available until next power up. 0xFFFFFFFF is reserved
  15742. and can not be used as security code.<br><br>
  15743. To administrate a remote device whose Remote ID must be known. The Remote ID can be determined
  15744. as follows:
  15745. <ul><br>
  15746. <code>attr &lt;name&gt; remote manager</code><br>
  15747. power-up the remote device<br>
  15748. <code>get &lt;name&gt; remoteID</code><br><br>
  15749. </ul>
  15750. All commands are described in the remote management chapters of the <a href="#EnOcean_remoteSet">set</a>-
  15751. and <a href="#EnOcean_remoteGet">get</a>-commands.<br><br>
  15752. The Remote Management Function is configured using the following attributes:<br>
  15753. <ul>
  15754. <li><a href="#EnOcean_remoteCode">remoteCode</a></li>
  15755. <li><a href="#EnOcean_remoteEEP">remoteEEP</a></li>
  15756. <li><a href="#EnOcean_remoteID">remoteID</a></li>
  15757. <li><a href="#EnOcean_remoteManagement">remoteManagement</a></li>
  15758. <li><a href="#EnOcean_remoteManufID">remoteManufID</a></li>
  15759. </ul><br>
  15760. The content of events is described in the chapter <a href="#EnOcean_remoteEvents">Remote Management Events</a><br><br>.
  15761. The following extended functions are supported:
  15762. <ul>
  15763. <li>210:remoteLinkTableInfo</li>
  15764. <li>211:remoteLinkTable</li>
  15765. <li>212:remoteLinkTable</li>
  15766. <li>213:remoteLinkTableGP</li>
  15767. <li>214:remoteLinkTableGP</li>
  15768. <li>220:remoteLearnMode</li>
  15769. <li>221:remoteTeach</li>
  15770. <li>224:remoteReset</li>
  15771. <li>225:remoteRLT</li>
  15772. <li>226:remoteApplyChanges</li>
  15773. <li>227:remoteProductID</li>
  15774. <li>230:remoteDevCfg</li>
  15775. <li>231:remoteDevCfg</li>
  15776. <li>232:remoteLinkCfg</li>
  15777. <li>233:remoteLinkCfg</li>
  15778. <li>240:remoteAck</li>
  15779. <li>250:remoteRepeater</li>
  15780. <li>251:remoteRepeater</li>
  15781. <li>252:remoteRepeaterFilter</li>
  15782. </ul>
  15783. <br><br>
  15784. </ul>
  15785. <b>Signal Telegram</b><br>
  15786. <ul>
  15787. Signal Telegram as a feature is dedicated to signalize special events with optional data, trigger actions or
  15788. request responses. It extends the functionality of the device independently of used EEPs or other
  15789. communication profiles.<br>
  15790. Target key functional fields are:
  15791. <ul>
  15792. <li>Communication flow control</li>
  15793. <li>Energy harvesting and reporting</li>
  15794. <li>Failure & issues reporting</li>
  15795. <li>Radio link quality reporting</li>
  15796. </ul>
  15797. The Signal Telegram function commands are activated by the attribute <a href="#EnOcean_signal">signal</a>.
  15798. All commands are described in the signal telegram chapter of the <a href="#EnOcean_signalGet">get</a>-commands.
  15799. The content of events is described in the chapter <a href="#EnOcean_signalEvents">Signal Telegram Events</a>.
  15800. <br><br>
  15801. </ul>
  15802. <b>Radio Link Test</b><br>
  15803. <ul>
  15804. Units supporting the Radio Link Test (RLT) shall offer a functionality that allows for radio link testing between them
  15805. (Position A to Position B, point-to-point only). Fhem support at least 1BS and 4BS test messages. When two units
  15806. perform radio link testing one unit needs to act in a mode called RLT Master and the other unit needs to act in
  15807. a mode called RLT Slave. Fhem acts as RLT Master (subType radioLinkTest).<br>
  15808. The Radio Link Test device must be defined as follows<br>
  15809. <ul><br>
  15810. <code>define &lt;name&gt; EnOcean A5-3F-00</code><br>
  15811. </ul><br>
  15812. and has to by activated
  15813. <ul><br>
  15814. <code>set &lt;name&gt; standby</code><br>
  15815. </ul><br>
  15816. Alternatively, the device can also be created automatically by autocreate. Only one RLT device may be defined in FHEM.<br>
  15817. After activation the RLT Master listens for RLT Query messages. On reception of at least one RLT Query messsage the
  15818. RLT Master responds and starts transmission of RLT MasterTest messages. After that the RLT Master awaits the response
  15819. from the RLT Slave.<br>
  15820. A radio link test communication consits of a minimum of 16 and a maximum of 256 RLT MasterTest messages. When the
  15821. radio link test communication is completed the RLT Master gets deactivated automatically. The test results can be
  15822. found in the log file.
  15823. <br><br>
  15824. </ul>
  15825. <b>Security features</b><br>
  15826. <ul>
  15827. The receiving and sending of encrypted messages is supported. This module currently allows the secure operating mode of PTM 215
  15828. based switches.<br>
  15829. To receive secured telegrams, you first have to start the teach in mode via<br><br>
  15830. <code>set &lt;IODev&gt; teach &lt;t/s&gt;</code><br><br>
  15831. and then doing the following on the PTM 215 module:<br>
  15832. <ul>
  15833. <li>Remove the switch cover of the module</li>
  15834. <li>Press both buttons of one rocker side (A0 & A1 or B0 & B1)</li>
  15835. <li>While keeping the buttons pressed actuate the energy bow twice.</li><br>
  15836. </ul>
  15837. This generates two teach-in telegrams which create a Fhem device with the subType "switch.00" and synchronize the Fhem with
  15838. the PTM 215. Both the Fhem and the PTM 215 now maintain a counter which is used to generate a rolling code encryption scheme.
  15839. Also during teach-in, a private key is transmitted to the Fhem. The counter value is allowed to desynchronize for a maximum of
  15840. 128 counts, to allow compensating for missed telegrams, if this value is crossed you need to teach-in the PTM 215 again. Also
  15841. if your Fhem installation gets erased including the state information, you need to teach in the PTM 215 modules again (which
  15842. you would need to do anyway).<br><br>
  15843. To send secured telegrams, you first have send a secure teach-in to the remode device<br><br>
  15844. <ul>
  15845. <code>set &lt;name&gt; teachInSec</code><br><br>
  15846. </ul>
  15847. As for the security of this solution, if someone manages to capture the teach-in telegrams, he can extract the private key,
  15848. so the added security isn't perfect but relies on the fact, that none listens to you setting up your installation.
  15849. <br><br>
  15850. The cryptographic functions need the additional Perl modules Crypt/Rijndael and Crypt/Random. The module must be installed manually.
  15851. With the help of CPAN at the operating system level, for example,<br><br>
  15852. <ul>
  15853. <code>/usr/bin/perl -MCPAN -e 'install Crypt::Rijndael'</code><br>
  15854. <code>/usr/bin/perl -MCPAN -e 'install Crypt::Random'</code>
  15855. </ul>
  15856. <br><br>
  15857. </ul>
  15858. <a name="EnOceandefine"></a>
  15859. <b>Define</b>
  15860. <ul>
  15861. <code>define &lt;name&gt; EnOcean &lt;DEF&gt; [&lt;EEP&gt;]|getNextID|&lt;EEP&gt;</code>
  15862. <br><br>
  15863. Define an EnOcean device, connected via a <a href="#TCM">TCM</a> modul. The
  15864. &lt;DEF&gt; is the SenderID/DestinationID of the device (8 digit hex number), for example
  15865. <ul><br>
  15866. <code>define switch1 EnOcean FFC54500</code><br>
  15867. </ul><br>
  15868. In order to control devices, you cannot reuse the SenderIDs/
  15869. DestinationID of other devices (like remotes), instead you have to create
  15870. your own, which must be in the allowed SenderID range of the underlying Fhem
  15871. IO device, see <a href="#TCM">TCM</a> BaseID, LastID. For this first query the
  15872. <a href="#TCM">TCM</a> with the <code>get &lt;tcm&gt; baseID</code> command
  15873. for the BaseID. You can use up to 128 IDs starting with the BaseID shown there.
  15874. If you are using an Fhem SenderID outside of the allowed range, you will see an
  15875. ERR_ID_RANGE message in the Fhem log.<br>
  15876. FHEM can assign a free SenderID alternatively, for example
  15877. <ul><br>
  15878. <code>define switch1 EnOcean getNextID</code><br>
  15879. </ul><br>
  15880. If the EEP is known, the appropriate device can be created with the basic parameters, for example
  15881. <ul><br>
  15882. <code>define sensor1 EnOcean FFC54500 A5-02-05</code><br>
  15883. </ul><br>
  15884. or
  15885. <ul><br>
  15886. <code>define sensor1 EnOcean A5-02-05</code><br>
  15887. </ul><br>
  15888. Inofficial EEP for special devices
  15889. <ul>
  15890. <li>G5-07-01 PioTek-Tracker<br></li>
  15891. <li>G5-10-12 Room Sensor and Control Unit [Eltako FUTH65D]<br></li>
  15892. <li>G5-38-08 Gateway, Dimming [Eltako FSG, FUD]<br></li>
  15893. <li>H5-38-08 Gateway, Dimming [Eltako TF61D, TF100D]<br></li>
  15894. <li>M5-38-08 Gateway, Switching [Eltako FSR14]<br></li>
  15895. <li>N5-38-08 Gateway, Switching [Eltako TF61L, TF61R, TF100A, TF100L]<br></li>
  15896. <li>G5-3F-7F Shutter [Eltako FSB]<br></li>
  15897. <li>H5-3F-7F Shutter [Eltako TF61J]<br></li>
  15898. <li>L6-02-01 Smoke Detector [Eltako FRW]<br></li>
  15899. <li>G5-ZZ-ZZ Light and Presence Sensor [Omnio Ratio eagle-PM101]<br></li>
  15900. <li>ZZ-ZZ-ZZ EnOcean RAW profile<br></li>
  15901. <br><br>
  15902. </ul>
  15903. The <a href="#autocreate">autocreate</a> module may help you if the actor or sensor send
  15904. acknowledge messages or teach-in telegrams. In order to control this devices e. g. switches with
  15905. additional SenderIDs you can use the attributes <a href="#subDef">subDef</a>,
  15906. <a href="#subDef0">subDef0</a> and <a href="#subDefI">subDefI</a>.<br>
  15907. Fhem communicates unicast, if bidirectional 4BS or UTE teach-in is used, see
  15908. <a href="#EnOcean_teach-in"> Bidirectional Teach-In / Teach-Out</a>. In this case
  15909. Fhem send unicast telegrams with its SenderID and the DestinationID of the device.
  15910. <br><br>
  15911. </ul>
  15912. <a name="EnOceaninternals"></a>
  15913. <b>Internals</b>
  15914. <ul>
  15915. <li>DEF: 0000000 ... FFFFFFFF|&lt;EEP&gt;<br>
  15916. EnOcean DestinationID or SenderID<br>
  15917. If the attributes subDef* are set, this values are used as EnOcean SenderID.<br>
  15918. For an existing device, the device can be re-parameterized by entering the EEP.<br>
  15919. </li>
  15920. <li>Dev_EURID: 0000000 ... FFFFFFFF<br>
  15921. EnOcean ChipID of the device<br>
  15922. </li>
  15923. <li>Dev_RepeatingCounter: 0...2<br>
  15924. Number of forwardings by repeaters received by the device<br>
  15925. </li>
  15926. <li>Dev_RSSImax: LP/dBm<br>
  15927. Largest field strength received by the device<br>
  15928. </li>
  15929. <li>Dev_RSSImin: LP/dBm<br>
  15930. Smallest field strength received by the device<br>
  15931. </li>
  15932. <li>Dev_SubTelNum: 1...15<br>
  15933. Number of sub telegrams received by the device<br>
  15934. </li>
  15935. <li>&lt;IODev&gt;_DestinationID: 0000000 ... FFFFFFFF<br>
  15936. Received destination address, Broadcast radio: FFFFFFFF<br>
  15937. </li>
  15938. <li>&lt;IODev&gt;_PacketType: 1 ... 255<br>
  15939. Number of the packet type of last data telegram received<br>
  15940. </li>
  15941. <li>&lt;IODev&gt;_ReceivingQuality: excellent|good|bad<br>
  15942. excellent: RSSI >= -76 dBm (internal standard antenna sufficiently)<br>
  15943. good: RSSI < -76 dBm and RSSI >= -87 dBm (good antenna necessary)<br>
  15944. bad: RSSI < -87 dBm (repeater required)<br>
  15945. </li>
  15946. <li>&lt;IODev&gt;_RepeatingCounter: 0...2<br>
  15947. Number of forwardings by repeaters<br>
  15948. </li>
  15949. <li>&lt;IODev&gt;_RSSI: LP/dBm<br>
  15950. Received signal strength indication (best value of all received subtelegrams)<br>
  15951. </li>
  15952. <li>&lt;IODev&gt;_SubTelNum: 1...15<br>
  15953. Number of sub telegrams received<br>
  15954. </li>
  15955. <br><br>
  15956. </ul>
  15957. <a name="EnOceanset"></a>
  15958. <b>Set</b>
  15959. <ul>
  15960. <li><a name="EnOcean_teach-in">Teach-In / Teach-Out</a>
  15961. <ul>
  15962. <li>Teach-in remote devices</li>
  15963. <br>
  15964. <code>set &lt;IODev&gt; teach &lt;t/s&gt;</code>
  15965. <br><br>
  15966. Set Fhem in the learning mode.<br>
  15967. A device, which is then also put in this state is to paired with
  15968. Fhem. Bidirectional Teach-In / Teach-Out is used for some 4BS, VLD and MSC devices,
  15969. e. g. EEP 4BS, RORG A5-20-01 (Battery Powered Actuator).<br>
  15970. Bidirectional Teach-In for 4BS, UTE and Generic Profiles are supported.<br>
  15971. <code>IODev</code> is the name of the TCM Module.<br>
  15972. <code>t/s</code> is the time for the learning period.
  15973. <br><br>
  15974. Types of learning modes see <a href="#TCM_learningMode">learningMode</a>
  15975. <br><br>
  15976. Example:
  15977. <ul><code>set TCM_0 teach 600</code></ul>
  15978. <br>
  15979. <li>RPS profiles Teach-In (switches)</li>
  15980. <br>
  15981. <code>set &lt;name&gt; A0|AI|B0|BI|C0|CI|D0|DI</code>
  15982. <br><br>
  15983. Send teach-in telegram to remote device.
  15984. <br><br>
  15985. <li>1BS profiles Teach-In (contact)</li>
  15986. <br>
  15987. <code>set &lt;name&gt; teach</code>
  15988. <br><br>
  15989. Send teach-in telegram to remote device.
  15990. <br><br>
  15991. <li>4BS profiles Teach-In (sensors, dimmer, room controller etc.)</li>
  15992. <br>
  15993. <code>set &lt;name&gt; teach</code>
  15994. <br><br>
  15995. Send teach-in telegram to remote device.<br>
  15996. If no SenderID (attr subDef) was assigned before a learning telegram is sent for the first time, a free SenderID
  15997. is assigned automatically.
  15998. <br><br>
  15999. <li>UTE - Universal Uni- and Bidirectional Teach-In</li>
  16000. <br>
  16001. <code>set &lt;name&gt; teachIn|teachOut</code>
  16002. <br><br>
  16003. Send teach-in telegram to remote device.<br>
  16004. If no SenderID (attr subDef) was assigned before a learning telegram is sent for the first time, a free SenderID
  16005. is assigned automatically.
  16006. <br><br>
  16007. <li>Generic Profiles Teach-In</li>
  16008. <br>
  16009. <code>set &lt;name&gt; teachIn|teachOut</code>
  16010. <br><br>
  16011. Send teach-in telegram to remote device.<br>
  16012. If no SenderID (attr subDef) was assigned before a learning telegram is sent for the first time, a free SenderID
  16013. is assigned automatically.
  16014. <br><br>
  16015. <li>Secure Devices Teach-In</li>
  16016. <br>
  16017. <code>set &lt;name&gt; teachInSec</code>
  16018. <br><br>
  16019. Secure teach-in to the remode device.<br>
  16020. If no SenderID (attr subDef) was assigned before a learning telegram is sent for the first time, a free SenderID
  16021. is assigned automatically.
  16022. <br><br>
  16023. </ul>
  16024. </li>
  16025. <li><a name="EnOcean_smartAck">Smart Ack Learning</a>
  16026. <ul>
  16027. <li>Teach-in remote Smart Ack devices</li>
  16028. <br>
  16029. <code>set &lt;IODev&gt; smartAckLearn &lt;t/s&gt;</code>
  16030. <br><br>
  16031. Set Fhem in the Smart Ack learning mode.<br>
  16032. The post master fuctionality must be activated using the command <code>smartAckMailboxMax</code> in advance.<br>
  16033. The simple learnmode is supported, see <a href="#TCM_smartAckLearnMode">smartAckLearnMode</a><br>
  16034. A device, which is then also put in this state is to paired with
  16035. Fhem. Bidirectional learn in for 4BS, UTE and Generic Profiles are supported.<br>
  16036. <code>IODev</code> is the name of the TCM Module.<br>
  16037. <code>t/s</code> is the time for the learning period.
  16038. <br><br>
  16039. Example:
  16040. <ul><code>set TCM_0 smartAckLearn 600</code></ul>
  16041. <br>
  16042. </ul>
  16043. </li>
  16044. <li><a name="EnOcean_remoteSet">Remote Management</a>
  16045. <ul>
  16046. <code>set &lt;name&gt; &lt;value&gt;</code>
  16047. <br><br>
  16048. where <code>value</code> is
  16049. <li>remoteAction<br>
  16050. sent action command to perfoms an action, depending on the functionality of the device</li>
  16051. <li>remoteApplyChanges devCfg|linkTable|no_change<br>
  16052. apply changes</li>
  16053. <li>remoteDevCfg &lt;index&gt; &lt;value&gt;<br>
  16054. set configuration</li>
  16055. <li>remoteLinkTable in|out &lt;index&gt; &lt;ID&gt; &lt;EEP&gt; &lt;channel&gt;<br>
  16056. set link table content</li>
  16057. <li>remoteLinkCfg in|out &lt;index&gt; &lt;data index&gt; &lt;value&gt;<br>
  16058. set link based configuration</li>
  16059. <li>remoteLinkTableGP in|out &lt;index&gt; &lt;GP channel description&gt;<br>
  16060. set link table content</li>
  16061. <li>remoteLock<br>
  16062. locks the remote device or local client</li>
  16063. <li>remoteLearnMode in|out|off &lt;index&gt;<br>
  16064. initiate remote learn-in or learn-out of inbound index</li>
  16065. <li>remoteReset devCfg|linkTableIn|linkTableOut|no_change<br>
  16066. reset to defaults</li>
  16067. <li>remoteRLT on|off &lt;number of RLT slaves&gt;<br>
  16068. reset to defaults</li>
  16069. <li>remoteRepeater on|off|filter &lt;level&gt; &lt;filter structure&gt;<br>
  16070. set repeater functions</li>
  16071. <li>remoteRepeaterFilter apply|block|delete|deleteAll destinationID|sourceID|rorg|rssi &lt;filter value&gt;<br>
  16072. set repeater functions</li>
  16073. <li>remoteSetCode<br>
  16074. set the remote security code</li>
  16075. <li>remoteTeach &lt;channel&gt;<br>
  16076. request teach-in telegram from channel 00..FF</li>
  16077. <li>remoteUnlock [1...1800]<br>
  16078. unlocks the remote device or local client<br>
  16079. The unlock period can be set in the client mode between 1s and 1800 s.</li>
  16080. <br>
  16081. [&lt;channel&gt;] = 00...FF<br>
  16082. [&lt;EEP&gt;] = &lt;RORG&gt;-&lt;function&gt;-&lt;type&gt;<br>
  16083. [&lt;filter structure&gt;] = AND|OR<br>
  16084. [&lt;filter value&gt;] = &lt;destinationID&gt;|&lt;sourceID&gt;|&lt;RORG&gt;|&lt;LP/dBm&gt;<br>
  16085. [&lt;GP channel description&gt;] = &lt;name of channel 00&gt;:&lt;O|I&gt;:&lt;channel type&gt;:&lt;signal type&gt;:&lt;value type&gt;[:&lt;resolution&gt;[:&lt;engineering min&gt;:&lt;scaling min&gt;:&lt;engineering max&gt;:&lt;scaling max&gt;]]<br>
  16086. [&lt;ID&gt;] = 00000001...FFFFFFFE<br>
  16087. [&lt;index&gt;] = 00...FF<br>
  16088. [&lt;number of RLT slaves&gt;] = 01..7F<br>
  16089. [&lt;level&gt;] = 1|2<br>
  16090. [&lt;data index&gt;] = 0000...FFFF<br>
  16091. [&lt;value&gt;] = n x 00...FF<br>
  16092. </ul><br>
  16093. </li><br>
  16094. <li>Switch, Pushbutton Switch (EEP F6-02-01 ... F6-03-02)<br>
  16095. RORG RPS [default subType]
  16096. <ul>
  16097. <code>set &lt;name&gt; &lt;value&gt;</code>
  16098. <br><br>
  16099. where <code>value</code> is one of A0, AI, B0, BI, C0, CI, D0, DI,
  16100. combinations of these and released. First and second action can be sent
  16101. simultaneously. Separate first and second action with a comma.<br>
  16102. In fact we are trying to emulate a PT200 type remote.<br>
  16103. If you define an <a href="#eventMap">eventMap</a> attribute with on/off,
  16104. then you will be able to easily set the device from the <a
  16105. href="#FHEMWEB">WEB</a> frontend.<br>
  16106. <a href="#setExtensions">set extensions</a> are supported, if the corresponding
  16107. <a href="#eventMap">eventMap</a> specifies the <code>on</code> and <code>off</code>
  16108. mappings, for example <code>attr <name> eventMap on-till:on-till AI:on A0:off</code>.<br>
  16109. With the help of additional <a href="#EnOceanattr">attributes</a>, the
  16110. behavior of the devices can be adapt.<br>
  16111. The attr subType must be switch. This is done if the device was created by autocreate.
  16112. <br><br>
  16113. Example:
  16114. <ul><code>
  16115. set switch1 BI<br>
  16116. set switch1 B0,CI<br>
  16117. attr eventMap BI:on B0:off<br>
  16118. set switch1 on<br>
  16119. </code></ul><br>
  16120. </ul>
  16121. </li>
  16122. <li>Staircase off-delay timer (EEP F6-02-01 ... F6-02-02)<br>
  16123. RORG RPS [Eltako FTN14, tested with Eltako FTN14 only]<br>
  16124. <ul>
  16125. <code>set &lt;name&gt; &lt;value&gt;</code>
  16126. <br><br>
  16127. where <code>value</code> is
  16128. <li>on<br>
  16129. issue switch on command</li>
  16130. <li>released<br>
  16131. start timer</li>
  16132. </ul><br>
  16133. Set attr eventMap to B0:on BI:off, attr subType to switch, attr
  16134. webCmd to on:released and if needed attr switchMode to pushbutton manually.<br>
  16135. The attr subType must be switch. This is done if the device was created by autocreate.<br>
  16136. Use the sensor type "Schalter" for Eltako devices. The Staircase
  16137. off-delay timer is switched on when pressing "on" and the time will be started
  16138. when pressing "released". "released" immediately after "on" is sent if
  16139. the attr switchMode is set to "pushbutton".
  16140. </li>
  16141. <br><br>
  16142. <li>Pushbutton Switch (EEP D2-03-00)<br>
  16143. RORG VLD [EnOcean PTM 215 Modul]
  16144. <ul>
  16145. <code>set &lt;name&gt; &lt;value&gt;</code>
  16146. <br><br>
  16147. where <code>value</code> is
  16148. <li>teachIn<br>
  16149. initiate UTE teach-in</li>
  16150. <li>teachInSec<br>
  16151. initiate secure teach-in</li>
  16152. <li>teachOut<br>
  16153. initiate UTE teach-out</li>
  16154. <li>A0|AI|B0|BI<br>
  16155. issue switch command</li>
  16156. <li>A0,B0|A0,AI|AI,B0|AI,BI<br>
  16157. issue switch command</li>
  16158. <li>pressed<br>
  16159. energy bow pressed</li>
  16160. <li>pressed34<br>
  16161. 3 or 4 buttons and energy bow pressed</li>
  16162. <li>released<br>
  16163. energy bow released</li><br>
  16164. </ul>
  16165. First and second action can be sent simultaneously. Separate first and second action with a comma.<br>
  16166. If you define an <a href="#eventMap">eventMap</a> attribute with on/off,
  16167. then you will be able to easily set the device from the <a href="#FHEMWEB">WEB</a> frontend.<br>
  16168. <a href="#setExtensions">set extensions</a> are supported, if the corresponding
  16169. <a href="#eventMap">eventMap</a> specifies the <code>on</code> and <code>off</code>
  16170. mappings, for example <code>attr <name> eventMap on-till:on-till AI:on A0:off</code>.<br>
  16171. If <a href="#EnOcean_comMode">comMode</a> is set to biDir the device can be controlled bidirectionally.<br>
  16172. With the help of additional <a href="#EnOceanattr">attributes</a>, the behavior of the devices can be adapt.<br>
  16173. The attr subType must be switch.00. This is done if the device was created by autocreate.
  16174. <br><br>
  16175. <ul>
  16176. Example:
  16177. <ul><code>
  16178. set switch1 BI<br>
  16179. set switch1 B0,CI<br>
  16180. attr eventMap BI:on B0:off<br>
  16181. set switch1 on<br>
  16182. </code></ul><br>
  16183. </ul>
  16184. </li>
  16185. <li>Single Input Contact, Door/Window Contact (EEP D5-00-01)<br>
  16186. RORG 1BS [tested with Eltako FSR14]
  16187. <ul>
  16188. <code>set &lt;name&gt; &lt;value&gt;</code>
  16189. <br><br>
  16190. where <code>value</code> is
  16191. <li>closed<br>
  16192. issue closed command</li>
  16193. <li>open<br>
  16194. issue open command</li>
  16195. <li>teach<br>
  16196. initiate teach-in</li>
  16197. </ul><br>
  16198. The attr subType must be contact. The attribute must be set manually.
  16199. A monitoring period can be set for signOfLife telegrams of the sensor, see
  16200. <a href="#EnOcean_signOfLife">signOfLife</a> and <a href="#EnOcean_signOfLifeInterval">signOfLifeInterval</a>.
  16201. Default is "off" and an interval of 1980 sec.
  16202. </li><br><br>
  16203. <li>Room Sensor and Control Unit (EEP A5-10-02)<br>
  16204. [Thermokon SR04 PTS]<br>
  16205. <ul>
  16206. <code>set &lt;name&gt; &lt;value&gt;</code>
  16207. <br><br>
  16208. where <code>value</code> is
  16209. <li>teach<br>
  16210. initiate teach-in</li>
  16211. <li>setpoint [0 ... 255]<br>
  16212. Set the actuator to the specifed setpoint.</li>
  16213. <li>setpointScaled [&lt;floating-point number&gt;]<br>
  16214. Set the actuator to the scaled setpoint.</li>
  16215. <li>fanStage [auto|0|1|2|3]<br>
  16216. Set fan stage</li>
  16217. <li>switch [on|off]<br>
  16218. Set switch</li>
  16219. </ul><br>
  16220. The actual temperature will be taken from the temperature reported by
  16221. a temperature reference device <a href="#temperatureRefDev">temperatureRefDev</a>
  16222. primarily or from the attribute <a href="#actualTemp">actualTemp</a> if it is set.<br>
  16223. If the attribute <a href="#EnOcean_setCmdTrigger">setCmdTrigger</a> is set to "refDev", a setpoint
  16224. command is sent when the reference device is updated.<br>
  16225. The scaling of the setpoint adjustment is device- and vendor-specific. Set the
  16226. attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
  16227. <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled setting
  16228. setpointScaled.<br>
  16229. The attr subType must be roomSensorControl.05. The attribute must be set manually.
  16230. </li>
  16231. <br><br>
  16232. <li>Room Sensor and Control Unit (EEP A5-10-03)<br>
  16233. [used for IntesisBox PA-AC-ENO-1i]<br>
  16234. <ul>
  16235. <code>set &lt;name&gt; &lt;value&gt;</code>
  16236. <br><br>
  16237. where <code>value</code> is
  16238. <li>teach<br>
  16239. initiate teach-in</li>
  16240. <li>setpoint [0 ... 255]<br>
  16241. Set the actuator to the specifed setpoint.</li>
  16242. <li>setpointScaled [&lt;floating-point number&gt;]<br>
  16243. Set the actuator to the scaled setpoint.</li>
  16244. <li>fanStage [auto|0|1|2|3]<br>
  16245. Set fan stage</li>
  16246. <li>switch [on|off]<br>
  16247. Set switch</li>
  16248. </ul><br>
  16249. The actual temperature will be taken from the temperature reported by
  16250. a temperature reference device <a href="#temperatureRefDev">temperatureRefDev</a>
  16251. primarily or from the attribute <a href="#actualTemp">actualTemp</a> if it is set.<br>
  16252. If the attribute <a href="#EnOcean_setCmdTrigger">setCmdTrigger</a> is set to "refDev", a setpoint
  16253. command is sent when the reference device is updated.<br>
  16254. The scaling of the setpoint adjustment is device- and vendor-specific. Set the
  16255. attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
  16256. <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled setting
  16257. setpointScaled.<br>
  16258. The attr subType must be roomSensorControl.05 and attr manufID must be 019. The attribute must be set manually.
  16259. </li>
  16260. <br><br>
  16261. <li>Room Sensor and Control Unit (A5-10-06 plus night reduction)<br>
  16262. [Eltako FTR65DS, FTR65HS]<br>
  16263. <ul>
  16264. <code>set &lt;name&gt; &lt;value&gt;</code>
  16265. <br><br>
  16266. where <code>value</code> is
  16267. <li>teach<br>
  16268. initiate teach-in</li>
  16269. <li>desired-temp [t/&#176C [lock|unlock]]<br>
  16270. Set the desired temperature.</li>
  16271. <li>nightReduction [t/K [lock|unlock]]<br>
  16272. Set night reduction</li>
  16273. <li>setpointTemp [t/&#176C [lock|unlock]]<br>
  16274. Set the desired temperature</li>
  16275. </ul><br>
  16276. The actual temperature will be taken from the temperature reported by
  16277. a temperature reference device <a href="#temperatureRefDev">temperatureRefDev</a>
  16278. primarily or from the attribute <a href="#actualTemp">actualTemp</a> if it is set.<br>
  16279. If the attribute <a href="#EnOcean_setCmdTrigger">setCmdTrigger</a> is set to "refDev", a setpointTemp
  16280. command is sent when the reference device is updated.<br>
  16281. This profil can be used with a further Room Sensor and Control Unit Eltako FTR55*
  16282. to control a heating/cooling relay FHK12, FHK14 or FHK61. If Fhem and FTR55*
  16283. is teached in, the temperature control of the FTR55* can be either blocked
  16284. or to a setpoint deviation of +/- 3 K be limited. For this use the optional parameter
  16285. [block] = lock|unlock, unlock is default.<br>
  16286. The attr subType must be roomSensorControl.05 and attr manufID must be 00D.
  16287. The attributes must be set manually.
  16288. </li>
  16289. <br><br>
  16290. <li>Room Sensor and Control Unit (EEP A5-10-10)<br>
  16291. [Thermokon SR04 * rH, Thanos SR *]<br>
  16292. <ul>
  16293. <code>set &lt;name&gt; &lt;value&gt;</code>
  16294. <br><br>
  16295. where <code>value</code> is
  16296. <li>teach<br>
  16297. initiate teach-in</li>
  16298. <li>setpoint [0 ... 255]<br>
  16299. Set the actuator to the specifed setpoint.</li>
  16300. <li>setpointScaled [&lt;floating-point number&gt;]<br>
  16301. Set the actuator to the scaled setpoint.</li>
  16302. <li>switch [on|off]<br>
  16303. Set switch</li>
  16304. </ul><br>
  16305. The actual temperature will be taken from the temperature reported by
  16306. a temperature reference device <a href="#temperatureRefDev">temperatureRefDev</a>
  16307. primarily or from the attribute <a href="#actualTemp">actualTemp</a> if it is set.<br>
  16308. The actual humidity will be taken from the humidity reported by
  16309. a humidity reference device <a href="#EnOcean_humidityRefDev">humidityRefDev</a>
  16310. primarily or from the attribute <a href="#EnOcean_humidity">humidity</a> if it is set.<br>
  16311. If the attribute <a href="#EnOcean_setCmdTrigger">setCmdTrigger</a> is set to "refDev", a setpoint
  16312. command is sent when the reference device is updated.<br>
  16313. The scaling of the setpoint adjustment is device- and vendor-specific. Set the
  16314. attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
  16315. <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled setting
  16316. setpointScaled.<br>
  16317. The attr subType must be roomSensorControl.01. The attribute must be set manually.
  16318. </li>
  16319. <br><br>
  16320. <li>Room Sensor and Control Unit (EEP A5-10-12)<br>
  16321. [Eltako FUTH65D]<br>
  16322. <ul>
  16323. <code>set &lt;name&gt; &lt;value&gt;</code>
  16324. <br><br>
  16325. where <code>value</code> is
  16326. <li>teach<br>
  16327. initiate teach-in</li>
  16328. <li>setpoint [0 ... 255]<br>
  16329. Set the actuator to the specifed setpoint.</li>
  16330. <li>setpointScaled [&lt;floating-point number&gt;]<br>
  16331. Set the actuator to the scaled setpoint.</li>
  16332. <li>switch [on|off]<br>
  16333. Set switch</li>
  16334. </ul><br>
  16335. The actual temperature will be taken from the temperature reported by
  16336. a temperature reference device <a href="#temperatureRefDev">temperatureRefDev</a>
  16337. primarily or from the attribute <a href="#actualTemp">actualTemp</a> if it is set.<br>
  16338. If the attribute <a href="#EnOcean_setCmdTrigger">setCmdTrigger</a> is set to "refDev", a setpoint
  16339. command is sent when the reference device is updated.<br>
  16340. The attr subType must be roomSensorControl.01 and attr manufID must be 00D. The attribute must be set manually.
  16341. </li>
  16342. <br><br>
  16343. <li>Battery Powered Actuator (EEP A5-20-01)<br>
  16344. [Kieback&Peter MD15-FTL-xx]<br>
  16345. <ul>
  16346. <code>set &lt;name&gt; &lt;value&gt;</code>
  16347. <br><br>
  16348. where <code>value</code> is
  16349. <li>setpoint setpoint/%<br>
  16350. Set the actuator to the specifed setpoint (0...100). The setpoint can also be set by the
  16351. <a href="#EnOcean_setpointRefDev">setpointRefDev</a> device if it is set.</li>
  16352. <li>setpointTemp t/&#176C<br>
  16353. Set the actuator to the specifed temperature setpoint. The temperature setpoint can also be set by the
  16354. <a href="#EnOcean_setpointTempRefDev">setpointTempRefDev</a> device if it is set.<br>
  16355. The FHEM PID controller calculates the actuator setpoint based on the temperature setpoint. The controller's
  16356. operation can be set via the PID parameters <a href="#EnOcean_pidFactor_P">pidFactor_P</a>,
  16357. <a href="#EnOcean_pidFactor_I">pidFactor_I</a> and <a href="#EnOcean_pidFactor_D">pidFactor_D</a>.<br>
  16358. If the attribute pidCtrl is set to off, the PI controller of the actuator is used (selfCtrl mode). Please
  16359. read the instruction manual of the device, whether the device has an internal PI controller.<br></li>
  16360. <li>runInit<br>
  16361. Maintenance Mode: Run init sequence</li>
  16362. <li>valveOpens<br>
  16363. Maintenance Mode: Valve opens<br>
  16364. After the valveOpens command, the valve remains open permanently and can no longer be controlled by Fhem.
  16365. By pressing the button on the device itself, the actuator is returned to its normal operating state.</li>
  16366. <li>valveCloses<br>
  16367. Maintenance Mode: Valve closes</li>
  16368. </ul><br>
  16369. The Heating Radiator Actuating Drive is configured using the following attributes:<br>
  16370. <ul>
  16371. <li><a href="#EnOcean_pidActorCallBeforeSetting">pidActorCallBeforeSetting</a></li>
  16372. <li><a href="#EnOcean_pidActorErrorAction">pidActorErrorAction</a></li>
  16373. <li><a href="#EnOcean_pidActorErrorPos">pidActorErrorPos</a></li>
  16374. <li><a href="#EnOcean_pidActorLimitLower">pidActorLimitLower</a></li>
  16375. <li><a href="#EnOcean_pidActorLimitUpper">pidActorLimitUpper</a></li>
  16376. <li><a href="#EnOcean_pidCtrl">pidCtrl</a></li>
  16377. <li><a href="#EnOcean_pidDeltaTreshold">pidDeltaTreshold</a></li>
  16378. <li><a href="#EnOcean_pidFactor_P">pidFactor_P</a></li>
  16379. <li><a href="#EnOcean_pidFactor_I">pidFactor_I</a></li>
  16380. <li><a href="#EnOcean_pidFactor_D">pidFactor_D</a></li>
  16381. <li><a href="#EnOcean_pidIPortionCallBeforeSetting">pidIPortionCallBeforeSetting</a></li>
  16382. <li><a href="#EnOcean_pidSensorTimeout">pidSensorTimeout</a></li>
  16383. <li><a href="#EnOcean_rcvRespAction">rcvRespAction</a></li>
  16384. <li><a href="#EnOcean_setpointRefDev">setpointRefDev</a></li>
  16385. <li><a href="#EnOcean_setpointSummerMode">setpointSummerMode</a></li>
  16386. <li><a href="#EnOcean_setpointTempRefDev">setpointTempRefDev</a></li>
  16387. <li><a href="#temperatureRefDev">temperatureRefDev</a></li>
  16388. <li><a href="#EnOcean_summerMode">summerMode</a></li>
  16389. </ul>
  16390. The actual temperature will be reported by the Heating Radiator Actuating Drive or by the
  16391. <a href="#temperatureRefDev">temperatureRefDev</a> if it is set. The internal temperature sensor
  16392. of the Micropelt iTRV is not suitable as an actual temperature value for the PID controller.
  16393. An external room thermostat is required.<br>
  16394. The attr event-on-change-reading .* shut not by set. The PID controller expects periodic events.
  16395. If these are missing, a communication alarm is signaled.<br>
  16396. The attr subType must be hvac.01. This is done if the device was
  16397. created by autocreate. To control the device, it must be bidirectional paired,
  16398. see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.<br>
  16399. The command is not sent until the device wakes up and sends a message, usually
  16400. every 10 minutes.
  16401. </li>
  16402. <br><br>
  16403. <li>Heating Radiator Actuating Drive (EEP A5-20-04)<br>
  16404. [Holter SmartDrive MX]<br>
  16405. <ul>
  16406. <code>set &lt;name&gt; &lt;value&gt;</code>
  16407. <br><br>
  16408. where <code>value</code> is
  16409. <li>setpoint setpoint/%<br>
  16410. Set the actuator to the specifed setpoint (0...100). The setpoint can also be set by the
  16411. <a href="#EnOcean_setpointRefDev">setpointRefDev</a> device if it is set.</li>
  16412. <li>setpointTemp t/&#176C<br>
  16413. Set the actuator to the specifed temperature setpoint. The temperature setpoint can also be set by the
  16414. <a href="#EnOcean_setpointTempRefDev">setpointTempRefDev</a> device if it is set.<br>
  16415. The FHEM PID controller calculates the actuator setpoint based on the temperature setpoint. The controller's
  16416. operation can be set via the PID parameters <a href="#EnOcean_pidFactor_P">pidFactor_P</a>,
  16417. <a href="#EnOcean_pidFactor_I">pidFactor_I</a> and <a href="#EnOcean_pidFactor_D">pidFactor_D</a>.</li>
  16418. <li>runInit<br>
  16419. Maintenance Mode: Run init sequence</li>
  16420. <li>valveOpens<br>
  16421. Maintenance Mode: Valve opens<br>
  16422. After the valveOpens command, the valve remains open permanently and can no longer be controlled by Fhem.
  16423. By pressing the button on the device itself, the actuator is returned to its normal operating state.</li>
  16424. <li>valveCloses<br>
  16425. Maintenance Mode: Valve closes</li>
  16426. </ul><br>
  16427. The Heating Radiator Actuating Drive is configured using the following attributes:<br>
  16428. <ul>
  16429. <li><a href="#EnOcean_blockKey">blockKey</a></li>
  16430. <li><a href="#EnOcean_displayOrientation">displayOrientation</a></li>
  16431. <li><a href="#EnOcean_measurementCtrl">measurementCtrl</a></li>
  16432. <li><a href="#model">model</a></li>
  16433. <li><a href="#EnOcean_pidActorCallBeforeSetting">pidActorCallBeforeSetting</a></li>
  16434. <li><a href="#EnOcean_pidActorErrorAction">pidActorErrorAction</a></li>
  16435. <li><a href="#EnOcean_pidActorErrorPos">pidActorErrorPos</a></li>
  16436. <li><a href="#EnOcean_pidActorLimitLower">pidActorLimitLower</a></li>
  16437. <li><a href="#EnOcean_pidActorLimitUpper">pidActorLimitUpper</a></li>
  16438. <li><a href="#EnOcean_pidCtrl">pidCtrl</a></li>
  16439. <li><a href="#EnOcean_pidDeltaTreshold">pidDeltaTreshold</a></li>
  16440. <li><a href="#EnOcean_pidFactor_P">pidFactor_P</a></li>
  16441. <li><a href="#EnOcean_pidFactor_I">pidFactor_I</a></li>
  16442. <li><a href="#EnOcean_pidFactor_D">pidFactor_D</a></li>
  16443. <li><a href="#EnOcean_pidIPortionCallBeforeSetting">pidIPortionCallBeforeSetting</a></li>
  16444. <li><a href="#EnOcean_pidSensorTimeout">pidSensorTimeout</a></li>
  16445. <li><a href="#EnOcean_setpointRefDev">setpointRefDev</a></li>
  16446. <li><a href="#EnOcean_setpointTempRefDev">setpointTempRefDev</a></li>
  16447. <li><a href="#temperatureRefDev">temperatureRefDev</a></li>
  16448. <li><a href="#EnOcean_summerMode">summerMode</a></li>
  16449. <li><a href="#EnOcean_wakeUpCycle">wakeUpCycle</a></li>
  16450. </ul>
  16451. The actual temperature will be reported by the Heating Radiator Actuating Drive or by the
  16452. <a href="#temperatureRefDev">temperatureRefDev</a> if it is set.<br>
  16453. The attr event-on-change-reading .* shut not by set. The PID controller expects periodic events.
  16454. If these are missing, a communication alarm is signaled.<br>
  16455. The attr subType must be hvac.04. This is done if the device was
  16456. created by autocreate. To control the device, it must be bidirectional paired,
  16457. see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.<br>
  16458. The OEM version of the Holter SmartDrive MX has an internal PID controller. This function is activated by
  16459. attr <device> model Holter_OEM and attr <device> pidCtrl off.<br>
  16460. The command is not sent until the device wakes up and sends a message, usually
  16461. every 5 minutes.
  16462. </li>
  16463. <br><br>
  16464. <li>Generic HVAC Interface (EEP A5-20-10)<br>
  16465. [IntesisBox PA-AC-ENO-1i]<br>
  16466. <ul>
  16467. <code>set &lt;name&gt; &lt;value&gt;</code>
  16468. <br><br>
  16469. where <code>value</code> is
  16470. <li>ctrl auto|0...100<br>
  16471. Set control variable</li>
  16472. <li>fanSpeed auto|1...14<br>
  16473. Set fan speed</li>
  16474. <li>occupancy occupied|off|standby|unoccupied<br>
  16475. Set room occupancy</li>
  16476. <li>on<br>
  16477. Set on</li>
  16478. <li>off<br>
  16479. Set off</li>
  16480. <li>mode auto|heat|morning_warmup|cool|night_purge|precool|off|test|emergency_heat|fan_only|free_cool|ice|max_heat|eco|dehumidification|calibration|emergency_cool|emergency_stream|max_cool|hvc_load|no_load|auto_heat|auto_cool<br>
  16481. Set mode</li>
  16482. <li>teach<br>
  16483. Teach-in</li>
  16484. <li>vanePosition auto|horizontal|position_2|position_3|position_4|vertical|swing|vertical_swing|horizontal_swing|hor_vert_swing|stop_swing<br>
  16485. Set vane position</li>
  16486. </ul><br>
  16487. The attr subType must be hvac.10. This is done if the device was
  16488. created by autocreate. To control the device, it must be bidirectional paired,
  16489. see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.
  16490. </li>
  16491. <br><br>
  16492. <li>Generic HVAC Interface - Error Control (EEP A5-20-11)<br>
  16493. [IntesisBox PA-AC-ENO-1i]<br>
  16494. <ul>
  16495. <code>set &lt;name&gt; &lt;value&gt;</code>
  16496. <br><br>
  16497. where <code>value</code> is
  16498. <li>externalDisable disable|enable<br>
  16499. Set external disablement</li>
  16500. <li>remoteCtrl disable|enable<br>
  16501. Dieable/enable remote controller</li>
  16502. <li>teach<br>
  16503. Teach-in</li>
  16504. <li>window closed|opened<br>
  16505. Set window state</li>
  16506. </ul><br>
  16507. The attr subType must be hvac.11. This is done if the device was
  16508. created by autocreate. To control the device, it must be bidirectional paired,
  16509. see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.
  16510. </li>
  16511. <br><br>
  16512. <li>Energy management, <a name="demand_response">demand response</a> (EEP A5-37-01)<br>
  16513. demand response master commands<br>
  16514. <ul>
  16515. <code>set &lt;name&gt; &lt;value&gt;</code>
  16516. <br><br>
  16517. where <code>value</code> is
  16518. <li>level 0...15 [&lt;powerUsageScale&gt; [&lt;randomStart&gt; [&lt;randomEnd&gt; [timeout]]]]<br>
  16519. set demand response level</li>
  16520. <li>max [&lt;powerUsageScale&gt; [&lt;randomStart&gt; [&lt;randomEnd&gt; [timeout]]]]<br>
  16521. set power usage level to max</li>
  16522. <li>min [&lt;powerUsageScale&gt; [&lt;randomStart&gt; [&lt;randomEnd&gt; [timeout]]]]<br>
  16523. set power usage level to min</li>
  16524. <li>power power/% [&lt;powerUsageScale&gt; [&lt;randomStart&gt; [&lt;randomEnd&gt; [timeout]]]]<br>
  16525. set power</li>
  16526. <li>setpoint 0...255 [&lt;powerUsageScale&gt; [&lt;randomStart&gt; [&lt;randomEnd&gt; [timeout]]]]<br>
  16527. set setpoint</li>
  16528. <li>teach<br>
  16529. initiate teach-in</li>
  16530. </ul><br>
  16531. [&lt;powerUsageScale&gt;] = max|rel, [&lt;powerUsageScale&gt;] = max is default<br>
  16532. [&lt;randomStart&gt;] = yes|no, [&lt;randomStart&gt;] = no is default<br>
  16533. [&lt;randomEnd&gt;] = yes|no, [&lt;randomEnd&gt;] = no is default<br>
  16534. [timeout] = 0/min | 15/min ... 3825/min, [timeout] = 0 is default<br>
  16535. The attr subType must be energyManagement.01.<br>
  16536. This is done if the device was created by autocreate.<br>
  16537. </li>
  16538. <br><br>
  16539. <li><a name="Gateway">Gateway</a> (EEP A5-38-08)<br>
  16540. The Gateway profile include 7 different commands (Switching, Dimming,
  16541. Setpoint Shift, Basic Setpoint, Control variable, Fan stage, Blind Central Command).
  16542. The commands can be selected by the attribute gwCmd or command line. The attribute
  16543. entry has priority.<br>
  16544. <ul>
  16545. <code>set &lt;name&gt; &lt;value&gt;</code>
  16546. <br><br>
  16547. where <code>value</code> is
  16548. <li>&lt;gwCmd&gt; &lt;cmd&gt; [subCmd]<br>
  16549. initiate Gateway commands by command line</li>
  16550. <li>&lt;cmd&gt; [subCmd]<br>
  16551. initiate Gateway commands if attribute gwCmd is set.</li>
  16552. </ul><br>
  16553. The attr subType must be gateway. Attribute gwCmd can also be set to
  16554. switching|dimming|setpointShift|setpointBasic|controlVar|fanStage|blindCmd.<br>
  16555. This is done if the device was created by autocreate.<br>
  16556. For Eltako devices attributes must be set manually.
  16557. </li>
  16558. <br><br>
  16559. <li>Gateway (EEP A5-38-08)<br>
  16560. Switching<br>
  16561. [Eltako FLC61, FSA12, FSR14]<br>
  16562. <ul>
  16563. <code>set &lt;name&gt; &lt;value&gt;</code>
  16564. <br><br>
  16565. where <code>value</code> is
  16566. <li>teach<br>
  16567. initiate teach-in mode</li>
  16568. <li>on [lock|unlock]<br>
  16569. issue switch on command</li>
  16570. <li>off [lock|unlock]<br>
  16571. issue switch off command</li>
  16572. <li><a href="#setExtensions">set extensions</a> are supported.</li>
  16573. </ul><br>
  16574. The attr subType must be gateway and gwCmd must be switching. This is done if the device was
  16575. created by autocreate.<br>
  16576. For Eltako devices attributes must be set manually. For Eltako FSA12 attribute model must be set
  16577. to Eltako_FSA12.
  16578. </li>
  16579. <br><br>
  16580. <li>Gateway (EEP A5-38-08)<br>
  16581. Dimming<br>
  16582. [Eltako FUD12, FUD14, FUD61, FUD70, FSG14, ...]<br>
  16583. <ul>
  16584. <code>set &lt;name&gt; &lt;value&gt;</code>
  16585. <br><br>
  16586. where <code>value</code> is
  16587. <li>dim/% [rampTime/s [lock|unlock]]<br>
  16588. issue dim command</li>
  16589. <li>teach<br>
  16590. initiate teach-in mode</li>
  16591. <li>on [lock|unlock]<br>
  16592. issue switch on command</li>
  16593. <li>off [lock|unlock]<br>
  16594. issue switch off command</li>
  16595. <li>dim dim/% [rampTime/s [lock|unlock]]<br>
  16596. issue dim command</li>
  16597. <li>dimup dim/% [rampTime/s [lock|unlock]]<br>
  16598. issue dim command</li>
  16599. <li>dimdown dim/% [rampTime/s [lock|unlock]]<br>
  16600. issue dim command</li>
  16601. <li><a href="#setExtensions">set extensions</a> are supported.</li>
  16602. </ul><br>
  16603. rampTime Range: t = 1 s ... 255 s or 0 if no time specified,
  16604. for Eltako: t = 1 = fast dimming ... 255 = slow dimming or 0 = dimming speed on the dimmer used<br>
  16605. The attr subType must be gateway and gwCmd must be dimming. This is done if the device was
  16606. created by autocreate.<br>
  16607. For Eltako devices attributes must be set manually. Use the sensor type "PC/FVS" for Eltako devices.
  16608. </li>
  16609. <br><br>
  16610. <li>Gateway (EEP A5-38-08)<br>
  16611. Dimming of fluorescent lamps<br>
  16612. [Eltako FSG70, tested with Eltako FSG70 only]<br>
  16613. <ul>
  16614. <code>set &lt;name&gt; &lt;value&gt;</code>
  16615. <br><br>
  16616. where <code>value</code> is
  16617. <li>on<br>
  16618. issue switch on command</li>
  16619. <li>off<br>
  16620. issue switch off command</li>
  16621. <li><a href="#setExtensions">set extensions</a> are supported.</li>
  16622. </ul><br>
  16623. The attr subType must be gateway and gwCmd must be dimming. Set attr eventMap to B0:on BI:off,
  16624. attr subTypeSet to switch and attr switchMode to pushbutton manually.<br>
  16625. Use the sensor type "Richtungstaster" for Eltako devices.
  16626. </li>
  16627. <br><br>
  16628. <li>Gateway (EEP A5-38-08)<br>
  16629. Setpoint shift<br>
  16630. [untested]<br>
  16631. <ul>
  16632. <code>set &lt;name&gt; &lt;value&gt;</code>
  16633. <br><br>
  16634. where <code>value</code> is
  16635. <li>teach<br>
  16636. initiate teach-in mode</li>
  16637. <li>shift 1/K <br>
  16638. issue Setpoint shift</li>
  16639. </ul><br>
  16640. Shift Range: T = -12.7 K ... 12.8 K<br>
  16641. The attr subType must be gateway and gwCmd must be setpointShift.
  16642. This is done if the device was created by autocreate.<br>
  16643. </li>
  16644. <br><br>
  16645. <li>Gateway (EEP A5-38-08)<br>
  16646. Basic Setpoint<br>
  16647. [untested]<br>
  16648. <ul>
  16649. <code>set &lt;name&gt; &lt;value&gt;</code>
  16650. <br><br>
  16651. where <code>value</code> is
  16652. <li>teach<br>
  16653. initiate teach-in mode</li>
  16654. <li>basic t/&#176C<br>
  16655. issue Basic Setpoint</li>
  16656. </ul><br>
  16657. Setpoint Range: t = 0 &#176C ... 51.2 &#176C<br>
  16658. The attr subType must be gateway and gwCmd must be setpointBasic.
  16659. This is done if the device was created by autocreate.<br>
  16660. </li>
  16661. <br><br>
  16662. <li>Gateway (EEP A5-38-08)<br>
  16663. Control variable<br>
  16664. [untested]<br>
  16665. <ul>
  16666. <code>set &lt;name&gt; &lt;value&gt;</code>
  16667. <br><br>
  16668. where <code>value</code> is
  16669. <li>teach<br>
  16670. initiate teach-in mode</li>
  16671. <li>presence present|absent|standby<br>
  16672. issue Room occupancy</li>
  16673. <li>energyHoldOff normal|holdoff<br>
  16674. issue Energy hold off</li>
  16675. <li>controllerMode auto|heating|cooling|off<br>
  16676. issue Controller mode</li>
  16677. <li>controllerState auto|override <0 ... 100> <br>
  16678. issue Control variable override</li>
  16679. </ul><br>
  16680. Override Range: cvov = 0 % ... 100 %<br>
  16681. The attr subType must be gateway and gwCmd must be controlVar.
  16682. This is done if the device was created by autocreate.<br>
  16683. </li>
  16684. <br><br>
  16685. <li>Gateway (EEP A5-38-08)<br>
  16686. Fan stage<br>
  16687. [untested]<br>
  16688. <ul>
  16689. <code>set &lt;name&gt; &lt;value&gt;</code>
  16690. <br><br>
  16691. where <code>value</code> is
  16692. <li>teach<br>
  16693. initiate teach-in mode</li>
  16694. <li>stage 0 ... 3|auto<br>
  16695. issue Fan Stage override</li>
  16696. </ul><br>
  16697. The attr subType must be gateway and gwCmd must be fanStage.
  16698. This is done if the device was created by autocreate.<br>
  16699. </li>
  16700. <br><br>
  16701. <li>Gateway (EEP A5-38-08)<br>
  16702. <a name="Blind Command Central">Blind Command Central</a><br>
  16703. [not fully tested]<br>
  16704. <ul>
  16705. <code>set &lt;name&gt; &lt;value&gt;</code>
  16706. <br><br>
  16707. where <code>value</code> is
  16708. <li>position/% [&alpha;/&#176]<br>
  16709. drive blinds to position with angle value</li>
  16710. <li>teach<br>
  16711. initiate teach-in mode</li>
  16712. <li>status<br>
  16713. Status request</li>
  16714. <li>opens<br>
  16715. issue blinds opens command</li>
  16716. <li>up tu/s ta/s<br>
  16717. issue roll up command</li>
  16718. <li>closes<br>
  16719. issue blinds closes command</li>
  16720. <li>down td/s ta/s<br>
  16721. issue roll down command</li>
  16722. <li>position position/% [&alpha;/&#176]<br>
  16723. drive blinds to position with angle value</li>
  16724. <li>stop<br>
  16725. issue blinds stops command</li>
  16726. <li>runtimeSet tu/s td/s<br>
  16727. set runtime parameter</li>
  16728. <li>angleSet ta/s<br>
  16729. set angle configuration</li>
  16730. <li>positionMinMax positionMin/% positionMax/%<br>
  16731. set min, max values for position</li>
  16732. <li>angleMinMax &alpha;o/&#176 &alpha;s/&#176<br>
  16733. set slat angle for open and shut position</li>
  16734. <li>positionLogic normal|inverse<br>
  16735. set position logic</li>
  16736. </ul><br>
  16737. Runtime Range: tu|td = 0 s ... 255 s<br>
  16738. Select a runtime up and a runtime down that is at least as long as the
  16739. shading element or roller shutter needs to move from its end position to
  16740. the other position.<br>
  16741. Position Range: position = 0 % ... 100 %<br>
  16742. Angle Time Range: ta = 0 s ... 25.5 s<br>
  16743. Runtime value for the sunblind reversion time. Select the time to revolve
  16744. the sunblind from one slat angle end position to the other end position.<br>
  16745. Slat Angle: &alpha;|&alpha;o|&alpha;s = -180 &#176 ... 180 &#176<br>
  16746. Position Logic, normal: Blinds fully opens corresponds to Position = 0 %<br>
  16747. Position Logic, inverse: Blinds fully opens corresponds to Position = 100 %<br>
  16748. The attr subType must be gateway and gwCmd must be blindCmd.<br>
  16749. See also attributes <a href="#EnOcean_sendDevStatus">sendDevStatus and <a href="#EnOcean_serviceOn">serviceOn</a></a><br>
  16750. The profile is linked with controller profile, see <a href="#Blind Status">Blind Status</a>.<br>
  16751. </li>
  16752. <br><br>
  16753. <li>Extended Lighting Control (EEP A5-38-09)<br>
  16754. [untested]<br>
  16755. <ul>
  16756. <code>set &lt;name&gt; &lt;value&gt;</code>
  16757. <br><br>
  16758. where <code>value</code> is
  16759. <li>teach<br>
  16760. initiate remote teach-in</li>
  16761. <li>on<br>
  16762. issue switch on command</li>
  16763. <li>off<br>
  16764. issue switch off command</li>
  16765. <li>dim dim [rampTime/s]<br>
  16766. issue dim command</li>
  16767. <li>dimup rampTime/s<br>
  16768. issue dim command</li>
  16769. <li>dimdown rampTime/s<br>
  16770. issue dim command</li>
  16771. <li>stop<br>
  16772. stop dimming</li>
  16773. <li>rgb &lt;red color value&gt&lt;green color value&gt&lt;blue color value&gt<br>
  16774. issue color value command</li>
  16775. <li>scene drive|store 0..15<br>
  16776. store actual value in the scene or drive to scene value</li>
  16777. <li>dimMinMax &lt;min value&gt &lt;max value&gt<br>
  16778. set minimal and maximal dimmer value</li>
  16779. <li>lampOpHours 0..65535<br>
  16780. set the operation hours of the lamp</li>
  16781. <li>block unlock|on|off|local<br>
  16782. locking local operations</li>
  16783. <li>meteringValues 0..65535 mW|W|kW|MW|Wh|kWh|MWh|GWh|mA|mV<br>
  16784. set a new value for the energy metering (overwrite the actual value with the selected unit)</li>
  16785. <li>meteringValues 0..6553.5 A|V<br>
  16786. set a new value for the energy metering (overwrite the actual value with the selected unit)</li>
  16787. <li><a href="#setExtensions">set extensions</a> are supported.</li>
  16788. </ul><br>
  16789. color values: 00 ... FF hexadecimal<br>
  16790. rampTime Range: t = 1 s ... 65535 s or 1 if no time specified, ramping time can be set by attribute
  16791. <a href="#EnOcean_rampTime">rampTime</a><br>
  16792. The attr subType or subTypSet must be lightCtrl.01. This is done if the device was created by autocreate.<br>
  16793. The subType is associated with the subtype lightCtrlState.02.
  16794. </li>
  16795. <br><br>
  16796. <li><a name="Manufacturer Specific Applications">Manufacturer Specific Applications</a> (EEP A5-3F-7F)<br>
  16797. Shutter<br>
  16798. [Eltako FSB12, FSB14, FSB61, FSB70, tested with Eltako devices only]<br>
  16799. <ul>
  16800. <code>set &lt;name&gt; &lt;value&gt;</code>
  16801. <br><br>
  16802. where <code>value</code> is
  16803. <li>position/% [&alpha;/&#176]<br>
  16804. drive blinds to position with angle value</li>
  16805. <li>anglePos &alpha;/&#176<br>
  16806. drive blinds to angle value</li>
  16807. <li>closes<br>
  16808. issue blinds closes command</li>
  16809. <li>down td/s<br>
  16810. issue roll down command</li>
  16811. <li>opens<br>
  16812. issue blinds opens command</li>
  16813. <li>position position/% [&alpha;/&#176]<br>
  16814. drive blinds to position with angle value</li>
  16815. <li>stop<br>
  16816. issue stop command</li>
  16817. <li>teach<br>
  16818. initiate teach-in mode</li>
  16819. <li>up tu/s<br>
  16820. issue roll up command</li>
  16821. </ul><br>
  16822. Run-time Range: tu|td = 1 s ... 255 s<br>
  16823. Position Range: position = 0 % ... 100 %<br>
  16824. Slat Angle Range: &alpha; = -180 &#176 ... 180 &#176<br>
  16825. Angle Time Range: ta = 0 s ... 6 s<br>
  16826. The devive can only fully controlled if the attributes <a href="#angleMax">angleMax</a>,
  16827. <a href="#angleMin">angleMin</a>, <a href="#angleTime">angleTime</a>,
  16828. <a href="#shutTime">shutTime</a> and <a href="#shutTimeCloses">shutTimeCloses</a>,
  16829. are set correctly.
  16830. If <a href="#EnOcean_settingAccuracy">settingAccuracy</a> is set to high, the run-time is sent in 1/10 increments.<br>
  16831. Set attr subType to manufProfile, manufID to 00D and attr model to Eltako_FSB14|FSB61|FSB70|FSB_ACK manually.
  16832. If the attribute model is set to Eltako_FSB_ACK, with the status "open_ack" the readings position and anglePos are also updated.<br>
  16833. Use the sensor type "Szenentaster/PC" for Eltako devices.
  16834. </li>
  16835. <br><br>
  16836. <li>Electronic switches and dimmers with Energy Measurement and Local Control (D2-01-00 - D2-01-12)<br>
  16837. [Telefunken Funktionsstecker, PEHA Easyclick, AWAG Elektrotechnik AG Omnio UPS 230/xx,UPD 230/xx, NodOn in-wall module, smart plug]<br>
  16838. <ul>
  16839. <code>set &lt;name&gt; &lt;value&gt;</code>
  16840. <br><br>
  16841. where <code>value</code> is
  16842. <li>autoOffTime t/s [&lt;channel&gt;]<br>
  16843. set auto Off timer</li>
  16844. <li>delayOffTime t/s [&lt;channel&gt;]<br>
  16845. set delay Off timer</li>
  16846. <li>dim/% [&lt;channel&gt; [&lt;rampTime&gt;]]<br>
  16847. issue dimming command</li>
  16848. <li>extSwitchMode unavailable|switch|pushbutton|auto [&lt;channel&gt;]<br>
  16849. set external interface mode</li>
  16850. <li>extSwitchType toggle|direction [&lt;channel&gt;]<br>
  16851. set external interface type</li>
  16852. <li>on [&lt;channel&gt;]<br>
  16853. issue switch on command</li>
  16854. <li>off [&lt;channel&gt;]<br>
  16855. issue switch off command</li>
  16856. <li>dim dim/% [&lt;channel&gt; [&lt;rampTime&gt;]]<br>
  16857. issue dimming command</li>
  16858. <li>local dayNight day|night, day is default<br>
  16859. set the user interface indication</li>
  16860. <li>local defaultState on|off|last, off is default<br>
  16861. set the default setting of the output channels when switch on</li>
  16862. <li>local localControl enabled|disabled, disabled is default<br>
  16863. enable the local control of the device</li>
  16864. <li>local overCurrentShutdown off|restart, off is default<br>
  16865. set the behavior after a shutdown due to an overcurrent</li>
  16866. <li>local overCurrentShutdownReset not_active|trigger, not_active is default<br>
  16867. trigger a reset after an overcurrent</li>
  16868. <li>local rampTime&lt;1...3&gt; 0/s, 0.5/s ... 7/s, 7.5/s, 0 is default<br>
  16869. set the dimming time of timer 1 ... 3</li>
  16870. <li>local teachInDev enabled|disabled, disabled is default<br>
  16871. enable the taught-in devices with different EEP</li>
  16872. <li>measurement delta 0/s ... 4095/s, 0 is default<br>
  16873. define the difference between two displayed measurements </li>
  16874. <li>measurement mode energy|power, energy is default<br>
  16875. define the measurand</li>
  16876. <li>measurement report query|auto, query is default<br>
  16877. specify the measurement method</li>
  16878. <li>measurement reset not_active|trigger, not_active is default<br>
  16879. resetting the measured values</li>
  16880. <li>measurement responseTimeMax 10/s ... 2550/s, 10 is default<br>
  16881. set the maximum time between two outputs of measured values</li>
  16882. <li>measurement responseTimeMin 1/s ... 255/s, 1 is default<br>
  16883. set the minimum time between two outputs of measured values</li>
  16884. <li>measurement unit Ws|Wh|KWh|W|KW, Ws is default<br>
  16885. specify the measurement unit</li>
  16886. <li>roomCtrlMode off|comfort|comfort-1|comfort-2|economy|buildingProtection<br>
  16887. set pilot wire mode</li>
  16888. <li>special repeater off|1|2<br>
  16889. set repeater level of device (additional NodOn command)
  16890. </li>
  16891. </ul><br>
  16892. [autoOffTime] = 0 s ... 0.1 s ... 6553.4 s<br>
  16893. [delayOffTime] = 0 s ... 0.1 s ... 6553.4 s<br>
  16894. [channel] = 0...29|all|input, all is default<br>
  16895. The default channel can be specified with the attr <a href="#EnOcean_defaultChannel">defaultChannel</a>.<br>
  16896. [rampTime] = 1..3|switch|stop, switch is default<br>
  16897. The attr subType must be actuator.01. This is done if the device was
  16898. created by autocreate. To control the device, it must be bidirectional paired,
  16899. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  16900. </li>
  16901. <br><br>
  16902. <li>Blind Control for Position and Angle (D2-05-00 - D2-05-01)<br>
  16903. [AWAG Elektrotechnik AG OMNIO UPJ 230/12, REGJ12/04M ]<br>
  16904. <ul>
  16905. <code>set &lt;name&gt; &lt;value&gt;</code>
  16906. <br><br>
  16907. where <code>value</code> is
  16908. <li>opens [&lt;channel&gt;]<br>
  16909. issue blinds opens command</li>
  16910. <li>closes [&lt;channel&gt;]<br>
  16911. issue blinds closes command</li>
  16912. <li>position position/% [[&alpha;/%] [[&lt;channel&gt;] [directly|opens|closes]]]<br>
  16913. drive blinds to position with angle value</li>
  16914. <li>anglePos &alpha;/% [&lt;channel&gt;]<br>
  16915. drive blinds to angle value</li>
  16916. <li>stop [&lt;channel&gt;]<br>
  16917. issue stop command</li>
  16918. <li>alarm [&lt;channel&gt;]<br>
  16919. set actuator to the "alarm" mode. When the actuator ist set to the "alarm" mode neither local
  16920. nor central positioning and configuration commands will be executed. Before entering the "alarm"
  16921. mode, the actuator will execute the "alarm action" as configured by the attribute <a href="#EnOcean_alarmAction">alarmAction</a>
  16922. </li>
  16923. <li>lock [&lt;channel&gt;]<br>
  16924. set actuator to the "blockade" mode. When the actuator ist set to the "blockade" mode neither local
  16925. nor central positioning and configuration commands will be executed.
  16926. </li>
  16927. <li>unlock [&lt;channel&gt;]<br>
  16928. issue unlock command</li>
  16929. </ul><br>
  16930. Channel Range: 1 ... 4|all, default is all<br>
  16931. Position Range: position = 0 % ... 100 %<br>
  16932. Slat Angle Range: &alpha; = 0 % ... 100 %<br>
  16933. The devive can only fully controlled if the attributes <a href="#EnOcean_alarmAction">alarmAction</a>,
  16934. <a href="#angleTime">angleTime</a>, <a href="#EnOcean_reposition">reposition</a> and <a href="#shutTime">shutTime</a>
  16935. are set correctly.<br>
  16936. With the attribute <a name="EnOcean_defaultChannel">defaultChannel</a> the default channel can be specified.<br>
  16937. The attr subType must be blindsCtrl.00 or blindsCtrl.01. This is done if the device was
  16938. created by autocreate. To control the device, it must be bidirectional paired,
  16939. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  16940. </li>
  16941. <br><br>
  16942. <li>Multisensor Windows Handle (D2-06-01)<br>
  16943. [Soda GmbH]<br>
  16944. <ul>
  16945. <code>set &lt;name&gt; &lt;value&gt;</code>
  16946. <br><br>
  16947. where <code>value</code> is
  16948. <li>presence absent|present<br>
  16949. set vacation mode</li>
  16950. <li>handleClosedClick disable|enable<br>
  16951. set handle closed click feature</li>
  16952. <li>batteryLowClick disable|enable<br>
  16953. set battery low click feature</li>
  16954. <li>teachSlave contact|windowHandleClosed|windowHandleOpen|windowHandleTilted<br>
  16955. sent teach-in to the slave devices (contact: EEP: D5-00-01, windowHandle: EEP F6-10-00)<br>
  16956. The events window or handle will get forwarded once a slave-device contact or windowHandle is taught in.
  16957. </li>
  16958. <li>updateInterval t/s<br>
  16959. set sensor update interval</li>
  16960. <li>blinkInterval t/s<br>
  16961. set vacation blink interval</li>
  16962. </ul><br>
  16963. sensor update interval Range: updateInterval = 5 ... 65535<br>
  16964. vacation blick interval Range: blinkInterval = 3 ... 255<br>
  16965. The multisensor window handle is configured using the following attributes:<br>
  16966. <ul>
  16967. <li><a href="#EnOcean_subDefH">subDefH</a></li>
  16968. <li><a href="#EnOcean_subDefW">subDefW</a></li>
  16969. </ul>
  16970. The attr subType must be multisensor.01. This is done if the device was
  16971. created by autocreate.
  16972. </li>
  16973. <br><br>
  16974. <li>Room Control Panels (D2-10-00 - D2-10-02)<br>
  16975. [Kieback & Peter RBW322-FTL]<br>
  16976. <ul>
  16977. <code>set &lt;name&gt; &lt;value&gt;</code>
  16978. <br><br>
  16979. where <code>value</code> is
  16980. <li>buildingProtectionTemp t/&#176C<br>
  16981. set building protection temperature</li>
  16982. <li>clearCmds [&lt;channel&gt;]<br>
  16983. clear waiting commands</li>
  16984. <li>comfortTemp t/&#176C<br>
  16985. set comfort temperature</li>
  16986. <li>config<br>
  16987. Setting the configuration of the room controller, the configuration parameters are set using attributes.</li>
  16988. <li>cooling auto|off|on|no_change<br>
  16989. switch cooling</li>
  16990. <li>deleteTimeProgram<br>
  16991. delete time programs of the room controller</li>
  16992. <li>desired-temp t/&#176C<br>
  16993. set setpoint temperature</li>
  16994. <li>economyTemp t/&#176C<br>
  16995. set economy temperature</li>
  16996. <li>fanSpeed fanspeed/%<br>
  16997. set fan speed</li>
  16998. <li>fanSpeedMode central|local<br>
  16999. set fan speed mode</li>
  17000. <li>heating auto|off|on|no_change<br>
  17001. switch heating</li>
  17002. <li>preComfortTemp t/&#176C<br>
  17003. set pre comfort temperature</li>
  17004. <li>roomCtrlMode buildingProtectionTemp|comfortTemp|economyTemp|preComfortTemp<br>
  17005. select setpoint temperature</li>
  17006. <li>setpointTemp t/&#176C<br>
  17007. set current setpoint temperature</li>
  17008. <li>time<br>
  17009. set time and date of the room controller </li>
  17010. <li>timeProgram<br>
  17011. set time programms of the room contoller</li>
  17012. <li>window closed|open<br>
  17013. put the window state</li>
  17014. </ul><br>
  17015. Setpoint Range: t = 0 &#176C ... 40 &#176C<br>
  17016. The room controller is configured using the following attributes:<br>
  17017. <ul>
  17018. <li><a href="#EnOcean_blockDateTime">blockDateTime</a></li>
  17019. <li><a href="#EnOcean_blockDisplay">blockDisplay</a></li>
  17020. <li><a href="#EnOcean_blockFanSpeed">blockFanSpeed</a></li>
  17021. <li><a href="#EnOcean_blockMotion">blockMotion</a></li>
  17022. <li><a href="#EnOcean_blockProgram">blockProgram</a></li>
  17023. <li><a href="#EnOcean_blockOccupany">blockOccupancy</a></li>
  17024. <li><a href="#EnOcean_blockTemp">blockTemp</a></li>
  17025. <li><a href="#EnOcean_blockTimeProgram">blockTimeProgram</a></li>
  17026. <li><a href="#EnOcean_blockSetpointTemp">blockSetpointTemp</a></li>
  17027. <li><a href="#EnOcean_daylightSavingTime">daylightSavingTime</a></li>
  17028. <li><a href="#EnOcean_displayContent">displayContent</a></li>
  17029. <li><a href="#EnOcean_pollInterval">pollInterval</a></li>
  17030. <li><a href="#EnOcean_temperatureScale">temperatureScale</a></li>
  17031. <li><a href="#EnOcean_timeNotation">timeNotation</a></li>
  17032. <li><a href="#EnOcean_timeProgram[1-4]">timeProgram[1-4]</a></li>
  17033. </ul>
  17034. The attr subType must be roomCtrlPanel.00. This is done if the device was
  17035. created by autocreate. To control the device, it must be bidirectional paired,
  17036. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  17037. </li>
  17038. <br><br>
  17039. <li>Room Control Panels (D2-11-01 - D2-11-08)<br>
  17040. [Thermokon EasySens SR06 LCD-2T/-2T rh -4T/-4T rh]<br>
  17041. <ul>
  17042. <code>set &lt;name&gt; &lt;value&gt;</code>
  17043. <br><br>
  17044. where <code>value</code> is
  17045. <li>cooling on|off, default [colling] = off<br>
  17046. set cooling symbol at the display</li>
  17047. <li>desired-temp t/&#176C<br>
  17048. set setpoint temperature</li>
  17049. <li>fanSpeed auto|off|1|2|3<br>
  17050. set fan speed</li>
  17051. <li>heating on|off, default [heating] = off<br>
  17052. set heating symbol at the display</li>
  17053. <li>occupancy occupied|unoccupied<br>
  17054. set occupancy state</li>
  17055. <li>setpointTemp t/&#176C<br>
  17056. set current setpoint temperature</li>
  17057. <li>setpointShiftMax t/K<br>
  17058. set setpoint shift max</li>
  17059. <li>setpointType setpointTemp|setpointShift<br>
  17060. set setpoint type</li>
  17061. <li>window closed|open, default [window] = closed<br>
  17062. set window open symbol at the display</li>
  17063. </ul><br>
  17064. Setpoint Range: t = 5 &#176C ... 40 &#176C<br>
  17065. Setpoint Shift Max Range: t = 0 K ... 10 K<br>
  17066. The attr subType must be roomCtrlPanel.01. This is done if the device was
  17067. created by autocreate. To control the device, it must be bidirectional paired by Smart Ack,
  17068. see <a href="#EnOcean_smartAck">SmartAck Learning</a>.
  17069. </li>
  17070. <br><br>
  17071. <li>Fan Control (D2-20-00 - D2-20-02)<br>
  17072. [Maico ECA x RC/RCH, ER 100 RC, untested]<br>
  17073. <ul>
  17074. <code>set &lt;name&gt; &lt;value&gt;</code>
  17075. <br><br>
  17076. where <code>value</code> is
  17077. <li>on<br>
  17078. fan on</li>
  17079. <li>off<br>
  17080. fan off</li>
  17081. <li>desired-temp [t/&#176C]<br>
  17082. set setpoint temperature</li>
  17083. <li>fanSpeed fanspeed/%|auto|default<br>
  17084. set fan speed</li>
  17085. <li>humidityThreshold rH/%<br>
  17086. set humidity threshold</li>
  17087. <li>roomSize 0...350/m<sup>2</sup>|default|not_used<br>
  17088. set room size</li>
  17089. <li>setpointTemp [t/&#176C]<br>
  17090. set current setpoint temperature</li>
  17091. </ul><br>
  17092. Setpoint Range: t = 0 &#176C ... 40 &#176C<br>
  17093. The fan controller is configured using the following attributes:<br>
  17094. <ul>
  17095. <li><a href="#EnOcean_setCmdTrigger">setCmdTrigger</a></li>
  17096. <li><a href="#EnOcean_switchHysteresis">switchHysteresis</a></li>
  17097. <li><a href="#temperatureRefDev">temperatureRefDev</a></li>
  17098. </ul>
  17099. The attr subType must be fanCtrl.00. This is done if the device was
  17100. created by autocreate. To control the device, it must be bidirectional paired,
  17101. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>. The profile
  17102. behaves like a master. Only one fan can be taught as a slave.
  17103. </li>
  17104. <br><br>
  17105. <li>Heat Recovery Ventilation (D2-50-00 - D2-50-11)<br>
  17106. [untested]<br>
  17107. <ul>
  17108. <code>set &lt;name&gt; &lt;value&gt;</code>
  17109. <br><br>
  17110. where <code>value</code> is
  17111. <li>ventilation off|1...4|auto|demand|supplyAir|exhaustAir<br>
  17112. select ventilation mode/level</li>
  17113. <li>heatExchangerBypass opens|closes<br>
  17114. override of automatic heat exchanger bypass control</li>
  17115. <li>startTimerMode<br>
  17116. enable timer operation mode</li>
  17117. <li>CO2Threshold default|1/%<br>
  17118. override CO2 threshold for CO2 control in automatic mode</li>
  17119. <li>humidityThreshold default|rH/%<br>
  17120. override humidity threshold for humidity control in automatic mode</li>
  17121. <li>airQuatityThreshold default|1/%<br>
  17122. override air qualidity threshold for air qualidity control in automatic mode</li>
  17123. <li>roomTemp default|t/&#176C<br>
  17124. override room temperature threshold for room temperature control mode</li>
  17125. </ul><br>
  17126. roomTemp Range: t = -63 &#176C ... 63 &#176C<br>
  17127. xThreshold Range: 0 % ... 100 %<br>
  17128. The attr subType must be heatRecovery.00. This is done if the device was
  17129. created by autocreate. To control the device, it must be bidirectional paired,
  17130. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  17131. </li>
  17132. <br><br>
  17133. <li>Valve Control (EEP D2-A0-01)<br>
  17134. <ul>
  17135. <code>set &lt;name&gt; &lt;value&gt;</code>
  17136. <br><br>
  17137. where <code>value</code> is
  17138. <li>closes<br>
  17139. issue closes command (master)</li>
  17140. <li>opens<br>
  17141. issue opens command (master)</li>
  17142. <li>closed<br>
  17143. issue closed command (slave)</li>
  17144. <li>open<br>
  17145. issue open command (slave)</li>
  17146. <li>teachIn<br>
  17147. initiate UTE teach-in (slave)</li>
  17148. <li>teachOut<br>
  17149. initiate UTE teach-out (slave)</li>
  17150. </ul><br>
  17151. The valve controller is configured using the following attributes:<br>
  17152. <ul>
  17153. <li><a href="#EnOcean_devMode">devMode</a></li>
  17154. </ul>
  17155. The attr subType must be valveCtrl.00. This is done if the device was
  17156. created by autocreate. To control the device, it must be bidirectional paired,
  17157. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>. The profile
  17158. behaves like a master or slave, see <a href="#EnOcean_devMode">devMode</a>.
  17159. </li>
  17160. <br><br>
  17161. <li>Generic Profiles<br>
  17162. <ul>
  17163. <code>set &lt;name&gt; &lt;value&gt;</code>
  17164. <br><br>
  17165. where <code>value</code> is
  17166. <li>&lt;00 ... 64&gt;-&lt;channel name&gt; &lt;value&gt;<br>
  17167. set channel value</li>
  17168. <li>channelName &lt;channel number&gt;-&lt;channel name&gt;<br>
  17169. rename channel</li>
  17170. <li>teachIn<br>
  17171. sent teach-in telegram</li>
  17172. <li>teachOut<br>
  17173. sent teach-out telegram</li>
  17174. </ul><br>
  17175. The generic profile device is configured using the following attributes:<br>
  17176. <ul>
  17177. <li><a href="#EnOcean_comMode">comMode</a></li>
  17178. <li><a href="#EnOcean_devMode">devMode</a></li>
  17179. <li><a href="#EnOcean_gpDef">gpDef</a></li>
  17180. <li><a href="#EnOcean_manufID">manufID</a></li>
  17181. </ul>
  17182. The attr subType must be genericProfile. This is done if the device was
  17183. created by autocreate. If the profile in slave mode is operated, especially the channel
  17184. definition in the gpDef attributes must be entered manually.
  17185. </li>
  17186. <br><br>
  17187. <li>RAW Command<br>
  17188. <ul>
  17189. <code>set &lt;name&gt; &lt;value&gt;</code>
  17190. <br><br>
  17191. where <code>value</code> is
  17192. <li>1BS|4BS|GPCD|GPSD|GPTI|GPTR|MSC|RPS|UTE|VLD data [status]<br>
  17193. sent data telegram</li>
  17194. </ul><br>
  17195. [data] = &lt;1-byte hex ... 512-byte hex&gt;<br>
  17196. [status] = 0x00 ... 0xFF<br>
  17197. With the help of this command data messages in hexadecimal format can be sent.
  17198. Telegram types (RORG) 1BS, 4BS, RPS, MSC, UTE, VLD, GPCD, GPSD, GPTI and GPTR are supported.
  17199. For further information, see <a href="http://www.enocean-alliance.org/eep/">EnOcean Equipment Profiles (EEP)</a> and
  17200. Generic Profiles.
  17201. </li>
  17202. <br><br>
  17203. <li>Radio Link Test<br>
  17204. <ul>
  17205. <code>set &lt;name&gt; &lt;value&gt;</code>
  17206. <br><br>
  17207. where <code>value</code> is
  17208. <li>standby|stop<br>
  17209. set RLT Master state
  17210. </li>
  17211. </ul><br>
  17212. The Radio Link Test device is configured using the following attributes:<br>
  17213. <ul>
  17214. <li><a href="#EnOcean_rltRepeat">rltRepeat</a></li>
  17215. <li><a href="#EnOcean_rltType">rltType</a></li>
  17216. </ul>
  17217. The attr subType must be readioLinkTest. This is done if the device was
  17218. created by autocreate or manually by <code>define &lt;name&gt; EnOcean A5-3F-00</code><br>.
  17219. </li>
  17220. <br><br>
  17221. </ul></ul>
  17222. <a name="EnOceanget"></a>
  17223. <b>Get</b>
  17224. <ul>
  17225. <li><a name="EnOcean_remoteGet">Remote Management</a>
  17226. <ul>
  17227. <code>get &lt;name&gt; &lt;value&gt;</code>
  17228. <br><br>
  17229. where <code>value</code> is
  17230. <li>remoteDevCfg &lt;start data index&gt; &lt;end data index&gt;<br>
  17231. get device configuration between start index and end index</li>
  17232. <li>remoteFunctions<br>
  17233. get a list of the supported extended functions</li>
  17234. <li>remoteID<br>
  17235. get the remote device ID</li>
  17236. <li>remoteLinkTableInfo<br>
  17237. query supported link table info</li>
  17238. <li>remoteLinkCfg in|out &lt;index&gt; &lt;start data index&gt; &lt;end data index&gt; &lt;length&gt;<br>
  17239. get link table between start index and end index</li>
  17240. <li>remoteLinkTable in|out &lt;start index&gt; &lt;end index&gt;<br>
  17241. get link table between start index and end index</li>
  17242. <li>remoteLinkTableGP in|out &lt;index&gt;<br>
  17243. get link table GP entry with index</li>
  17244. <li>remotePing<br>
  17245. get a ping response from the remote device</li>
  17246. <li>remoteProductID<br>
  17247. query product ID</li>
  17248. <li>remoteRepeater<br>
  17249. asks for the repeater status of the remote device</li>
  17250. <li>remoteStatus<br>
  17251. asks for the status info of the remote device</li>
  17252. <br>
  17253. [&lt;data index&gt;] = 0000...FFFF<br>
  17254. [&lt;index&gt;] = 00...FF<br>
  17255. [&lt;length&gt;] = n x 00...FF<br>
  17256. </ul>
  17257. </li><br><br>
  17258. <li><a name="EnOcean_signalGet">Signal Telegram</a>
  17259. <ul>
  17260. <code>get &lt;name&gt; &lt;value&gt;</code>
  17261. <br><br>
  17262. where <code>value</code> is
  17263. <li>signal energy<br>
  17264. get the energy status</li>
  17265. <li>signal revision<br>
  17266. get the revision of the device</li>
  17267. <li>signal RXlevel<br>
  17268. get the RX level of receiced request</li>
  17269. <li>signal harvester<br>
  17270. get the energy current harvested reporting</li>
  17271. </ul><br>
  17272. Trigger status messages of the device.
  17273. </li><br><br>
  17274. <li>Dual Channel Switch Actuator (EEP A5-11-059)<br>
  17275. [untested]<br>
  17276. <ul>
  17277. <code>get &lt;name&gt; &lt;value&gt;</code>
  17278. <br><br>
  17279. where <code>value</code> is
  17280. <li>state<br>
  17281. status request</li>
  17282. </ul><br>
  17283. The attr subType or subTypSet must be switch.05. This is done if the device was created by autocreate.
  17284. </li>
  17285. <br><br>
  17286. <li>Extended Lighting Control (EEP A5-38-09)<br>
  17287. [untested]<br>
  17288. <ul>
  17289. <code>get &lt;name&gt; &lt;value&gt;</code>
  17290. <br><br>
  17291. where <code>value</code> is
  17292. <li>state<br>
  17293. status request</li>
  17294. </ul><br>
  17295. The attr subType or subTypSet must be lightCtrl.01. This is done if the device was created by autocreate.<br>
  17296. The subType is associated with the subtype lightCtrlState.02.
  17297. </li>
  17298. <br><br>
  17299. <li>Electronic switches and dimmers with Energy Measurement and Local Control (D2-01-00 - D2-01-12)<br>
  17300. [Telefunken Funktionsstecker, PEHA Easyclick, AWAG Elektrotechnik AG Omnio UPS 230/xx,UPD 230/xx, NodOn in-wall module, smart plug]<br>
  17301. <ul>
  17302. <code>get &lt;name&gt; &lt;value&gt;</code>
  17303. <br><br>
  17304. where <code>value</code> is
  17305. <li>roomCtrlMode<br>
  17306. get pilot wire mode</li>
  17307. <li>settings [&lt;channel&gt;]<br>
  17308. get external interface settings</li>
  17309. <li>state [&lt;channel&gt;]<br>
  17310. </li>
  17311. <li>measurement &lt;channel&gt; energy|power<br>
  17312. </li>
  17313. <li>special &lt;channel&gt; health|load|voltage|serialNumber<br>
  17314. additional Permondo SmartPlug PSC234 commands
  17315. </li>
  17316. <li>special &lt;channel&gt; firmwareVersion|reset|taughtInDevID|taughtInDevNum<br>
  17317. additional NodOn commands
  17318. </li>
  17319. </ul><br>
  17320. The default channel can be specified with the attr <a href="#EnOcean_defaultChannel">defaultChannel</a>.<br>
  17321. The attr subType must be actuator.01. This is done if the device was
  17322. created by autocreate. To control the device, it must be bidirectional paired,
  17323. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  17324. </li>
  17325. <br><br>
  17326. <li>Blind Control for Position and Angle (D2-05-00)<br>
  17327. [AWAG Elektrotechnik AG OMNIO UPJ 230/12, REGJ12/04M]<br>
  17328. <ul>
  17329. <code>get &lt;name&gt; &lt;value&gt;</code>
  17330. <br><br>
  17331. where <code>value</code> is
  17332. <li>position [&lt;channel&gt;]<br>
  17333. query position and angle value</li>
  17334. </ul><br>
  17335. Channel Range: 1 ... 4|all, default is all<br>
  17336. The devive can only fully controlled if the attributes <a href="#EnOcean_alarmAction">alarmAction</a>,
  17337. <a href="#angleTime">angleTime</a>, <a href="#EnOcean_reposition">reposition</a> and <a href="#shutTime">shutTime</a>
  17338. are set correctly.<br>
  17339. With the attribute <a name="EnOcean_defaultChannel">defaultChannel</a> the default channel can be specified.<br>
  17340. The attr subType must be blindsCtrl.00 or blindsCrtl.01. This is done if the device was
  17341. created by autocreate. To control the device, it must be bidirectional paired,
  17342. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  17343. </li>
  17344. <br><br>
  17345. <li>Multisensor Windows Handle (D2-06-01)<br>
  17346. [Soda GmbH]<br>
  17347. <ul>
  17348. <code>get &lt;name&gt; &lt;value&gt;</code>
  17349. <br><br>
  17350. where <code>value</code> is
  17351. <li>config<br>
  17352. get configuration settings</li>
  17353. <li>log<br>
  17354. get log data</li>
  17355. </ul><br>
  17356. The multisensor window handle is configured using the following attributes:<br>
  17357. <ul>
  17358. <li><a href="#EnOcean_subDefH">subDefH</a></li>
  17359. <li><a href="#EnOcean_subDefW">subDefW</a></li>
  17360. </ul>
  17361. The attr subType must be multisensor.01. This is done if the device was
  17362. created by autocreate.
  17363. </li>
  17364. <br><br>
  17365. <li>Room Control Panels (D2-10-00 - D2-10-02)<br>
  17366. [Kieback & Peter RBW322-FTL]<br>
  17367. <ul>
  17368. <code>get &lt;name&gt; &lt;value&gt;</code>
  17369. <br><br>
  17370. where <code>value</code> is
  17371. <li>config<br>
  17372. get the configuration of the room controler</li>
  17373. <li>data<br>
  17374. get data</li>
  17375. <li>roomCtrl<br>
  17376. get the parameter of the room controler</li>
  17377. <li>timeProgram<br>
  17378. get the time program</li>
  17379. </ul><br>
  17380. The attr subType must be roomCtrlPanel.00. This is done if the device was
  17381. created by autocreate. To control the device, it must be bidirectional paired,
  17382. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  17383. </li>
  17384. <br><br>
  17385. <li>Fan Control (D2-20-00 - D2-20-02)<br>
  17386. [Maico ECA x RC/RCH, ER 100 RC, untested]<br>
  17387. <ul>
  17388. <code>get &lt;name&gt; &lt;value&gt;</code>
  17389. <br><br>
  17390. where <code>value</code> is
  17391. <li>state<br>
  17392. get the state of the room controler</li>
  17393. </ul><br>
  17394. The attr subType must be fanCtrl.00. This is done if the device was
  17395. created by autocreate. To control the device, it must be bidirectional paired,
  17396. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  17397. </li>
  17398. <br><br>
  17399. <li>Heat Recovery Ventilation (D2-50-00 - D2-50-11)<br>
  17400. [untested]<br>
  17401. <ul>
  17402. <code>get &lt;name&gt; &lt;value&gt;</code>
  17403. <br><br>
  17404. where <code>value</code> is
  17405. <li>basicState<br>
  17406. get the basic state</li>
  17407. <li>extendedState<br>
  17408. get the extended state</li>
  17409. </ul><br>
  17410. The attr subType must be heatRecovery.00. This is done if the device was
  17411. created by autocreate. To control the device, it must be bidirectional paired,
  17412. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  17413. </li>
  17414. <br><br>
  17415. <li>Valve Control (EEP D2-A0-01)<br>
  17416. <ul>
  17417. <code>get &lt;name&gt; &lt;value&gt;</code>
  17418. <br><br>
  17419. where <code>value</code> is
  17420. <li>state<br>
  17421. get the state of the valve controler (master)</li>
  17422. </ul><br>
  17423. The attr subType must be valveCtrl.00. This is done if the device was
  17424. created by autocreate. To control the device, it must be bidirectional paired,
  17425. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>. The profile
  17426. behaves like a master or slave, see <a href="#EnOcean_devMode">devMode</a>.
  17427. </li>
  17428. <br><br>
  17429. </ul><br>
  17430. <a name="EnOceanattr"></a>
  17431. <b>Attributes</b>
  17432. <ul>
  17433. <ul>
  17434. <li><a name="actualTemp">actualTemp</a> t/&#176C<br>
  17435. The value of the actual temperature, used by a Room Sensor and Control Unit
  17436. or when controlling HVAC components e. g. Battery Powered Actuators (MD15 devices). Should by
  17437. filled via a notify from a distinct temperature sensor.<br>
  17438. If absent, the reported temperature from the HVAC components is used.
  17439. </li>
  17440. <li><a name="EnOcean_alarmAction">alarmAction</a> &lt;channel1&gt;[:&lt;channel2&gt;[:&lt;channel3&gt;[:&lt;channel4&gt;]]]<br>
  17441. [alarmAction] = no|stop|opens|closes, default is no<br>
  17442. Action that is executed before the actuator is entering the "alarm" mode.<br>
  17443. Notice subType blindsCrtl.00, blindsCrtl.01: The attribute can only be set while the actuator is online.
  17444. </li>
  17445. <li><a name="angleMax">angleMax</a> &alpha;s/&#176, [&alpha;s] = -180 ... 180, 90 is default.<br>
  17446. Slat angle end position maximum.<br>
  17447. angleMax is supported for shutter.
  17448. </li>
  17449. <li><a name="angleMin">angleMin</a> &alpha;o/&#176, [&alpha;o] = -180 ... 180, -90 is default.<br>
  17450. Slat angle end position minimum.<br>
  17451. angleMin is supported for shutter.
  17452. </li>
  17453. <li><a name="angleTime">angleTime</a> &lt;channel1&gt;[:&lt;channel2&gt;[:&lt;channel3&gt;[:&lt;channel4&gt;]]]<br>
  17454. subType blindsCtrl.00, blindsCtrl.01: [angleTime] = 0|0.01 .. 2.54, 0 is default.<br>
  17455. subType manufProfile: [angleTime] = 0 ... 6, 0 is default.<br>
  17456. Runtime value for the sunblind reversion time. Select the time to revolve
  17457. the sunblind from one slat angle end position to the other end position.<br>
  17458. Notice subType blindsCrtl.00: The attribute can only be set while the actuator is online.
  17459. </li>
  17460. <li><a name="EnOcean_blockDateTime">blockDateTime</a> yes|no, [blockDateTime] = no is default.<br>
  17461. blockDateTime is supported for roomCtrlPanel.00.
  17462. </li>
  17463. <li><a name="EnOcean_blockDisplay">blockDisplay</a> yes|no, [blockDisplay] = no is default.<br>
  17464. blockDisplay is supported for roomCtrlPanel.00.
  17465. </li>
  17466. <li><a name="EnOcean_blockFanSpeed">blockFanSpeed</a> yes|no, [blockFanSpeed] = no is default.<br>
  17467. blockFanSpeed is supported for roomCtrlPanel.00.
  17468. </li>
  17469. <li><a name="EnOcean_blockKey">blockKey</a> yes|no, [blockKey] = no is default.<br>
  17470. blockKey is supported for roomCtrlPanel.00 and hvac.04.
  17471. </li>
  17472. <li><a name="EnOcean_blockMotion">blockMotion</a> yes|no, [blockMotion] = no is default.<br>
  17473. blockMotion is supported for roomCtrlPanel.00.
  17474. </li>
  17475. <li><a name="EnOcean_blockOccupany">blockOccupancy</a> yes|no, [blockOccupancy] = no is default.<br>
  17476. blockOccupancy is supported for roomCtrlPanel.00.
  17477. </li>
  17478. <li><a name="EnOcean_blockTemp">blockTemp</a> yes|no, [blockTemp] = no is default.<br>
  17479. blockTemp is supported for roomCtrlPanel.00.
  17480. </li>
  17481. <li><a name="EnOcean_blockTimeProgram">blockTimeProgram</a> yes|no, [blockTimeProgram] = no is default.<br>
  17482. blockTimeProgram is supported for roomCtrlPanel.00.
  17483. </li>
  17484. <li><a name="EnOcean_blockSetpointTemp">blockSetpointTemp</a> yes|no, [blockSetpointTemp] = no is default.<br>
  17485. blockSetPointTemp is supported for roomCtrlPanel.00.
  17486. </li>
  17487. <li><a name="EnOcean_blockUnknownMSC">blockUnknownMSC</a> yes|no,
  17488. [blockUnknownMSC] = no is default.<br>
  17489. If the structure of the MSC telegrams can not interpret the raw data to be output. Setting this attribute to yes,
  17490. the output can be suppressed.
  17491. </li>
  17492. <li><a name="EnOcean_comMode">comMode</a> biDir|confirm|uniDir, [comMode] = uniDir is default.<br>
  17493. Communication Mode between an enabled EnOcean device and Fhem.<br>
  17494. Unidirectional communication means a point-to-multipoint communication
  17495. relationship. The EnOcean device e. g. sensors does not know the unique
  17496. Fhem SenderID.<br>
  17497. If the attribute is set to confirm Fhem awaits confirmation telegrams from the remote device.<br>
  17498. Bidirectional communication means a point-to-point communication
  17499. relationship between an enabled EnOcean device and Fhem. It requires all parties
  17500. involved to know the unique Sender ID of their partners. Bidirectional communication
  17501. needs a teach-in / teach-out process, see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  17502. </li>
  17503. <li><a name="EnOcean_dataEnc">dataEnc</a> VAES|AES-CBC, [dataEnc] = VAES is default<br>
  17504. Data encryption algorithm
  17505. </li>
  17506. <li><a name="EnOcean_defaultChannel">defaultChannel</a> &lt;channel&gt;
  17507. subType actuator.01: [defaultChannel] = all|input|0 ... 29, all is default.<br>
  17508. subType blindsCtrl.00, blindsCtrl.01: [defaultChannel] = all|1 ... 4, all is default.<br>
  17509. Default device channel
  17510. </li>
  17511. <li><a name="EnOcean_daylightSavingTime">daylightSavingTime</a> supported|not_supported, [daylightSavingTime] = supported is default.<br>
  17512. daylightSavingTime is supported for roomCtrlPanel.00.
  17513. </li>
  17514. <li><a name="EnOcean_demandRespAction">demandRespAction</a> &lt;command&gt;<br>
  17515. Command being executed after an demand response command is set. If &lt;command&gt; is enclosed in {},
  17516. then it is a perl expression, if it is enclosed in "", then it is a shell command,
  17517. else it is a "plain" fhem.pl command (chain). In the &lt;command&gt; you can access the demand response
  17518. readings $TYPE, $NAME, $LEVEL, $SETPOINT, $POWERUSAGE, $POWERUSAGESCALE, $POWERUSAGELEVEL, $STATE. In addition,
  17519. the variables $TARGETNAME, $TARGETTYPE, $TARGETSTATE can be used if the action is executed
  17520. on the target device. This data is available as a local variable in perl, as environment variable for shell
  17521. scripts, and will be textually replaced for Fhem commands.
  17522. </li>
  17523. <li><a name="EnOcean_demandRespMax">demandRespMax</a> A0|AI|B0|BI|C0|CI|D0|DI, [demandRespMax] = B0 is default<br>
  17524. Switch command which is executed if the demand response switches to a maximum.
  17525. </li>
  17526. <li><a name="EnOcean_demandRespMin">demandRespMin</a> A0|AI|B0|BI|C0|CI|D0|DI, [demandRespMax] = BI is default<br>
  17527. Switch command which is executed if the demand response switches to a minimum.
  17528. </li>
  17529. <li><a name="EnOcean_demandRespRefDev">demandRespRefDev</a> &lt;name&gt;<br>
  17530. </li>
  17531. <li><a name="EnOcean_demandRespRandomTime">demandRespRandomTime</a> t/s [demandRespRandomTime] = 1 is default<br>
  17532. Maximum length of the random delay at the start or end of a demand respose event in slave mode.
  17533. </li>
  17534. <li><a name="EnOcean_demandRespThreshold">demandRespThreshold</a> 0...15 [demandRespTheshold] = 8 is default<br>
  17535. Threshold for switching the power usage level between minimum and maximum in the master mode.
  17536. </li>
  17537. <li><a name="EnOcean_demandRespTimeoutLevel">demandRespTimeoutLevel</a> max|last [demandRespTimeoutLevel] = max is default<br>
  17538. Demand response timeout level in slave mode.
  17539. </li>
  17540. <li><a name="devChannel">devChannel</a> 00 ... FF, [devChannel] = FF is default<br>
  17541. Number of the individual device channel, FF = all channels supported by the device
  17542. </li>
  17543. <li><a name="destinationID">destinationID</a> multicast|unicast|00000001 ... FFFFFFFF,
  17544. [destinationID] = multicast is default<br>
  17545. Destination ID, special values: multicast = FFFFFFFF, unicast = [DEF]
  17546. </li>
  17547. <li><a name="EnOcean_devMode">devMode</a> master|slave, [devMode] = master is default.<br>
  17548. device operation mode.
  17549. </li>
  17550. <li><a href="#devStateIcon">devStateIcon</a></li>
  17551. <li><a name="EnOcean_dimMax">dimMax</a> dim/%|off, [dimMax] = 255 is default.<br>
  17552. maximum brightness value<br>
  17553. dimMax is supported for the profile gateway/dimming.
  17554. </li>
  17555. <li><a name="EnOcean_dimMin">dimMin</a> dim/%|off, [dimMax] = off is default.<br>
  17556. minimum brightness value<br>
  17557. If [dimMax] = off, then the actuator takes down the ramp time set there.
  17558. dimMin is supported for the profile gateway/dimming.
  17559. </li>
  17560. <li><a name="dimValueOn">dimValueOn</a> dim/%|last|stored,
  17561. [dimValueOn] = 100 is default.<br>
  17562. Dim value for the command "on".<br>
  17563. The dimmer switched on with the value 1 % ... 100 % if [dimValueOn] =
  17564. 1 ... 100.<br>
  17565. The dimmer switched to the last dim value received from the
  17566. bidirectional dimmer if [dimValueOn] = last.<br>
  17567. The dimmer switched to the last Fhem dim value if [dimValueOn] =
  17568. stored.<br>
  17569. dimValueOn is supported for the profile gateway/dimming.
  17570. </li>
  17571. <li><a href="#EnOcean_disable">disable</a> 0|1<br>
  17572. If applied set commands will not be executed.
  17573. </li>
  17574. <li><a href="#EnOcean_disabledForIntervals">disabledForIntervals</a> HH:MM-HH:MM HH:MM-HH-MM...<br>
  17575. Space separated list of HH:MM tupels. If the current time is between
  17576. the two time specifications, set commands will not be executed. Instead of
  17577. HH:MM you can also specify HH or HH:MM:SS. To specify an interval
  17578. spawning midnight, you have to specify two intervals, e.g.:
  17579. <ul>
  17580. 23:00-24:00 00:00-01:00
  17581. </ul>
  17582. </li>
  17583. <li><a name="EnOcean_displayContent">displayContent</a>
  17584. humidity|off|setpointTemp|temperatureExtern|temperatureIntern|time|default|no_change, [displayContent] = no_change is default.<br>
  17585. displayContent is supported for roomCtrlPanel.00.
  17586. </li>
  17587. <li><a name="EnOcean_displayOrientation">displayOrientation</a> rad/&#176, [displayOrientation] = 0|90|180|270, 0 is default.<br>
  17588. Display orientation of the actuator
  17589. </li>
  17590. <li><a href="#do_not_notify">do_not_notify</a></li>
  17591. <li><a name="EnOcean_eep">eep</a> &lt;00...FF&gt;-&lt;00...3F&gt;-&lt;00...7F&gt;<br>
  17592. EnOcean Equipment Profile (EEP)
  17593. <li><a href="#eventMap">eventMap</a></li>
  17594. </li>
  17595. <li><a name="EnOcean_gpDef">gpDef</a> &lt;name of channel 00&gt;:&lt;O|I&gt;:&lt;channel type&gt;:&lt;signal type&gt;:&lt;value type&gt;[:&lt;resolution&gt;[:&lt;engineering min&gt;:&lt;scaling min&gt;:&lt;engineering max&gt;:&lt;scaling max&gt;]] ...
  17596. &lt;name of channel 64&gt;:&lt;O|I&gt;:&lt;channel type&gt;:&lt;signal type&gt;:&lt;value type&gt;[:&lt;resolution&gt;[:&lt;engineering min&gt;:&lt;scaling min&gt;:&lt;engineering max&gt;:&lt;scaling max&gt;]]
  17597. <br>
  17598. Generic Profiles channel definitions are set automatically in master mode. If the profile in slave mode is operated, the channel
  17599. definition must be entered manually. For each channel, the channel definitions are to be given in ascending order. The channel
  17600. parameters to be specified in decimal. First, the outgoing channels (direction = O) are to be defined, then the incoming channels
  17601. (direction = I) should be described. The channel numbers are assigned automatically starting with 00th.
  17602. </li>
  17603. <li><a name="gwCmd">gwCmd</a> switching|dimming|setpointShift|setpointBasic|controlVar|fanStage|blindCmd<br>
  17604. Gateway Command Type, see <a href="#Gateway">Gateway</a> profile
  17605. </li>
  17606. <li><a name="EnOcean_humidity">humidity</a> rH/%<br>
  17607. The value of the actual humidity, used by a Room Sensor and Control Unit. Should by
  17608. filled via a notify from a distinct humidity sensor.
  17609. </li>
  17610. <li><a name="EnOcean_humidityRefDev">humidityRefDev</a> &lt;name&gt;<br>
  17611. Name of the device whose reference value is read. The reference values is
  17612. the reading humidity.
  17613. </li>
  17614. <li><a href="#ignore">ignore</a></li>
  17615. <li><a href="#IODev">IODev</a></li>
  17616. <li><a name="EnOcean_keyRcv">keyRcv</a> &lt;private key 16 byte hex&gt;<br>
  17617. Private Key for receive direction
  17618. </li>
  17619. <li><a name="EnOcean_keySnd">keySnd</a> &lt;private key 16 byte hex&gt;<br>
  17620. Private Key for send direction
  17621. </li>
  17622. <li><a name="EnOcean_macAlgo">macAlgo</a> no|3|4<br>
  17623. MAC Algorithm
  17624. </li>
  17625. <li><a name="EnOcean_manufID">manufID</a> &lt;000 ... 7FF&gt;<br>
  17626. Manufacturer ID number
  17627. </li>
  17628. <li><a name="EnOcean_measurementCtrl">measurementCtrl</a> enable|disable<br>
  17629. Enable or disable the temperature measurements (room and feed temperature) of the actuator. If the temperature
  17630. measurements are turned off, an external temperature sensor must be exists, see attribute
  17631. <a href="#temperatureRefDev">temperatureRefDev</a>.
  17632. </li>
  17633. <li><a href="#model">model</a></li>
  17634. <li><a name="EnOcean_observe">observe</a> off|on, [observe] = off is default.<br>
  17635. Observing and repeating the execution of set commands
  17636. </li>
  17637. <li><a name="EnOcean_observeCmdRepetition">observeCmdRepetition</a> 1..5, [observeCmdRepetition] = 2 is default.<br>
  17638. Maximum number of command retries
  17639. </li>
  17640. <li><a name="EnOcean_observeErrorAction">observeErrorAction</a> &lt;command&gt;<br>
  17641. Command being executed after an error. If &lt;command&gt; is enclosed in {},
  17642. then it is a perl expression, if it is enclosed in "", then it is a shell command,
  17643. else it is a "plain" fhem.pl command (chain). In the &lt;command&gt; you can access the set
  17644. command. $TYPE, $NAME, $FAILEDDEV, $EVENT, $EVTPART0, $EVTPART1, $EVTPART2, etc. contains the space separated
  17645. set parts. The <a href="#eventMap">eventMap</a> replacements are taken into account. This data
  17646. is available as a local variable in perl, as environment variable for shell
  17647. scripts, and will be textually replaced for Fhem commands.
  17648. </li>
  17649. <li><a name="EnOcean_observeInterval">observeInterval</a> 1/s ... 255/s, [observeInterval] = 1 is default.<br>
  17650. Interval between two observations
  17651. </li>
  17652. <li><a name="EnOcean_observeLogic">observeLogic</a> and|or, [observeLogic] = or is default.<br>
  17653. Observe logic
  17654. </li>
  17655. <li><a name="EnOcean_observeRefDev">observeRefDev</a> &lt;name&gt; [&lt;name&gt; [&lt;name&gt;]],
  17656. [observeRefDev] = &lt;name of the own device&gt; is default<br>
  17657. Names of the devices to be observed. The list must be separated by spaces.
  17658. </li>
  17659. <li><a name="EnOcean_pidActorCallBeforeSetting">pidActorCallBeforeSetting</a>,
  17660. [pidActorCallBeforeSetting] = not defined is default<br>
  17661. Callback-function, which can manipulate the actorValue. Further information see modul PID20.
  17662. </li>
  17663. <li><a name="EnOcean_pidActorErrorAction">pidActorErrorAction</a> freeze|errorPos,
  17664. [pidActorErrorAction] = freeze is default<br>
  17665. required action on error
  17666. </li>
  17667. <li><a name="EnOcean_pidActorErrorPos">pidActorErrorPos</a> valvePos/%,
  17668. [pidActorErrorPos] = 0...100, 0 is default<br>
  17669. actor's position to be used in case of error
  17670. </li>
  17671. <li><a name="EnOcean_pidActorLimitLower">pidActorLimitLower</a> valvePos/%,
  17672. [pidActorLimitLower] = 0...100, 0 is default<br>
  17673. lower limit for actor
  17674. </li>
  17675. <li><a name="EnOcean_pidActorLimitUpper">pidActorLimitUpper</a> valvePos/%,
  17676. [pidActorLimitUpper] = 0...100, 100 is default<br>
  17677. upper limit for actor
  17678. </li>
  17679. <li><a name="EnOcean_pidCtrl">pidCtrl</a> on|off,
  17680. [pidCtrl] = on is default<br>
  17681. Activate the Fhem PID regulator
  17682. </li>
  17683. <li><a name="EnOcean_pidDeltaTreshold">pidDeltaTreshold</a> &lt;floating-point number&gt;,
  17684. [pidDeltaTreshold] = 0 is default<br>
  17685. if delta < delta-threshold the pid will enter idle state
  17686. </li>
  17687. <li><a name="EnOcean_pidFactor_P">pidFactor_P</a> &lt;floating-point number&gt;,
  17688. [pidFactor_P] = 25 is default<br>
  17689. P value for PID
  17690. </li>
  17691. <li><a name="EnOcean_pidFactor_I">pidFactor_I</a> &lt;floating-point number&gt;,
  17692. [pidFactor_I] = 0.25 is default<br>
  17693. I value for PID
  17694. </li>
  17695. <li><a name="EnOcean_pidFactor_D">pidFactor_D</a> &lt;floating-point number&gt;,
  17696. [pidFactor_D] = 0 is default<br>
  17697. D value for PID
  17698. </li>
  17699. <li><a name="EnOcean_pidIPortionCallBeforeSetting">pidIPortionCallBeforeSetting</a>
  17700. [pidIPortionCallBeforeSetting] = not defined is default<br>
  17701. Callback-function, which can manipulate the value of I-Portion. Further information see modul PID20.
  17702. </li>
  17703. <li><a name="EnOcean_pidSensorTimeout">pidSensorTimeout t/s</a>
  17704. [pidSensorTimeout] = 3600 is default<br>
  17705. number of seconds to wait before sensor <a href="#temperatureRefDev">temperatureRefDev</a> will be recognized n/a
  17706. </li>
  17707. <li><a name="EnOcean_pollInterval">pollInterval</a> t/s, [pollInterval] = 10 is default.<br>
  17708. [pollInterval] = 1 ... 1440.<br>
  17709. pollInterval is supported for roomCtrlPanel.00.
  17710. </li>
  17711. <li><a name="EnOcean_rampTime">rampTime</a> t/s or relative, [rampTime] = 1 is default.<br>
  17712. No ramping or for Eltako dimming speed set on the dimmer if [rampTime] = 0.<br>
  17713. Gateway/dimmung: Ramping time 1 s to 255 s or relative fast to low dimming speed if [rampTime] = 1 ... 255.<br>
  17714. lightCtrl.01: Ramping time 1 s to 65535 s<br>
  17715. rampTime is supported for gateway, command dimming and lightCtrl.01.
  17716. </li>
  17717. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  17718. <li><a name="EnOcean_rcvRespAction">rcvRespAction</a> &lt;command&gt;<br>
  17719. Command being executed after an message from the aktor is received and before an response message is sent.
  17720. If &lt;command&gt; is enclosed in {}, then it is a perl expression, if it is enclosed in "", then it is a shell command,
  17721. else it is a "plain" fhem.pl command (chain). In the &lt;command&gt; you can access the name of the device by using $NAME
  17722. and the current readings $ACTUATORSTATE, $BATTERY, $COVER, $ENERGYINPUT, $ENERGYSTORAGE, $MAINTENANCEMODE, $OPERATIONMODE,
  17723. $ROOMTEMP, $SELFCTRL, $SETPOINT, $SETPOINTTEMP, $SUMMERMODE, $TEMPERATURE, $WINDOW for the subType hvac.01 and $NAME,
  17724. $BATTERY, $FEEDTEMP, $MAINTENANCEMODE, $OPERATIONMODE, $ROOMTEMP, $SETPOINT, $SETPOINTTEMP, $SUMMERMODE, $TEMPERATURE
  17725. for the subType hvac.04.
  17726. This data is available as a local variable in perl, as environment variable for shell
  17727. scripts, and will be textually replaced for Fhem commands.
  17728. </li>
  17729. <li><a name="EnOcean_remoteCode">remoteCode</a> &lt;00000000...FFFFFFFE&gt;<br>
  17730. Remote Management Security Code, 00000000 is interpreted as on code has been set.
  17731. </li>
  17732. <li><a name="EnOcean_remoteEEP">remoteEEP</a> &lt;00...FF&gt;-&lt;00...3F&gt;-&lt;00...7F&gt;<br>
  17733. Remote Management EnOcean Equipment Profile (EEP)
  17734. </li>
  17735. <li><a name="EnOcean_remoteID">remoteID</a> &lt;00000001...FFFFFFFE&gt;<br>
  17736. Remote Management Remote Device ID
  17737. </li>
  17738. <li><a name="EnOcean_remoteManagement">remoteManagement</a> client|manager|off,
  17739. [remoteManagement] = off is default.<br>
  17740. Enable Remote Management for the device.
  17741. </li>
  17742. <li><a name="EnOcean_remoteManufID">remoteManufID</a> &lt;000...7FF&gt;<br>
  17743. Remote Management Manufacturer ID
  17744. </li>
  17745. <li><a name="repeatingAllowed">repeatingAllowed</a> yes|no,
  17746. [repeatingAllowed] = yes is default.<br>
  17747. EnOcean Repeater in the transmission range of Fhem may forward data messages
  17748. of the device, if the attribute is set to yes.
  17749. </li>
  17750. <li><a name="EnOcean_releasedChannel">releasedChannel</a> A|B|C|D|I|0|auto, [releasedChannel] = auto is default.<br>
  17751. Attribute releasedChannel determines via which SenderID (subDefA ... subDef0) the command released is sent.
  17752. If [releasedChannel] = auto, the SenderID the last command A0, AI, B0, BI, C0, CI, D0 or DI is used.
  17753. Attribute releasedChannel is supported for attr switchType = central and attr switchType = channel.
  17754. </li>
  17755. <li><a name="EnOcean_reposition">reposition</a> directly|opens|closes, [reposition] = directly is default.<br>
  17756. Attribute reposition specifies how to adjust the internal positioning tracker before going to the new position.
  17757. </li>
  17758. <li><a name="EnOcean_rlcAlgo">rlcAlgo</a> 2++|3++<br>
  17759. RLC Algorithm
  17760. </li>
  17761. <li><a name="EnOcean_rlcRcv">rlcRcv</a> &lt;rolling code 2 or 3 byte hex&gt;<br>
  17762. Rolling Code for receive direction
  17763. </li>
  17764. <li><a name="EnOcean_rlcSnd">rlcSnd</a> &lt;rolling code 2 or 3 byte hex&gt;<br>
  17765. Rolling Code for send direction
  17766. </li>
  17767. <li><a name="EnOcean_rlcTX">rlcTX</a> false|true<br>
  17768. Rolling Code is expected in the received telegram
  17769. </li>
  17770. <li><a name="EnOcean_rltRepeat">rltRepeat</a> 16|32|64|128|256,
  17771. [rltRepeat] = 16 is default.<br>
  17772. Number of RLT MasterTest messages sent
  17773. </li>
  17774. <li><a name="EnOcean_rltType">rltType</a> 1BS|4BS,
  17775. [rltType] = 4BS is default.<br>
  17776. Type of RLT MasterTest message
  17777. </li>
  17778. <li><a name="scaleDecimals">scaleDecimals</a> 0 ... 9<br>
  17779. Decimal rounding with x digits of the scaled reading setpoint
  17780. </li>
  17781. <li><a name="EnOcean_teachMethod">teachMethod</a> 1BS|4B|confirm|GP|RPS|smartAck|STE|UTE<br>
  17782. teach-in method
  17783. </li>
  17784. <li><a name="scaleMax">scaleMax</a> &lt;floating-point number&gt;<br>
  17785. Scaled maximum value of the reading setpoint
  17786. </li>
  17787. <li><a name="scaleMin">scaleMin</a> &lt;floating-point number&gt;<br>
  17788. Scaled minimum value of the reading setpoint
  17789. </li>
  17790. <li><a name="EnOcean_secLevel">secLevel</a> encapsulation|encryption|off, [secLevel] = off is default<br>
  17791. Security level of the data
  17792. </li>
  17793. <li><a name="EnOcean_secMode">secMode</a> rcv|snd|bidir<br>
  17794. Telegram direction, which is secured
  17795. </li>
  17796. <li><a name="EnOcean_sendDevStatus">sendDevStatus</a> no|yes, [sendDevStatus] = no is default.<br>
  17797. Send new status of the device.
  17798. </li>
  17799. <li><a name="sensorMode">sensorMode</a> switch|pushbutton,
  17800. [sensorMode] = switch is default.<br>
  17801. The status "released" will be shown in the reading state if the
  17802. attribute is set to "pushbutton".
  17803. </li>
  17804. <li><a name="EnOcean_serviceOn">serviceOn</a> no|yes,
  17805. [serviceOn] = no is default.<br>
  17806. Device in Service Mode.
  17807. </li>
  17808. <li><a name="EnOcean_setCmdTrigger">setCmdTrigger</a> man|refDev, [setCmdTrigger] = man is default.<br>
  17809. Operation mode to send set commands<br>
  17810. If the attribute is set to "refDev", a device-specific set command is sent when the reference device is updated.
  17811. For the subType "roomSensorControl.05" and "fanCrtl.00" the reference "temperatureRefDev" is supported.<br>
  17812. For the subType "roomSensorControl.01" the references "humidityRefDev" and "temperatureRefDev" are supported.<br>
  17813. </li>
  17814. <li><a name="EnOcean_setpointRefDev">setpointRefDev</a> &lt;name&gt;<br>
  17815. Name of the device whose reference value is read. The reference values is
  17816. the reading setpoint.
  17817. </li>
  17818. <li><a name="EnOcean_setpointSummerMode">setpointSummerMode</a> valvePos/%,
  17819. [setpointSummerMode] = 0...100, 0 is default<br>
  17820. Valve position in summer operation
  17821. </li>
  17822. <li><a name="EnOcean_setpointTempRefDev">setpointTempRefDev</a> &lt;name&gt;<br>
  17823. Name of the device whose reference value is read. The reference values is
  17824. the reading setpointTemp.
  17825. </li>
  17826. <li><a name="EnOcean_settingAccuracy">settingAccuracy</a> high|low,
  17827. [settingAccuracy] = low is default.<br>
  17828. set setting accurancy.
  17829. </li>
  17830. <li><a href="#showtime">showtime</a></li>
  17831. <li><a name="shutTime">shutTime</a> &lt;channel1&gt;[:&lt;channel2&gt;[:&lt;channel3&gt;[:&lt;channel4&gt;]]]<br>
  17832. subType blindsCtrl.00, blindsCtrl.01: [shutTime] = 5 ... 300, 300 is default.<br>
  17833. subType manufProfile: [shutTime] = 1 ... 255, 255 is default.<br>
  17834. Use the attr shutTime to set the time delay to the position "Halt" in
  17835. seconds. Select a delay time that is at least as long as the shading element
  17836. or roller shutter needs to move from its end position to the other position.<br>
  17837. Notice subType blindsCrtl.00: The attribute can only be set while the actuator is online.
  17838. </li>
  17839. <li><a name="shutTimeCloses">shutTimeCloses</a> t/s, [shutTimeCloses] = 1 ... 255,
  17840. [shutTimeCloses] = [shutTime] is default.<br>
  17841. Set the attr shutTimeCloses to define the runtime used by the commands opens and closes.
  17842. Select a runtime that is at least as long as the value set by the delay switch of the actuator.
  17843. <br>
  17844. shutTimeCloses is supported for shutter.
  17845. </li>
  17846. <li><a name="EnOcean_signal">signal</a> off|on,
  17847. [signal] = off is default.<br>
  17848. Activate the request functions of signal telegram messages.
  17849. </li>
  17850. <li><a name="EnOcean_signOfLife">signOfLife</a> off|on, [sifnOfLive] = off is default.<br>
  17851. Monitoring signOfLife telegrams from sensors.
  17852. </li>
  17853. <li><a name="EnOcean_signOfLifeInterval">signOfLifeInterval</a> 1...65535<br>
  17854. Monitoring period in seconds for signOfLife telegrams from sensors.
  17855. </li>
  17856. <li><a name="subDef">subDef</a> &lt;EnOcean SenderID&gt;,
  17857. [subDef] = [DEF] is default.<br>
  17858. SenderID (<a href="#TCM">TCM</a> BaseID + offset) to control a bidirectional switch or actor.<br>
  17859. In order to control devices that send acknowledge telegrams, you cannot reuse the ID of this
  17860. devices, instead you have to create your own, which must be in the
  17861. allowed ID-Range of the underlying IO device. For this first query the
  17862. <a href="#TCM">TCM</a> with the "<code>get &lt;tcm&gt; idbase</code>" command. You can use
  17863. up to 128 IDs starting with the base shown there.<br>
  17864. If [subDef] = getNextID FHEM can assign a free SenderID alternatively. The system configuration
  17865. needs to be reloaded. The assigned SenderID will only displayed after the system configuration
  17866. has been reloaded, e.g. Fhem command rereadcfg.
  17867. </li>
  17868. <li><a name="subDefA">subDefA</a> &lt;EnOcean SenderID&gt;,
  17869. [subDefA] = [subDef] is default.<br>
  17870. SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = A0|AI|released<br>
  17871. Used with switch type "channel". Set attr switchType to channel.<br>
  17872. subDefA is supported for switches.<br>
  17873. Second action is not sent.<br>
  17874. If [subDefA] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
  17875. displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
  17876. </li>
  17877. <li><a name="subDefB">subDefB</a> &lt;EnOcean SenderID&gt;,
  17878. [subDefB] = [subDef] is default.<br>
  17879. SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = B0|BI|released<br>
  17880. Used with switch type "channel". Set attr switchType to channel.<br>
  17881. subDefB is supported for switches.<br>
  17882. Second action is not sent.<br>
  17883. If [subDefB] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
  17884. displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
  17885. </li>
  17886. <li><a name="subDefC">subDefC</a> &lt;EnOcean SenderID&gt;,
  17887. [subDefC] = [subDef] is default.<br>
  17888. SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = C0|CI|released<br>
  17889. Used with switch type "channel". Set attr switchType to channel.<br>
  17890. subDefC is supported for switches.<br>
  17891. Second action is not sent.<br>
  17892. If [subDefC] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
  17893. displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
  17894. </li>
  17895. <li><a name="subDefD">subDefD</a> &lt;EnOcean SenderID&gt;,
  17896. [subDefD] = [subDef] is default.<br>
  17897. SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = D0|DI|released<br>
  17898. Used with switch type "channel". Set attr switchType to channel.<br>
  17899. subDefD is supported for switches.<br>
  17900. Second action is not sent.<br>
  17901. If [subDefD] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
  17902. displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
  17903. </li>
  17904. <li><a name="subDef0">subDef0</a> &lt;EnOcean SenderID&gt;,
  17905. [subDef0] = [subDef] is default.<br>
  17906. SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = A0|B0|C0|D0|released<br>
  17907. Used with switch type "central". Set attr switchType to central.<br>
  17908. Use the sensor type "zentral aus/ein" for Eltako devices.<br>
  17909. subDef0 is supported for switches.<br>
  17910. Second action is not sent.<br>
  17911. If [subDef0] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
  17912. displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
  17913. </li>
  17914. <li><a name="subDefI">subDefI</a> &lt;EnOcean SenderID&gt;,
  17915. [subDefI] = [subDef] is default.<br>
  17916. SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = AI|BI|CI|DI<br>
  17917. Used with switch type "central". Set attr switchType to central.<br>
  17918. Use the sensor type "zentral aus/ein" for Eltako devices.<br>
  17919. subDefI is supported for switches.<br>
  17920. Second action is not sent.<br>
  17921. If [subDefI] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
  17922. displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
  17923. </li>
  17924. <li><a name="EnOcean_subDefH">subDefH</a> &lt;EnOcean SenderID&gt;,
  17925. [subDefH] = undef is default.<br>
  17926. SenderID (<a href="#TCM">TCM</a> BaseID + offset)<br>
  17927. Used with subType "multisensor.00". If the attribute subDefH is set, the position of the window handle as EEP F6-10-00
  17928. (windowHandle) telegram is forwarded.<br>
  17929. If [subDefH] = getNextID FHEM can assign a free SenderID alternatively.
  17930. </li>
  17931. <li><a name="EnOcean_subDefW">subDefW</a> &lt;EnOcean SenderID&gt;,
  17932. [subDefW] = undef is default.<br>
  17933. SenderID (<a href="#TCM">TCM</a> BaseID + offset)<br>
  17934. Used with subType "multisensor.00". If the attribute subDefW is set, the window state as EEP D5-00-01
  17935. (contact) telegram is forwarded.<br>
  17936. If [subDefW] = getNextID FHEM can assign a free SenderID alternatively.
  17937. </li>
  17938. <li><a href="#subType">subType</a></li>
  17939. <li><a name="subTypeSet">subTypeSet</a> &lt;type of device&gt;, [subTypeSet] = [subType] is default.<br>
  17940. Type of device (EEP Profile) used for sending commands. Set the Attribute manually.
  17941. The profile has to fit their basic profile. More information can be found in the basic profiles.
  17942. </li>
  17943. <li><a name="EnOcean_summerMode">summerMode</a> off|on,
  17944. [summerMode] = off is default.<br>
  17945. Put Battery Powered Actuator (hvac.01) or Heating Radiator Actuating Drive (hvac.04) in summer operation
  17946. to reduce energy consumption. If [summerMode] = on, the set commands are not executed.
  17947. </li>
  17948. <li><a name="EnOcean_switchHysteresis">switchHysteresis</a> &lt;value&gt;,
  17949. [switchHysteresis] = 1 is default.<br>
  17950. Switch Hysteresis
  17951. </li>
  17952. <li><a name="switchMode">switchMode</a> switch|pushbutton,
  17953. [switchMode] = switch is default.<br>
  17954. The set command "released" immediately after &lt;value&gt; is sent if the
  17955. attribute is set to "pushbutton".
  17956. </li>
  17957. <li><a name="switchType">switchType</a> direction|universal|central|channel,
  17958. [switchType] = direction is default.<br>
  17959. EnOcean Devices support different types of sensors, e. g. direction
  17960. switch, universal switch or pushbutton, central on/off.<br>
  17961. For Eltako devices these are the sensor types "Richtungstaster",
  17962. "Universalschalter" or "Universaltaster", "Zentral aus/ein".<br>
  17963. With the sensor type <code>direction</code> switch on/off commands are
  17964. accepted, e. g. B0, BI, released. Fhem can control an device with this
  17965. sensor type unique. This is the default function and should be
  17966. preferred.<br>
  17967. Some devices only support the <code>universal switch
  17968. </code> or <code>pushbutton</code>. With a Fhem command, for example,
  17969. B0 or BI is switched between two states. In this case Fhem cannot
  17970. control this device unique. But if the Attribute <code>switchType
  17971. </code> is set to <code>universal</code> Fhem synchronized with
  17972. a bidirectional device and normal on/off commands can be used.
  17973. If the bidirectional device response with the channel B
  17974. confirmation telegrams also B0 and BI commands are to be sent,
  17975. e g. channel A with A0 and AI. Also note that confirmation telegrams
  17976. needs to be sent.<br>
  17977. Partly for the switchType <code>central</code> two different SenderID
  17978. are required. In this case set the Attribute <code>switchType</code> to
  17979. <code>central</code> and define the Attributes
  17980. <a href="#subDef0">subDef0</a> and <a href="#subDefI">subDefI</a>.<br>
  17981. Furthermore, SenderIDs can be used depending on the channel A, B, C or D.
  17982. In this case set the Attribute switchType to <code>channel</code> and define
  17983. the Attributes <a href="#subDefA">subDefA</a>, <a href="#subDefB">subDefB</a>,
  17984. <a href="#subDefC">subDefC</a>, or <a href="#subDefD">subDefD</a>.
  17985. </li>
  17986. <li><a name="temperatureRefDev">temperatureRefDev</a> &lt;name&gt;<br>
  17987. Name of the device whose reference value is read. The reference values is
  17988. the reading temperature.
  17989. </li>
  17990. <li><a name="EnOcean_temperatureScale">temperatureScale</a> F|C|default|no_change, [temperatureScale] = no_change is default.<br>
  17991. temperatureScale is supported for roomCtrlPanel.00.
  17992. </li>
  17993. <li><a name="EnOcean_timeNotation">timeNotation</a> 12|24|default|no_change, [timeNotation] = no_change is default.<br>
  17994. timeNotation is supported for roomCtrlPanel.00.
  17995. </li>
  17996. <li><a name="EnOcean_timeProgram[1-4]">timeProgram[1-4]</a> &lt;period&gt; &lt;starttime&gt; &lt;endtime&gt; &lt;roomCtrlMode&gt;, [timeProgam[1-4]] = &lt;none&gt; is default.<br>
  17997. [period] = FrMo|FrSu|ThFr|WeFr|TuTh|MoWe|SaSu|MoFr|MoSu|Su|Sa|Fr|Th|We|Tu|Mo<br>
  17998. [starttime] = [00..23]:[00|15|30|45]<br>
  17999. [endtime] = [00..23]:[00|15|30|45]<br>
  18000. [roomCtrlMode] = buildingProtection|comfort|economy|preComfort<br>
  18001. The Room Control Panel Kieback & Peter RBW322-FTL supports only [roomCtrlMode] = comfort.<br>
  18002. timeProgram is supported for roomCtrlPanel.00.
  18003. </li>
  18004. <li><a name="EnOcean_trackerWakeUpCycle">trackerWakeUpCycle</a> t/s, [wakeUpCycle] =10 s, 20 s, 30 s, 40 s, 60 s, 120 s, 180 s, 240 s, 3600, 86400 s, 30 s is default.<br>
  18005. Transmission cycle of the tracker.
  18006. </li>
  18007. <li><a name="EnOcean_updateState">updateState</a> default|yes|no, [updateState] = default is default.<br>
  18008. update reading state after set commands
  18009. </li>
  18010. <li><a name="EnOcean_uteResponseRequest">uteResponseRequest</a> yes|no<br>
  18011. request UTE teach-in/teach-out response message, the standard value depends on the EEP profil
  18012. </li>
  18013. <li><a href="#verbose">verbose</a></li>
  18014. <li><a name="EnOcean_wakeUpCycle">wakeUpCycle</a> t/s, [wakeUpCycle] = 10 s ... 151200 s, 300 s is default.<br>
  18015. Transmission cycle of the actuator.
  18016. </li>
  18017. <li><a href="#webCmd">webCmd</a></li>
  18018. </ul>
  18019. </ul>
  18020. <br>
  18021. <a name="EnOceanevents"></a>
  18022. <b>Generated events</b>
  18023. <ul>
  18024. <ul>
  18025. <li><a name="EnOcean_remoteEvents">Remote Management</a><br>
  18026. <ul>
  18027. <li>remoteDevCfg&lt;0000...FFFF&gt;: &lt;device config&gt;</li>
  18028. <li>remoteFunction&lt;01...99&gt;: &lt;remote function number&gt;:&lt;remote manufacturer ID&gt;:&lt;explanation&gt;</li>
  18029. <li>remoteLastFunctionNumber: 001...FFF</li>
  18030. <li>remoteLastStatusReturnCode: 00...FF</li>
  18031. <li>remoteLearn: not_supported|supported</li>
  18032. <li>remoteLinkCfg&lt;in|out&gt;&lt;00...FF&gt;: &lt;data index&gt;:&lt;device config&gt;</li>
  18033. <li>remoteLinkTableDesc&lt;in|out&gt;&lt;00...FF&gt;: &lt;DeviceID&gt;:&lt;EEP&gt;:&lt;channel&gt;</li>
  18034. <li>remoteLinkTableGPDesc&lt;in|out&gt;&lt;00...FF&gt;: &lt;name of channel 00&gt;:&lt;O|I&gt;:&lt;channel type&gt;:&lt;signal type&gt;:&lt;value type&gt;[:&lt;resolution&gt;[:&lt;engineering min&gt;:&lt;scaling min&gt;:&lt;engineering max&gt;:&lt;scaling max&gt;]]</li>
  18035. <li>remoteProductID: 00000000...FFFFFFFF</li>
  18036. <li>remoteRepeaterFilter: AND|OR</li>
  18037. <li>remoteRepeaterFunction: on|off|filter</li>
  18038. <li>remoteRepeaterLevel: 1|2</li>
  18039. <li>remoteTeach: not_supported|supported</li>
  18040. <li>remoteRSSI: LP/dBm</li>
  18041. <li>teach: &lt;result of teach procedure&gt;</li>
  18042. </ul>
  18043. </li>
  18044. <br><br>
  18045. <li><a name="EnOcean_signalEvents">Signal Telegram</a><br>
  18046. <ul>
  18047. <li>harvester: very_good|good|average|bad|very_bad</li>
  18048. <li>hwVersion: 00000000...FFFFFFFF</li>
  18049. <li>trigger: heartbeat</li>
  18050. <li>smartAckMailbox: empty|not_exists|reset</li>
  18051. <li>swVersion: 00000000...FFFFFFFF</li>
  18052. </ul>
  18053. </li>
  18054. <br><br>
  18055. <li>Switch (EEP F6-02-01 ... F6-03-02)<br>
  18056. <ul>
  18057. <li>A0</li>
  18058. <li>AI</li>
  18059. <li>B0</li>
  18060. <li>BI</li>
  18061. <li>C0</li>
  18062. <li>CI</li>
  18063. <li>D0</li>
  18064. <li>DI</li>
  18065. <li>&lt;BtnX,BtnY&gt; First and second action where BtnX and BtnY is
  18066. one of the above, e.g. A0 BI or D0 CI</li>
  18067. <li>buttons: pressed|released</li>
  18068. <li>state: &lt;BtnX&gt;[,&lt;BtnY&gt;]</li>
  18069. </ul><br>
  18070. Switches (remote controls) or actors with more than one
  18071. (pair) keys may have multiple channels e. g. B0/BI, A0/AI with one
  18072. SenderID or with separate addresses.
  18073. </li>
  18074. <br><br>
  18075. <li>Pushbutton Switch, Pushbutton Input Module (EEP F6-02-01 ... F6-02-02, F6-01-01)<br>
  18076. [Eltako FT55, FSM12, FSM61, FTS12]<br>
  18077. <ul>
  18078. <li>A0</li>
  18079. <li>AI</li>
  18080. <li>B0</li>
  18081. <li>BI</li>
  18082. <li>C0</li>
  18083. <li>CI</li>
  18084. <li>D0</li>
  18085. <li>DI</li>
  18086. <li>&lt;BtnX,BtnY&gt; First and second action where BtnX and BtnY is
  18087. one of the above, e.g. A0,BI or D0,CI</li>
  18088. <li>released</li>
  18089. <li>buttons: pressed|released</li>
  18090. <li>state: &lt;BtnX&gt;[,&lt;BtnY&gt;] [released]</li>
  18091. </ul><br>
  18092. The status of the device may become "released", this is not the case for a normal switch.<br>
  18093. Set attr model to Eltako_FT55|FSM12|FSM61|FTS12 or attr sensorMode to pushbutton manually.
  18094. </li>
  18095. <br><br>
  18096. <li>Pushbutton Switch (EEP F6-3F-7F)<br>
  18097. [Eltako FGW14/FAM14 with internal decryption and RS-485 communication]<br>
  18098. <ul>
  18099. <li>A0</li>
  18100. <li>AI</li>
  18101. <li>B0</li>
  18102. <li>BI</li>
  18103. <li>C0</li>
  18104. <li>CI</li>
  18105. <li>D0</li>
  18106. <li>DI</li>
  18107. <li>&lt;BtnX,BtnY&gt; First and second action where BtnX and BtnY is
  18108. one of the above, e.g. A0,BI or D0,CI</li>
  18109. <li>released</li>
  18110. <li>buttons: pressed|released</li>
  18111. <li>state: &lt;BtnX&gt;[,&lt;BtnY&gt;] [released]</li>
  18112. </ul><br>
  18113. Set attr subType to switch.7F and manufID to 00D.<br>
  18114. The status of the device may become "released", this is not the case for
  18115. a normal switch. Set attr sensorMode to pushbutton manually.
  18116. </li>
  18117. <br><br>
  18118. <li>Pushbutton Switch (EEP D2-03-00)<br>
  18119. [EnOcean PTM 215 Modul]<br>
  18120. <ul>
  18121. <li>A0</li>
  18122. <li>AI</li>
  18123. <li>B0</li>
  18124. <li>BI</li>
  18125. <li>&lt;BtnX,BtnY&gt; First and second action where BtnX and BtnY is
  18126. one of the above, e.g. A0,BI</li>
  18127. <li>pressed</li>
  18128. <li>released</li>
  18129. <li>teach: &lt;result of teach procedure&gt;</li>
  18130. <li>energyBow: pressed|released</li>
  18131. <li>state: &lt;BtnX&gt;|&lt;BtnX&gt;,&lt;BtnY&gt;|released|pressed|teachIn|teachOut</li>
  18132. </ul><br>
  18133. The attr subType must be switch.00. This is done if the device was
  18134. created by autocreate. Set attr sensorMode to pushbutton manually if needed.
  18135. </li>
  18136. <br><br>
  18137. <li>Pushbutton Switch (EEP D2-03-0A)<br>
  18138. [Nodon Soft Button]<br>
  18139. <ul>
  18140. <li>on</li>
  18141. <li>off</li>
  18142. <li>batteryPrecent: r/% (Sensor Range: r = 1 % ... 100 %)</li>
  18143. <li>buttonD: on|off</li>
  18144. <li>buttonL: on|off</li>
  18145. <li>buttonS: on|off</li>
  18146. <li>state: on|off</li>
  18147. </ul><br>
  18148. The attr subType must be switch.0A. This is done if the device was
  18149. created by autocreate.
  18150. </li>
  18151. <br><br>
  18152. <li>Heating/Cooling Relay (EEP F6-02-01 ... F6-02-02)<br>
  18153. [Eltako FAE14, FHK14, untested]<br>
  18154. <ul>
  18155. <li>controllerMode: auto|off</li>
  18156. <li>energyHoldOff: normal|holdoff</li>
  18157. <li>buttons: pressed|released</li>
  18158. </ul><br>
  18159. Set attr subType to switch and model to Eltako_FAE14|FHK14 manually. In addition
  18160. every telegram received from a teached-in temperature sensor (e.g. FTR55H)
  18161. is repeated as a confirmation telegram from the Heating/Cooling Relay
  18162. FAE14, FHK14. In this case set attr subType to e. g. roomSensorControl.05
  18163. and attr manufID to 00D.
  18164. </li>
  18165. <br><br>
  18166. <li>Key Card Activated Switch (EEP F6-04-01)<br>
  18167. [Eltako FKC, FKF, FZS, untested]<br>
  18168. <ul>
  18169. <li>keycard_inserted</li>
  18170. <li>keycard_removed</li>
  18171. <li>state: keycard_inserted|keycard_removed</li>
  18172. </ul><br>
  18173. Set attr subType to keycard manually.
  18174. </li>
  18175. <br><br>
  18176. <li>Wind Speed Threshold Detector (EEP F6-05-00)<br>
  18177. <ul>
  18178. <li>on</li>
  18179. <li>off</li>
  18180. <li>alarm: dead_sensor|off</li>
  18181. <li>windSpeed: dead_sensor|on|off</li>
  18182. <li>battery: low|ok</li>
  18183. <li>state: on|off</li>
  18184. </ul><br>
  18185. Set attr subType to windSpeed.00 manually.
  18186. </li>
  18187. <br><br>
  18188. <li>Liquid Leakage Sensor (EEP F6-05-01)<br>
  18189. [untested]<br>
  18190. <ul>
  18191. <li>dry</li>
  18192. <li>wet</li>
  18193. <li>state: dry|wet</li>
  18194. </ul><br>
  18195. Set attr subType to liquidLeakage manually.
  18196. </li>
  18197. <br><br>
  18198. <li>Smoke Detector (EEP F6-05-02)<br>
  18199. [Eltako FRW]<br>
  18200. <ul>
  18201. <li>smoke-alarm</li>
  18202. <li>off</li>
  18203. <li>alarm: dead_sensor|smoke-alarm|off</li>
  18204. <li>battery: low|ok</li>
  18205. <li>state: smoke-alarm|off</li>
  18206. </ul><br>
  18207. Set attr subType to smokeDetector.02 manually.
  18208. </li>
  18209. <br><br>
  18210. <li>Window Handle (EEP F6-10-00, D2-03-10)<br>
  18211. [HOPPE SecuSignal, Eltako FHF, Eltako FTKE]<br>
  18212. <ul>
  18213. <li>closed</li>
  18214. <li>open</li>
  18215. <li>tilted</li>
  18216. <li>open_from_tilted</li>
  18217. <li>state: closed|open|tilted|open_from_tilted</li>
  18218. </ul><br>
  18219. The device windowHandle or windowHandle.10 should be created by autocreate.
  18220. </li>
  18221. <br><br>
  18222. <li>Single Input Contact, Door/Window Contact<br>
  18223. 1BS Telegram (EEP D5-00-01)<br>
  18224. [EnOcean EMCS, STM 320, STM 329, STM 250, Eltako FTK, Peha D 450 FU]
  18225. <ul>
  18226. <li>closed</li>
  18227. <li>open</li>
  18228. <li>alarm: dead_sensor</li>
  18229. <li>teach: &lt;result of teach procedure&gt;</li>
  18230. <li>state: open|closed</li>
  18231. </ul></li>
  18232. The device should be created by autocreate. A monitoring period can be set for signOfLife telegrams of the sensor, see
  18233. <a href="#EnOcean_signOfLife">signOfLife</a> and <a href="#EnOcean_signOfLifeInterval">signOfLifeInterval</a>.
  18234. Default is "off" and an interval of 1310 sec.
  18235. <br><br>
  18236. <li>Temperature Sensors with with different ranges (EEP A5-02-01 ... A5-02-30)<br>
  18237. [EnOcean STM 330, Eltako FTF55, Thermokon SR65 ...]<br>
  18238. <ul>
  18239. <li>t/&#176C</li>
  18240. <li>temperature: t/&#176C (Sensor Range: t = &lt;t min&gt; &#176C ... &lt;t max&gt; &#176C)</li>
  18241. <li>state: t/&#176C</li>
  18242. </ul><br>
  18243. The attr subType must be tempSensor.01 ... tempSensor.30. This is done if the device was
  18244. created by autocreate.
  18245. </li>
  18246. <br><br>
  18247. <li>Temperatur and Humidity Sensor (EEP A5-04-02)<br>
  18248. [Eltako FAFT60, FIFT63AP]<br>
  18249. <ul>
  18250. <li>T: t/&#176C H: rH/% B: unknown|low|ok</li>
  18251. <li>battery: unknown|low|ok</li>
  18252. <li>energyStorage: unknown|empty|charged|full</li>
  18253. <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
  18254. <li>temperature: t/&#176C (Sensor Range: t = -20 &#176C ... 60 &#176C)</li>
  18255. <li>voltage: U/V</li> (Sensor Range: U = 0 V ... 6.6 V)
  18256. <li>state: T: t/&#176C H: rH/% B: unknown|low|ok</li>
  18257. </ul><br>
  18258. The attr subType must be tempHumiSensor.02 and attr
  18259. manufID must be 00D for Eltako Devices. This is done if the device was
  18260. created by autocreate.
  18261. </li>
  18262. <br><br>
  18263. <li>Temperatur and Humidity Sensor (EEP A5-04-03)<br>
  18264. [untested]<br>
  18265. <ul>
  18266. <li>T: t/&#176C H: rH/%</li>
  18267. <li>alarm: dead_sensor</li>
  18268. <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
  18269. <li>telegramType: heartbeat|event</li>
  18270. <li>temperature: t/&#176C (Sensor Range: t = -20 &#176C ... 60 &#176C)</li>
  18271. <li>state: T: t/&#176C H: rH/%</li>
  18272. </ul><br>
  18273. The attr subType must be tempHumiSensor.03. This is done if the device was
  18274. created by autocreate.<br>
  18275. A monitoring period can be set for signOfLife telegrams of the sensor, see
  18276. <a href="#EnOcean_signOfLife">signOfLife</a> and <a href="#EnOcean_signOfLifeInterval">signOfLifeInterval</a>.
  18277. Default is "off" and an interval of 1540 sec.
  18278. </li>
  18279. <br><br>
  18280. <li>Barometric Sensor (EEP A5-05-01)<br>
  18281. [untested]<br>
  18282. <ul>
  18283. <li>P/hPa</li>
  18284. <li>airPressure: P/hPa (Sensor Range: P = 500 hPa ... 1150 hPa</li>
  18285. <li>telegramType: heartbeat|event</li>
  18286. <li>state: P/hPa</li>
  18287. </ul><br>
  18288. The attr subType must be baroSensor.01. This is done if the device was
  18289. created by autocreate.
  18290. </li>
  18291. <br><br>
  18292. <li>Light Sensor (EEP A5-06-01)<br>
  18293. [Eltako FAH60, FAH63, FIH63, Thermokon SR65 LI]<br>
  18294. <ul>
  18295. <li>E/lx</li>
  18296. <li>brightness: E/lx (Sensor Range: 300 lx ... 30 klx, 600 lx ... 60 klx
  18297. , Sensor Range for Eltako: E = 0 lx ... 100 lx, 300 lx ... 30 klx)</li>
  18298. <li>voltage: U/V</li> (Sensor Range: U = 0 V ... 5.1 V)
  18299. <li>state: E/lx</li>
  18300. </ul><br>
  18301. Eltako devices only support Brightness.<br>
  18302. The attr subType must be lightSensor.01 and attr manufID must be 00D
  18303. for Eltako Devices. This is done if the device was created by
  18304. autocreate.
  18305. </li>
  18306. <br><br>
  18307. <li>Light Sensor (EEP A5-06-02)<br>
  18308. [untested]<br>
  18309. <ul>
  18310. <li>E/lx</li>
  18311. <li>brightness: E/lx (Sensor Range: 0 lx ... 1020 lx</li>
  18312. <li>voltage: U/V (Sensor Range: U = 0 V ... 5.1 V)</li>
  18313. <li>state: E/lx</li>
  18314. </ul><br>
  18315. The attr subType must be lightSensor.02. This is done if the device was
  18316. created by autocreate.
  18317. </li>
  18318. <br><br>
  18319. <li>Light Sensor (EEP A5-06-03)<br>
  18320. [untested]<br>
  18321. <ul>
  18322. <li>E/lx</li>
  18323. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
  18324. <li>errorCode: 251 ... 255</li>
  18325. <li>state: E/lx</li>
  18326. </ul><br>
  18327. The attr subType must be lightSensor.03. This is done if the device was
  18328. created by autocreate.
  18329. </li>
  18330. <br><br>
  18331. <li>Light Sensor (EEP A5-06-04)<br>
  18332. [untested]<br>
  18333. <ul>
  18334. <li>T: t/&#176C E: E/lx B: ok|low</li>
  18335. <li>battery: ok|low</li>
  18336. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 65535 lx)</li>
  18337. <li>energyStorage: 1/%</li>
  18338. <li>temperature: t/&#176C (Sensor Range: t = -20 &#176C ... 60 &#176C)</li>
  18339. <li>state: T: t/&#176C E: E/lx B: ok|low</li>
  18340. </ul><br>
  18341. The attr subType must be lightSensor.04. This is done if the device was
  18342. created by autocreate.
  18343. </li>
  18344. <br><br>
  18345. <li>Light Sensor (EEP A5-06-05)<br>
  18346. [untested]<br>
  18347. <ul>
  18348. <li>E/lx</li>
  18349. <li>brightness: E/lx (Sensor Range: 0 lx ... 10200 lx</li>
  18350. <li>voltage: U/V (Sensor Range: U = 0 V ... 5.1 V)</li>
  18351. <li>state: E/lx</li>
  18352. </ul><br>
  18353. The attr subType must be lightSensor.05. This is done if the device was created by autocreate.
  18354. </li>
  18355. <br><br>
  18356. <li>Occupancy Sensor (EEP A5-07-01, A5-07-02)<br>
  18357. [EnOcean EOSW]<br>
  18358. <ul>
  18359. <li>on|off</li>
  18360. <li>battery: ok|low</li>
  18361. <li>button: pressed|released</li>
  18362. <li>current: I/&#181;A (Sensor Range: I = 0 V ... 127.0 &#181;A)</li>
  18363. <li>errorCode: 251 ... 255</li>
  18364. <li>motion: on|off</li>
  18365. <li>sensorType: ceiling|wall</li>
  18366. <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
  18367. <li>state: on|off</li>
  18368. </ul><br>
  18369. The attr subType must be occupSensor.<01|02>. This is done if the device was
  18370. created by autocreate. Current is the solar panel current. Some values are
  18371. displayed only for certain types of devices.
  18372. </li>
  18373. <br><br>
  18374. <li>Eltako/PioTek-Tracker TF-TTB (EEP A5-07-01)<br>
  18375. <ul>
  18376. <li>on|off</li>
  18377. <li>battery: ok|low</li>
  18378. <li>button: pressed|released</li>
  18379. <li>motion: on|off</li>
  18380. <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
  18381. <li>state: on|off</li>
  18382. </ul><br>
  18383. The attr subType must be occupSensor.01. This is done if the device was
  18384. created by autocreate. The attr model has to be set manually to tracker.
  18385. Alternatively, the profile will be defined with inofficial EEP G5-07-01.<br>
  18386. The transmission cycle is set using the attribute <a href="#EnOcean_trackerWakeUpCycle">trackerWakeUpCycle</a>.
  18387. </li>
  18388. <br><br>
  18389. <li>Occupancy Sensor (EEP A5-07-03)<br>
  18390. [untested]<br>
  18391. <ul>
  18392. <li>M: on|off E: E/lx U: U/V</li>
  18393. <li>battery: ok|low</li>
  18394. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
  18395. <li>errorCode: 251 ... 255</li>
  18396. <li>motion: on|off</li>
  18397. <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
  18398. <li>state: M: on|off E: E/lx U: U/V</li>
  18399. </ul><br>
  18400. The attr subType must be occupSensor.03. This is done if the device was
  18401. created by autocreate.
  18402. </li>
  18403. <br><br>
  18404. <li>Light, Temperatur and Occupancy Sensor (EEP A5-08-01 ... A5-08-03)<br>
  18405. [Eltako FABH63, FBH55, FBH63, FIBH63, Thermokon SR-MDS, PEHA 482 FU-BM DE]<br>
  18406. <ul>
  18407. <li>M: on|off E: E/lx P: absent|present T: t/&#176C U: U/V</li>
  18408. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 510, 1020, 1530 or 2048 lx)</li>
  18409. <li>motion: on|off</li>
  18410. <li>presence: absent|present</li>
  18411. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 51 &#176C or -30 &#176C ... 50 &#176C)</li>
  18412. <li>voltage: U/V</li> (Sensor Range: U = 0 V ... 5.1 V)
  18413. <li>state: M: on|off E: E/lx P: absent|present T: t/&#176C U: U/V</li>
  18414. </ul><br>
  18415. Eltako and PEHA devices only support Brightness and Motion.<br>
  18416. The attr subType must be lightTempOccupSensor.<01|02|03> and attr
  18417. manufID must be 00D for Eltako Devices. This is done if the device was
  18418. created by autocreate.
  18419. </li>
  18420. <br><br>
  18421. <li>Gas Sensor, CO Sensor (EEP A5-09-01)<br>
  18422. [untested]<br>
  18423. <ul>
  18424. <li>CO: c/ppm (Sensor Range: c = 0 ppm ... 255 ppm)</li>
  18425. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 255 &#176C)</li>
  18426. <li>state: c/ppm</li>
  18427. </ul><br>
  18428. The attr subType must be COSensor.01. This is done if the device was created by autocreate.
  18429. </li>
  18430. <br><br>
  18431. <li>Gas Sensor, CO Sensor (EEP A5-09-02)<br>
  18432. [untested]<br>
  18433. <ul>
  18434. <li>CO: c/ppm (Sensor Range: c = 0 ppm ... 1020 ppm)</li>
  18435. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 51.0 &#176C)</li>
  18436. <li>voltage: U/V</li> (Sensor Range: U = 0 V ... 5.1 V)
  18437. <li>state: c/ppm</li>
  18438. </ul><br>
  18439. The attr subType must be COSensor.02. This is done if the device was
  18440. created by autocreate.
  18441. </li>
  18442. <br><br>
  18443. <li>Gas Sensor, CO2 Sensor (EEP A5-09-04)<br>
  18444. [Thermokon SR04 CO2 *, Eltako FCOTF63, untested]<br>
  18445. <ul>
  18446. <li>airQuality: high|mean|moderate|low (Air Quality Classes DIN EN 13779)</li>
  18447. <li>CO2: c/ppm (Sensor Range: c = 0 ppm ... 2550 ppm)</li>
  18448. <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
  18449. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 51 &#176C)</li>
  18450. <li>state: T: t/&#176C H: rH/% CO2: c/ppm AQ: high|mean|moderate|low</li>
  18451. </ul><br>
  18452. The attr subType must be tempHumiCO2Sensor.01. This is done if the device was
  18453. created by autocreate.
  18454. </li>
  18455. <br><br>
  18456. <li>Gas Sensor, Volatile organic compounds (VOC) Sensor (EEP A5-09-05, A5-09-0C)<br>
  18457. [untested]<br>
  18458. <ul>
  18459. <li>concentration: c/[unit] (Sensor Range: c = 0 ... 655350</li>
  18460. <li>concentrationUnit: ppb|&mu;/m3</li>
  18461. <li>vocName: Name of last measured VOC</li>
  18462. <li>state: c/[unit]</li>
  18463. </ul><br>
  18464. The attr subType must be vocSensor.01. This is done if the device was
  18465. created by autocreate.
  18466. </li>
  18467. <br><br>
  18468. <li>Gas Sensor, Radon Sensor (EEP A5-09-06)<br>
  18469. [untested]<br>
  18470. <ul>
  18471. <li>Rn: A m3/Bq (Sensor Range: A = 0 Bq/m3 ... 1023 Bq/m3)</li>
  18472. <li>state: A m3/Bq</li>
  18473. </ul><br>
  18474. The attr subType must be radonSensor.01. This is done if the device was
  18475. created by autocreate.
  18476. </li>
  18477. <br><br>
  18478. <li>Gas Sensor, Particles Sensor (EEP A5-09-07)<br>
  18479. [untested]<br>
  18480. Three channels with particle sizes of up to 10 &mu;m, 2.5 &mu;m and 1 &mu;m are supported<br>.
  18481. <ul>
  18482. <li>particles_10: p m3/&mu;g | inactive (Sensor Range: p = 0 &mu;g/m3 ... 511 &mu;g/m3)</li>
  18483. <li>particles_2_5: p m3/&mu;g | inactive (Sensor Range: p = 0 &mu;g/m3 ... 511 &mu;g/m3)</li>
  18484. <li>particles_1: p m3/&mu;g | inactive (Sensor Range: p = 0 &mu;g/m3 ... 511 &mu;g/m3)</li>
  18485. <li>state: PM10: p m3/&mu;g PM2_5: p m3/&mu;g PM1: p m3/&mu;g</li>
  18486. </ul><br>
  18487. The attr subType must be particlesSensor.01. This is done if the device was
  18488. created by autocreate.
  18489. </li>
  18490. <br><br>
  18491. <li>CO2 Sensor (EEP A5-09-08, A5-09-09)<br>
  18492. [untested]<br>
  18493. <ul>
  18494. <li>CO2: c/ppm (Sensor Range: c = 0 ppm ... 2000 ppm)</li>
  18495. <li>powerFailureDetection: detected|not_detected</li>
  18496. <li>state: c/ppm</li>
  18497. </ul><br>
  18498. The attr subType must be CO2Sensor.01. This is done if the device was
  18499. created by autocreate.
  18500. </li>
  18501. <br><br>
  18502. <li>H Sensor (EEP A5-09-0A)<br>
  18503. [untested]<br>
  18504. <ul>
  18505. <li>c/ppm</li>
  18506. <li>voltage: U/V (Sensor Range: U = 2 V ... 5 V)</li>
  18507. <li>H: c/ppm (Sensor Range: c = 0 ppm ... 2000 ppm)</li>
  18508. <li>temperature: t/&#176C (Sensor Range: t = -20 &#176C ... 60 &#176C)</li>
  18509. <li>state: c/ppm</li>
  18510. </ul><br>
  18511. The attr subType must be HSensor.01. This is done if the device was
  18512. created by autocreate.
  18513. </li>
  18514. <br><br>
  18515. <li>Radiation Sensor (EEP A5-09-0B)<br>
  18516. [untested]<br>
  18517. <ul>
  18518. <li>1/[unit]</li>
  18519. <li>radioactivity: 1/[unit] (Sensor Range: c = 0 [unit] ... 65535 [unit])</li>
  18520. <li>unit: uSv/h|cpm|Bq/L|Bq/kg</li>
  18521. <li>voltage: U/V (Sensor Range: U = 2 V ... 5 V)</li>
  18522. <li>state: 1/[unit]</li>
  18523. </ul><br>
  18524. The attr subType must be radiationSensor.01. This is done if the device was
  18525. created by autocreate.
  18526. </li>
  18527. <br><br>
  18528. <li>Room Sensor and Control Unit (EEP A5-10-01 ... A5-10-0D)<br>
  18529. [Eltako FTR55*, Thermokon SR04 *, Thanos SR *]<br>
  18530. <ul>
  18531. <li>T: t/&#176C SP: 0 ... 255 F: 0|1|2|3|auto SW: 0|1</li>
  18532. <li>fanStage: 0|1|2|3|auto</li>
  18533. <li>switch: on|off</li>
  18534. <li>setpoint: 0 ... 255</li>
  18535. <li>setpointScaled: &lt;floating-point number&gt;</li>
  18536. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18537. <li>state: T: t/&#176C SP: 0 ... 255 F: 0|1|2|3|auto SW: on|off</li><br>
  18538. Alternatively for Eltako devices
  18539. <li>T: t/&#176C SPT: t/&#176C NR: t/&#176C</li>
  18540. <li>block: lock|unlock</li>
  18541. <li>nightReduction: t/K</li>
  18542. <li>setpointTemp: t/&#176C</li>
  18543. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18544. <li>state: T: t/&#176C SPT: t/&#176C NR: t/K</li><br>
  18545. </ul><br>
  18546. The scaling of the setpoint adjustment is device- and vendor-specific. Set the
  18547. attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
  18548. <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
  18549. setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
  18550. adjust the scaling alternatively.<br>
  18551. The attr subType must be roomSensorControl.05 and attr
  18552. manufID must be 00D for Eltako Devices. This is done if the device was
  18553. created by autocreate.
  18554. </li>
  18555. <br><br>
  18556. <li>Room Sensor and Control Unit (EEP A5-04-01, A5-10-10 ... A5-10-14)<br>
  18557. [Eltako FUTH65D, Thermokon SR04 * rH, Thanos SR *]<br>
  18558. <ul>
  18559. <li>T: t/&#176C H: rH/% SP: 0 ... 255 SW: 0|1</li>
  18560. <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
  18561. <li>switch: 0|1</li>
  18562. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18563. <li>setpoint: 0 ... 255</li>
  18564. <li>setpointScaled: &lt;floating-point number&gt;</li>
  18565. <li>state: T: t/&#176C H: rH/% SP: 0 ... 255 SW: 0|1</li>
  18566. </ul><br>
  18567. The scaling of the setpoint adjustment is device- and vendor-specific. Set the
  18568. attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
  18569. <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
  18570. setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
  18571. adjust the scaling alternatively.<br>
  18572. The attr subType must be roomSensorControl.01 and attr
  18573. manufID must be 00D for Eltako Devices. This is
  18574. done if the device was created by autocreate.
  18575. </li>
  18576. <br><br>
  18577. <li>Room Sensor and Control Unit (EEP A5-10-15 ... A5-10-17)<br>
  18578. [untested]<br>
  18579. <ul>
  18580. <li>T: t/&#176C SP: 0 ... 63 P: absent|present</li>
  18581. <li>presence: absent|present</li>
  18582. <li>temperature: t/&#176C (Sensor Range: t = -10 &#176C ... 41.2 &#176C)</li>
  18583. <li>setpoint: 0 ... 63</li>
  18584. <li>setpointScaled: &lt;floating-point number&gt;</li>
  18585. <li>state: T: t/&#176C SP: 0 ... 63 P: absent|present</li>
  18586. </ul><br>
  18587. The scaling of the setpoint adjustment is device- and vendor-specific. Set the
  18588. attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
  18589. <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
  18590. setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
  18591. adjust the scaling alternatively.<br>
  18592. The attr subType must be roomSensorControl.02. This is done if the device was
  18593. created by autocreate.
  18594. </li>
  18595. <br><br>
  18596. <li>Room Sensor and Control Unit (EEP A5-10-18)<br>
  18597. [untested]<br>
  18598. <ul>
  18599. <li>T: t/&#176C B: E/lx F: 0|1|2|3|4|5|auto|off SP: t/&#176C P: absent|present|disabled</li>
  18600. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
  18601. <li>fan: 0|1|2|3|4|5|auto|off</li>
  18602. <li>presence: absent|present|disabled</li>
  18603. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18604. <li>setpoint: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18605. <li>state: T: t/&#176C B: E/lx F: 0|1|2|3|4|5|auto|off SP: t/&#176C P: absent|present|disabled</li>
  18606. </ul><br>
  18607. The attr subType must be roomSensorControl.18. This is done if the device was
  18608. created by autocreate.
  18609. </li>
  18610. <br><br>
  18611. <li>Room Sensor and Control Unit (EEP A5-10-19)<br>
  18612. [untested]<br>
  18613. <ul>
  18614. <li>T: t/&#176C H: rH/% F: 0|1|2|3|4|5|auto|off SP: t/&#176C P: absent|present|disabled</li>
  18615. <li>fan: 0|1|2|3|4|5|auto|off</li>
  18616. <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
  18617. <li>presence: absent|present|disabled</li>
  18618. <li>setpoint: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18619. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18620. <li>state: T: t/&#176C H: rH/% F: 0|1|2|3|4|5|auto|off SP: t/&#176C P: absent|present|disabled</li>
  18621. </ul><br>
  18622. The attr subType must be roomSensorControl.19. This is done if the device was
  18623. created by autocreate.
  18624. </li>
  18625. <br><br>
  18626. <li>Room Sensor and Control Unit (EEP A5-10-1A)<br>
  18627. [untested]<br>
  18628. <ul>
  18629. <li>T: t/&#176C F: 0|1|2|3|4|5|auto|off SP: t/&#176C P: absent|present|disabled U: U/V</li>
  18630. <li>errorCode: 251 ... 255</li>
  18631. <li>fan: 0|1|2|3|4|5|auto|off</li>
  18632. <li>presence: absent|present|disabled</li>
  18633. <li>setpoint: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18634. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18635. <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
  18636. <li>state: T: t/&#176C F: 0|1|2|3|4|5|auto|off SP: t/&#176C P: absent|present|disabled U: U/V</li>
  18637. </ul><br>
  18638. The attr subType must be roomSensorControl.1A. This is done if the device was
  18639. created by autocreate.
  18640. </li>
  18641. <br><br>
  18642. <li>Room Sensor and Control Unit (EEP A5-10-1B)<br>
  18643. [untested]<br>
  18644. <ul>
  18645. <li>T: t/&#176C B: E/lx F: 0|1|2|3|4|5|auto|off P: absent|present|disabled U: U/V</li>
  18646. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
  18647. <li>errorCode: 251 ... 255</li>
  18648. <li>fan: 0|1|2|3|4|5|auto|off</li>
  18649. <li>presence: absent|present|disabled</li>
  18650. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18651. <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
  18652. <li>state: T: t/&#176C B: E/lx F: 0|1|2|3|4|5|auto|off P: absent|present|disabled U: U/V</li>
  18653. </ul><br>
  18654. The attr subType must be roomSensorControl.1B. This is done if the device was
  18655. created by autocreate.
  18656. </li>
  18657. <br><br>
  18658. <li>Room Sensor and Control Unit (EEP A5-10-1C)<br>
  18659. [untested]<br>
  18660. <ul>
  18661. <li>T: t/&#176C B: E/lx F: 0|1|2|3|4|5|auto|off SP: E/lx P: absent|present|disabled</li>
  18662. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
  18663. <li>fan: 0|1|2|3|4|5|auto|off</li>
  18664. <li>presence: absent|present|disabled</li>
  18665. <li>setpoint: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
  18666. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18667. <li>state: T: t/&#176C B: E/lx F: 0|1|2|3|4|5|auto|off SP: E/lx P: absent|present|disabled</li>
  18668. </ul><br>
  18669. The attr subType must be roomSensorControl.1C. This is done if the device was
  18670. created by autocreate.
  18671. </li>
  18672. <br><br>
  18673. <li>Room Sensor and Control Unit (EEP A5-10-1D)<br>
  18674. [untested]<br>
  18675. <ul>
  18676. <li>T: t/&#176C H: rH/% F: 0|1|2|3|4|5|auto|off SP: rH/% P: absent|present|disabled</li>
  18677. <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
  18678. <li>fan: 0|1|2|3|4|5|auto|off</li>
  18679. <li>presence: absent|present|disabled</li>
  18680. <li>setpoint: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
  18681. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18682. <li>state: T: t/&#176C H: rH/% F: 0|1|2|3|4|5|auto|off SP: rH/% P: absent|present|disabled</li>
  18683. </ul><br>
  18684. The attr subType must be roomSensorControl.1D. This is done if the device was
  18685. created by autocreate.
  18686. </li>
  18687. <br><br>
  18688. <li>Room Sensor and Control Unit (EEP A5-10-1F)<br>
  18689. [untested]<br>
  18690. <ul>
  18691. <li>T: t/&#176C F: 0|1|2|3|auto SP: 0 ... 255 P: absent|present|disabled</li>
  18692. <li>fan: 0|1|2|3|auto</li>
  18693. <li>presence: absent|present|disabled</li>
  18694. <li>setpoint: 0 ... 255</li>
  18695. <li>setpointScaled: &lt;floating-point number&gt;</li>
  18696. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18697. <li>state: T: t/&#176C F: 0|1|2|3|auto SP: 0 ... 255 P: absent|present|disabled</li>
  18698. </ul><br>
  18699. The scaling of the setpoint adjustment is device- and vendor-specific. Set the
  18700. attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
  18701. <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
  18702. setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
  18703. adjust the scaling alternatively.<br>
  18704. The attr subType must be roomSensorControl.1F. This is done if the device was
  18705. created by autocreate.
  18706. </li>
  18707. <br><br>
  18708. <li>Room Operation Panel (EEP A5-10-20, A5-10-21)<br>
  18709. [untested]<br>
  18710. <ul>
  18711. <li>T: t/&#176C H: rH/% SP: 0 ... 255 B: ok|low</li>
  18712. <li>activity: yes|no</li>
  18713. <li>battery: ok|low</li>
  18714. <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
  18715. <li>setpoint: 0 ... 255</li>
  18716. <li>setpointMode: auto|frostProtect|setpoint</li>
  18717. <li>setpointScaled: &lt;floating-point number&gt;</li>
  18718. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18719. <li>state: t/&#176C H: rH/% SP: 0 ... 255 B: ok|low</li>
  18720. </ul><br>
  18721. The scaling of the setpoint adjustment is device- and vendor-specific. Set the
  18722. attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
  18723. <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
  18724. setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
  18725. adjust the scaling alternatively.<br>
  18726. The attr subType must be roomSensorControl.20. This is done if the device was created by autocreate.
  18727. </li>
  18728. <br><br>
  18729. <li>Room Operation Panel (EEP A5-10-22, A5-10-23)<br>
  18730. [untested]<br>
  18731. <ul>
  18732. <li>T: t/&#176C H: rH/% SP: 0 ... 255 F: auto|off|1|2|3 O: occupied|unoccupied</li>
  18733. <li>fanSpeed: auto|off|1|2|3</li>
  18734. <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
  18735. <li>occupancy: occupied|unoccupied</li>
  18736. <li>setpoint: 0 ... 255</li>
  18737. <li>setpointScaled: &lt;floating-point number&gt;</li>
  18738. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  18739. <li>state: t/&#176C H: rH/% SP: 0 ... 255 F: auto|off|1|2|3 O: occupied|unoccupied</li>
  18740. </ul><br>
  18741. The scaling of the setpoint adjustment is device- and vendor-specific. Set the
  18742. attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
  18743. <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
  18744. setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
  18745. adjust the scaling alternatively.<br>
  18746. The attr subType must be roomSensorControl.22.
  18747. This is done if the device was created by autocreate.
  18748. </li>
  18749. <br><br>
  18750. <li>Lighting Controller State (EEP A5-11-01)<br>
  18751. [untested]<br>
  18752. <ul>
  18753. <li>on|off</li>
  18754. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 510 lx)</li>
  18755. <li>contact: open|closed</li>
  18756. <li>daylightHarvesting: enabled|disabled</li>
  18757. <li>dim: 0 ... 255</li>
  18758. <li>presence: absent|present</li>
  18759. <li>illum: 0 ... 255</li>
  18760. <li>mode: switching|dimming</li>
  18761. <li>powerRelayTimer: enabled|disabled</li>
  18762. <li>powerSwitch: on|off</li>
  18763. <li>repeater: enabled|disabled</li>
  18764. <li>state: on|off</li>
  18765. </ul><br>
  18766. The attr subType must be lightCtrlState.01 This is done if the device was
  18767. created by autocreate.
  18768. </li>
  18769. <br><br>
  18770. <li>Temperature Controller Output (EEP A5-11-02)<br>
  18771. [untested]<br>
  18772. <ul>
  18773. <li>t/&#176C</li>
  18774. <li>alarm: on|off</li>
  18775. <li>controlVar: cvar (Sensor Range: cvar = 0 % ... 100 %)</li>
  18776. <li>controllerMode: auto|heating|cooling|off</li>
  18777. <li>controllerState: auto|override</li>
  18778. <li>energyHoldOff: normal|holdoff</li>
  18779. <li>fan: 0 ... 3|auto</li>
  18780. <li>presence: present|absent|standby|frost</li>
  18781. <li>setpointTemp: t/&#176C (Sensor Range: t = 0 &#176C ... 51.2 &#176C)</li>
  18782. <li>state: t/&#176C</li>
  18783. </ul><br>
  18784. The attr subType must be tempCtrlState.01 This is done if the device was
  18785. created by autocreate.
  18786. </li>
  18787. <br><br>
  18788. <li><a name="Blind Status">Blind Status</a> (EEP A5-11-03)<br>
  18789. [untested, experimental status]<br>
  18790. <ul>
  18791. <li>open|closed|not_reached|not_available</li>
  18792. <li>alarm: on|off|no endpoints defined|not used</li>
  18793. <li>anglePos: &alpha;/&#176 (Sensor Range: &alpha; = -180 &#176 ... 180 &#176)</li>
  18794. <li>endPosition: open|closed|not_reached|not_available</li>
  18795. <li>position: pos/% (Sensor Range: pos = 0 % ... 100 %)</li>
  18796. <li>positionMode: normal|inverse</li>
  18797. <li>serviceOn: yes|no</li>
  18798. <li>shutterState: opens|closes|stopped|not_available</li>
  18799. <li>state: open|closed|not_reached|not_available</li>
  18800. </ul><br>
  18801. The attr subType must be shutterCtrlState.01 This is done if the device was
  18802. created by autocreate.<br>
  18803. The profile is linked with <a href="#Blind Command Central">Blind Command Central</a>.
  18804. The profile <a href="#Blind Command Central">Blind Command Central</a>
  18805. controls the devices centrally. For that the attributes subDef, subTypeSet
  18806. and gwCmd have to be set manually.
  18807. </li>
  18808. <br><br>
  18809. <li>Extended Lighting Status (EEP A5-11-04)<br>
  18810. [untested]<br>
  18811. <ul>
  18812. <li>on|off</li>
  18813. <li>alarm: off|lamp_failure|internal_failure|external_periphery_failure</li>
  18814. <li>blue: 0 ... 255</li>
  18815. <li>current: &lt;formula symbol&gt;/&lt;unit&gt; (Sensor range: &lt;formula symbol&gt; = 0 ... 65535 &lt;unit&gt;</li>
  18816. <li>currentUnit: mA|A</li>
  18817. <li>dim: 0 ... 255</li>
  18818. <li>energy: &lt;formula symbol&gt;/&lt;unit&gt; (Sensor range: &lt;formula symbol&gt; = 0 ... 65535 &lt;unit&gt;</li>
  18819. <li>energyUnit: Wh|kWh|MWh|GWh</li>
  18820. <li>green: 0 ... 255</li>
  18821. <li>measuredValue: &lt;formula symbol&gt;/&lt;unit&gt; (Sensor range: &lt;formula symbol&gt; = 0 ... 65535 &lt;unit&gt;</li>
  18822. <li>measureUnit: unknown</li>
  18823. <li>lampOpHours: t/h |unknown (Sensor range: t = 0 h ... 65535 h)</li>
  18824. <li>power: &lt;formula symbol&gt;/&lt;unit&gt; (Sensor range: &lt;formula symbol&gt; = 0 ... 65535 &lt;unit&gt;</li>
  18825. <li>powerSwitch: on|off</li>
  18826. <li>powerUnit: mW|W|kW|MW</li>
  18827. <li>red: 0 ... 255</li>
  18828. <li>rgb: RRGGBB (red (R), green (G) or blue (B) color component values: 00 ... FF)</li>
  18829. <li>serviceOn: yes|no</li>
  18830. <li>voltage: &lt;formula symbol&gt;/&lt;unit&gt; (Sensor range: &lt;formula symbol&gt; = 0 ... 65535 &lt;unit&gt;</li>
  18831. <li>voltageUnit: mV|V</li>
  18832. <li>state: on|off</li>
  18833. </ul><br>
  18834. The attr subType must be lightCtrlState.02 This is done if the device was
  18835. created by autocreate.
  18836. </li>
  18837. <br><br>
  18838. <li>Dual Channel Switch Actuator (EEP A5-11-05)<br>
  18839. [untested]<br>
  18840. <ul>
  18841. <li>1: on|off 2: on|off</li>
  18842. <li>channel1: on|off</li>
  18843. <li>channel2: on|off</li>
  18844. <li>workingMode: 1 ... 4</li>
  18845. <li>state: 1: on|off 2: on|off</li>
  18846. </ul><br>
  18847. The attr subType must be switch.05. This is done if the device was created by autocreate.
  18848. </li>
  18849. <br><br>
  18850. <li>Automated meter reading (AMR), Counter (EEP A5-12-00)<br>
  18851. [Thermokon SR-MI-HS, untested]<br>
  18852. <ul>
  18853. <li>1/s</li>
  18854. <li>currentValue<00 ... 15>: 1/s</li>
  18855. <li>counter<00 ... 15>: 0 ... 16777215</li>
  18856. <li>state: 1/s</li>
  18857. </ul><br>
  18858. The attr subType must be autoMeterReading.00. This is done if the device was
  18859. created by autocreate.
  18860. </li>
  18861. <br><br>
  18862. <li>Automated meter reading (AMR), Electricity (EEP A5-12-01)<br>
  18863. [Eltako FSS12, DSZ14DRS, DSZ14WDRS, Thermokon SR-MI-HS, untested]<br>
  18864. [Eltako FWZ12-16A tested]<br>
  18865. <ul>
  18866. <li>P/W</li>
  18867. <li>power: P/W</li>
  18868. <li>energy<0 ... 15>: E/kWh</li>
  18869. <li>currentTariff: 0 ... 15</li>
  18870. <li>serialNumber: S-&lt;nnnnnn&gt;</li>
  18871. <li>state: P/W</li>
  18872. </ul><br>
  18873. The attr subType must be autoMeterReading.01 and attr
  18874. manufID must be 00D for Eltako Devices. This is done if the device was
  18875. created by autocreate.
  18876. </li>
  18877. <br><br>
  18878. <li>Automated meter reading (AMR), Gas, Water (EEP A5-12-02, A5-12-03)<br>
  18879. [untested]<br>
  18880. <ul>
  18881. <li>Vs/l</li>
  18882. <li>flowrate: Vs/l</li>
  18883. <li>consumption<0 ... 15>: V/m3</li>
  18884. <li>currentTariff: 0 ... 15</li>
  18885. <li>state: Vs/l</li>
  18886. </ul><br>
  18887. The attr subType must be autoMeterReading.02|autoMeterReading.03.
  18888. This is done if the device was created by autocreate.
  18889. </li>
  18890. <br><br>
  18891. <li>Automated meter reading (AMR), Temperatur, Load (EEP A5-12-04)<br>
  18892. [untested]<br>
  18893. <ul>
  18894. <li>T: t/&#176C W: m/g B: full|ok|low|empty</li>
  18895. <li>battery: full|ok|low|empty</li>
  18896. <li>temperature: t/&#176C (Sensor Range: t = -40 &#176C ... 40 &#176C)</li>
  18897. <li>weight: m/g</li>
  18898. <li>state: T: t/&#176C W: m/g B: full|ok|low|empty</li>
  18899. </ul><br>
  18900. The attr subType must be autoMeterReading.04.
  18901. This is done if the device was created by autocreate.
  18902. </li>
  18903. <br><br>
  18904. <li>Automated meter reading (AMR), Temperatur, Container Sensor (EEP A5-12-05)<br>
  18905. [untested]<br>
  18906. <ul>
  18907. <li>T: t/&#176C L: <location0 ... location9> B: full|ok|low|empty</li>
  18908. <li>amount: 0 ... 10</li>
  18909. <li>battery: full|ok|low|empty</li>
  18910. <li>location<0 ... 9>: possessed|not_possessed</li>
  18911. <li>temperature: t/&#176C (Sensor Range: t = -40 &#176C ... 40 &#176C)</li>
  18912. <li>state: T: t/&#176C L: <location0 ... location9> B: full|ok|low|empty</li>
  18913. </ul><br>
  18914. The attr subType must be autoMeterReading.05.
  18915. This is done if the device was created by autocreate.
  18916. </li>
  18917. <br><br>
  18918. <li>Automated meter reading (AMR), Current Meter 16 Channels (EEP A5-12-10)<br>
  18919. [untested]<br>
  18920. <ul>
  18921. <li>I/mA</li>
  18922. <li>current<00 ... 15>: I/mA (Sensor Range: I = 0 mA ... 16777215 mA)</li>
  18923. <li>electricChange<00 ... 15>: Q/Ah (Sensor Range: Q = 0 Ah ... 16777215 Ah)</li>
  18924. <li>currentTariff: 00 ... 15</li>
  18925. <li>state: I/mA</li>
  18926. </ul><br>
  18927. The attr subType must be autoMeterReading.10. This is done if the device was created by autocreate.
  18928. </li>
  18929. <br><br>
  18930. <li>Environmental Applications<br>
  18931. Weather Station (EEP A5-13-01)<br>
  18932. Sun Intensity (EEP A5-13-02)<br>
  18933. [Eltako FWS61]<br>
  18934. <ul>
  18935. <li>T: t/&#176C B: E/lx W: Vs/m IR: yes|no</li>
  18936. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 999 lx)</li>
  18937. <li>dayNight: day|night</li>
  18938. <li>hemisphere: north|south</li>
  18939. <li>isRaining: yes|no</li>
  18940. <li>sunEast: E/lx (Sensor Range: E = 1 lx ... 150 klx)</li>
  18941. <li>sunSouth: E/lx (Sensor Range: E = 1 lx ... 150 klx)</li>
  18942. <li>sunWest: E/lx (Sensor Range: E = 1 lx ... 150 klx)</li>
  18943. <li>temperature: t/&#176C (Sensor Range: t = -40 &#176C ... 80 &#176C)</li>
  18944. <li>windSpeed: Vs/m (Sensor Range: V = 0 m/s ... 70 m/s)</li>
  18945. <li>state:T: t/&#176C B: E/lx W: Vs/m IR: yes|no</li>
  18946. </ul><br>
  18947. Brightness is the strength of the dawn light. SunEast,
  18948. sunSouth and sunWest are the solar radiation from the respective
  18949. compass direction. IsRaining is the rain indicator.<br>
  18950. The attr subType must be environmentApp and attr manufID must be 00D
  18951. for Eltako Devices. This is done if the device was created by
  18952. autocreate.<br>
  18953. The Eltako Weather Station FWS61 supports not the day/night indicator
  18954. (dayNight).<br>
  18955. </li>
  18956. <br><br>
  18957. <li>Environmental Applications<br>
  18958. Data Exchange (EEP A5-13-03)<br>
  18959. Time and Day Exchange (EEP A5-13-04)<br>
  18960. Direction Exchange (EEP A5-13-05)<br>
  18961. Geographic Exchange (EEP A5-13-06)<br>
  18962. <ul>
  18963. <li>azimuth: &alpha;/&deg; (Sensor Range: &alpha; = 0 &deg; ... 359 &deg;)</li>
  18964. <li>date: JJJJ-MM-DD</li>
  18965. <li>elevation: &beta;/&deg; (Sensor Range: &beta; = -90 &deg; ... 90 &deg;)</li>
  18966. <li>latitude: &phi;/&deg; (Sensor Range: &phi; = -90 &deg; ... 90 &deg;)</li>
  18967. <li>longitude: &lambda;/&deg; (Sensor Range: &lambda; = -180 &deg; ... 180 &deg;)</li>
  18968. <li>time: hh:mm:ss [AM|PM]</li>
  18969. <li>timeSource: GPS|RTC</li>
  18970. <li>weekday: Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday</li>
  18971. </ul><br>
  18972. The attr subType must be environmentApp. This is done if the device was created by
  18973. autocreate.
  18974. </li>
  18975. <br><br>
  18976. <li>Environmental Applications<br>
  18977. Sun Position and Radiation (EEP A5-13-10)<br>
  18978. [untested]<br>
  18979. <ul>
  18980. <li>SRA: E m2/W SNA: &alpha;/&deg; SNE: &beta;/&deg;</li>
  18981. <li>dayNight: day|night</li>
  18982. <li>solarRadiation: E m2/W (Sensor Range: E = 0 W/m2 ... 2000 W/m2)</li>
  18983. <li>sunAzimuth: &alpha;/&deg; (Sensor Range: &alpha; = -90 &deg; ... 90 &deg;)</li>
  18984. <li>sunElevation: &beta;/&deg; (Sensor Range: &beta; = 0 &deg; ... 90 &deg;)</li>
  18985. <li>state:SRA: E m2/W SNA: &alpha;/&deg; SNE: &beta;/&deg;</li>
  18986. </ul><br>
  18987. The attr subType must be environmentApp. This is done if the device was created by
  18988. autocreate.
  18989. </li>
  18990. <br><br>
  18991. <li>Wind Sensor (EEP A5-13-07)<br>
  18992. [Hideki, untested]<br>
  18993. <ul>
  18994. <li>Vh/km (Sensor Range: V = 0 km/h ... 199.9 km/h)</li>
  18995. <li>battery: ok|low</li>
  18996. <li>windSpeedAverage: Vh/km (Sensor Range: V = 0 km/h ... 199.9 km/h)</li>
  18997. <li>windSpeedDirection: NNE|NE|ENE|E|ESE|SE|SSE|S|SSW|SW|WSW|W|WNW|NW|NNW|N</li>
  18998. <li>windSpeedMax: Vh/km (Sensor Range: V = 0 km/h ... 199.9 km/h)</li>
  18999. <li>state:Vh/km (Sensor Range: V = 0 km/h ... 199.9 km/h)</li>
  19000. </ul><br>
  19001. The attr subType must be windSensor.01. This is done if the device was created by
  19002. autocreate.<br>
  19003. </li>
  19004. <br><br>
  19005. <li>Rain Sensor (EEP A5-13-08)<br>
  19006. [Hideki, untested]<br>
  19007. <ul>
  19008. <li>H/mm</li>
  19009. <li>battery: ok|low</li>
  19010. <li>rain: H/mm</li>
  19011. <li>state:H/mm</li>
  19012. </ul><br>
  19013. The amount of rainfall is calculated at intervals of 183 s.<br>
  19014. The attr subType must be rainSensor.01. This is done if the device was created by
  19015. autocreate.<br>
  19016. </li>
  19017. <br><br>
  19018. <li>Multi-Func Sensor (EEP A5-14-01 ... A5-14-06)<br>
  19019. [untested]<br>
  19020. <ul>
  19021. <li>C: open|closed V: on|off E: E/lx U: U/V</li>
  19022. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
  19023. <li>contact: open|closed</li>
  19024. <li>errorCode: 251 ... 255</li>
  19025. <li>vibration: on|off</li>
  19026. <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
  19027. <li>state: C: open|closed V: on|off E: E/lx U: U/V</li>
  19028. </ul><br>
  19029. The attr subType must be multiFuncSensor. This is done if the device was
  19030. created by autocreate.
  19031. </li>
  19032. <br><br>
  19033. <li>Dual Door Contact (EEP A5-14-07, A5-14-08)<br>
  19034. [untested]<br>
  19035. <ul>
  19036. <li>C: open|closed B: unlocked|locked V: on|off U: U/V</li>
  19037. <li>alarm: dead_sensor</li>
  19038. <li>block: unlocked|locked</li>
  19039. <li>contact: open|closed</li>
  19040. <li>vibration: on|off</li>
  19041. <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
  19042. <li>state: C: open|closed B: unlocked|locked V: on|off U: U/V</li>
  19043. </ul><br>
  19044. The attr subType must be doorContact. This is done if the device was
  19045. created by autocreate.
  19046. </li>
  19047. <br><br>
  19048. <li>Window/Door Contact (EEP A5-14-09, A5-14-0A)<br>
  19049. [untested]<br>
  19050. <ul>
  19051. <li>W: open|tilt|closed B: unlocked|locked V: on|off U: U/V</li>
  19052. <li>alarm: dead_sensor</li>
  19053. <li>vibration: on|off</li>
  19054. <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
  19055. <li>window: open|tilt|closed</li>
  19056. <li>state: W: open|tilt|closed V: on|off U: U/V</li>
  19057. </ul><br>
  19058. The attr subType must be windowContact. This is done if the device was
  19059. created by autocreate.
  19060. </li>
  19061. <br><br>
  19062. <li>Battery Powered Actuator (EEP A5-20-01)<br>
  19063. [Kieback&Peter MD15-FTL-xx]<br>
  19064. <ul>
  19065. <li>T: t/&#176C SPT: t/&#176C SP: setpoint/%</li>
  19066. <li>actuatorState: obstructed|ok</li>
  19067. <li>alarm: no_response_from_actuator</li>
  19068. <li>battery: ok|low</li>
  19069. <li>cover: open|closed</li>
  19070. <li>delta: &lt;floating-point number&gt;</li>
  19071. <li>energyInput: enabled|disabled</li>
  19072. <li>energyStorage: charged|empty</li>
  19073. <li>maintenanceMode: off|runInit|valveClosed|valveOpend:runInit</li>
  19074. <li>operationMode: off|setpoint|setpointTemp</li>
  19075. <li>p_d: &lt;floating-point number&gt;</li>
  19076. <li>p_i: &lt;floating-point number&gt;</li>
  19077. <li>p_p: &lt;floating-point number&gt;</li>
  19078. <li>pidAlarm: actuator_in_errorPos|dead_sensor|no_temperature_value|setpoint_device_missing</li>
  19079. <li>pidState: alarm|idle|processing|start|stop|</li>
  19080. <li>roomTemp: t/&#176C</li>
  19081. <li>selfCtl: on|off</li>
  19082. <li>setpoint: setpoint/%</li>
  19083. <li>setpointSet: setpoint/%</li>
  19084. <li>setpointCalc: setpoint/%</li>
  19085. <li>setpointTemp: t/&#176C</li>
  19086. <li>setpointTempSet: t/&#176C</li>
  19087. <li>teach: &lt;result of teach procedure&gt;</li>
  19088. <li>temperature: t/&#176C</li>
  19089. <li>waitingCmds: no_change|runInit|setpoint|setpointTemp|valveCloses|valveOpens</li>
  19090. <li>wakeUpCycle: t/s</li>
  19091. <li>window: open|closed</li>
  19092. <li>state: T: t/&#176C SPT: t/&#176C SP: setpoint/%</li>
  19093. </ul><br>
  19094. The internal temperature sensor (roomTemp) of the Micropelt iTRV is not suitable as
  19095. a room thermostat.<br>
  19096. The attr subType must be hvac.01. This is done if the device was created by
  19097. autocreate.
  19098. </li>
  19099. <br><br>
  19100. <li>Heating Radiator Actuating Drive (EEP A5-20-04)<br>
  19101. [Holter SmartDrive MX]<br>
  19102. <ul>
  19103. <li>T: t/&#176C SPT: t/&#176C SP: setpoint/%</li>
  19104. <li>alarm: measurement_error|battery_empty|frost_protection|blocked_valve|end_point_detection_error|no_valve|not_taught_in|no_response_from_controller|teach-in_error</li>
  19105. <li>battery: ok|empty</li>
  19106. <li>blockKey: yes|no</li>
  19107. <li>delta: &lt;floating-point number&gt;</li>
  19108. <li>feedTemp: t/&#176C</li>
  19109. <li>maintenanceMode: off|runInit|valveClosed|valveOpend:runInit</li>
  19110. <li>measurementState: active|inactive</li>
  19111. <li>operationMode: off|setpoint|setpointTemp</li>
  19112. <li>p_d: &lt;floating-point number&gt;</li>
  19113. <li>p_i: &lt;floating-point number&gt;</li>
  19114. <li>p_p: &lt;floating-point number&gt;</li>
  19115. <li>pidAlarm: actuator_in_errorPos|dead_sensor|no_temperature_value|setpoint_device_missing</li>
  19116. <li>pidState: alarm|idle|processing|start|stop|</li>
  19117. <li>roomTemp: t/&#176C</li>
  19118. <li>setpoint: setpoint/%</li>
  19119. <li>setpointSet: setpoint/%</li>
  19120. <li>setpointCalc: setpoint/%</li>
  19121. <li>setpointTemp: t/&#176C</li>
  19122. <li>setpointTempSet: t/&#176C</li>
  19123. <li>teach: &lt;result of teach procedure&gt;</li>
  19124. <li>temperature: t/&#176C</li>
  19125. <li>waitingCmds: no_change|runInit|setpoint|setpointTemp|valveCloses|valveOpens</li>
  19126. <li>state: T: t/&#176C SPT: t/&#176C SP: setpoint/%</li>
  19127. </ul><br>
  19128. The attr subType must be hvac.04. This is done if the device was created by
  19129. autocreate.
  19130. </li>
  19131. <br><br>
  19132. <li>Generic HVAC Interface (EEP A5-20-10)<br>
  19133. [IntesisBox PA-AC-ENO-1i]<br>
  19134. <ul>
  19135. <li>on|off</li>
  19136. <li>ctrl: auto|0...100</li>
  19137. <li>fanSpeed: auto|1...14</li>
  19138. <li>occupancy: occupied|off|standby|unoccupied</li>
  19139. <li>mode: auto|heat|morning_warmup|cool|night_purge|precool|off|test|emergency_heat|fan_only|free_cool|ice|max_heat|eco|dehumidification|calibration|emergency_cool|emergency_stream|max_cool|hvc_load|no_load|auto_heat|auto_cool</li>
  19140. <li>vanePosition: auto|horizontal|position_2|position_3|position_4|vertical|swing|vertical_swing|horizontal_swing|hor_vert_swing|stop_swing</li>
  19141. <li>powerSwitch: on|off</li>
  19142. <li>teach: &lt;result of teach procedure&gt;</li>
  19143. <li>state: on|off</li>
  19144. </ul><br>
  19145. The attr subType must be hvac.10. This is done if the device was created by
  19146. autocreate.
  19147. </li>
  19148. <br><br>
  19149. <li>Generic HVAC Interface - Error Control (EEP A5-20-11)<br>
  19150. [IntesisBox PA-AC-ENO-1i]<br>
  19151. <ul>
  19152. <li>error|ok</li>
  19153. <li>alarm: error|ok</li>
  19154. <li>errorCode: 0...65535</li>
  19155. <li>externalDisable: disable|enable</li>
  19156. <li>keyCardDisable: disable|enable</li>
  19157. <li>otherDisable: disable|enable</li>
  19158. <li>powerSwitch: on|off</li>
  19159. <li>remoteCtrl: disable|enable</li>
  19160. <li>teach: &lt;result of teach procedure&gt;</li>
  19161. <li>window: closed|opened</li>
  19162. <li>windowDisable: disable|enable</li>
  19163. <li>state: error|ok</li>
  19164. </ul><br>
  19165. The attr subType must be hvac.11. This is done if the device was created by
  19166. autocreate.
  19167. </li>
  19168. <br><br>
  19169. <li>Digital Input (EEP A5-30-01, A5-30-02)<br>
  19170. [Thermokon SR65 DI, untested]<br>
  19171. <ul>
  19172. <li>open|closed</li>
  19173. <li>battery: ok|low (only EEP A5-30-01)</li>
  19174. <li>contact: open|closed</li>
  19175. <li>teach: &lt;result of teach procedure&gt;</li>
  19176. <li>state: open|closed</li>
  19177. </ul><br>
  19178. The attr subType must be digitalInput.01 or digitalInput.02. This is done if the device was
  19179. created by autocreate.
  19180. </li>
  19181. <br><br>
  19182. <li>Digital Input (EEP A5-30-03)<br>
  19183. 4 digital Inputs, Wake, Temperature [untested]<br>
  19184. <ul>
  19185. <li>T: t/&#176C I: 0|1 0|1 0|1 0|1 W: 0|1</li>
  19186. <li>in0: 0|1</li>
  19187. <li>in1: 0|1</li>
  19188. <li>in2: 0|1</li>
  19189. <li>in3: 0|1</li>
  19190. <li>teach: &lt;result of teach procedure&gt;</li>
  19191. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  19192. <li>wake: high|low</li>
  19193. <li>state: T: t/&#176C I: 0|1 0|1 0|1 0|1 W: high|low</li>
  19194. </ul><br>
  19195. The attr subType must be digitalInput.03. This is done if the device was
  19196. created by autocreate.
  19197. </li>
  19198. <br><br>
  19199. <li>Smoke Detector (EEP A5-30-03)<br>
  19200. [Eltako TF-RWB]<br>
  19201. <ul>
  19202. <li>smoke-alarm</li>
  19203. <li>off</li>
  19204. <li>alarm: dead_sensor|smoke-alarm|off</li>
  19205. <li>teach: &lt;result of teach procedure&gt;</li>
  19206. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  19207. <li>state: smoke-alarm|off</li>
  19208. </ul><br>
  19209. The attr subType must be digitalInput.03. This is done if the device was
  19210. created by autocreate. Set attr model to Eltako_TF_RWB manually.
  19211. </li>
  19212. <br><br>
  19213. <li>Digital Input (EEP A5-30-04)<br>
  19214. 3 digital Inputs, 1 digital Input 8 bits [untested]<br>
  19215. <ul>
  19216. <li>0|1 0|1 0|1 0...255</li>
  19217. <li>in0: 0|1</li>
  19218. <li>in1: 0|1</li>
  19219. <li>in2: 0|1</li>
  19220. <li>in3: 0...255</li>
  19221. <li>teach: &lt;result of teach procedure&gt;</li>
  19222. <li>state: 0|1 0|1 0|1 0...255</li>
  19223. </ul><br>
  19224. The attr subType must be digitalInput.04. This is done if the device was
  19225. created by autocreate.
  19226. </li>
  19227. <br><br>
  19228. <li>Digital Input, single input contact, retransmission, battery monitor (EEP A5-30-05)<br>
  19229. [untested]<br>
  19230. <ul>
  19231. <li>error|event|heartbeat</li>
  19232. <li>battery: U/V (Range: U = 0 V ... 3.3 V</li>
  19233. <li>signalIdx: 0 ... 127</li>
  19234. <li>teach: &lt;result of teach procedure&gt;</li>
  19235. <li>telegramType: event|heartbeat</li>
  19236. <li>state: error|event|heartbeat</li>
  19237. </ul><br>
  19238. The attr subType must be digitalInput.05. This is done if the device was
  19239. created by autocreate.
  19240. </li>
  19241. <li>Energy management, demand response (EEP A5-37-01)<br>
  19242. <br>
  19243. <ul>
  19244. <li>on|off|waiting_for_start|waiting_for_stop</li>
  19245. <li>level: 0...15</li>
  19246. <li>powerUsage: powerUsage/%</li>
  19247. <li>powerUsageLevel: max|min</li>
  19248. <li>powerUsageScale: rel|max</li>
  19249. <li>randomEnd: yes|no</li>
  19250. <li>randomStart: yes|no</li>
  19251. <li>setpoint: 0...255</li>
  19252. <li>teach: &lt;result of teach procedure&gt;</li>
  19253. <li>timeout: yyyy-mm-dd hh:mm:ss</li>
  19254. <li>state: on|off|waiting_for_start|waiting_for_stop</li>
  19255. </ul><br>
  19256. The attr subType must be energyManagement.01. This is done if the device was
  19257. created by autocreate.
  19258. </li>
  19259. <br><br>
  19260. <li>Gateway (EEP A5-38-08)<br>
  19261. Switching<br>
  19262. [Eltako FLC61, FSA12, FSR14]<br>
  19263. <ul>
  19264. <li>on</li>
  19265. <li>off</li>
  19266. <li>executeTime: t/s (Sensor Range: t = 0.1 s ... 6553.5 s or 0 if no time specified)</li>
  19267. <li>executeType: duration|delay</li>
  19268. <li>block: lock|unlock</li>
  19269. <li>teach: &lt;result of teach procedure&gt;</li>
  19270. <li>state: on|off</li>
  19271. </ul><br>
  19272. The attr subType must be gateway and gwCmd must be switching. This is done if the device was
  19273. created by autocreate.<br>
  19274. For Eltako devices attributes must be set manually. Eltako devices only send on/off.
  19275. </li>
  19276. <br><br>
  19277. <li>Gateway (EEP A5-38-08)<br>
  19278. Dimming<br>
  19279. [Eltako FUD14, FUD61, FUD70, FSG14, ...]<br>
  19280. <ul>
  19281. <li>on</li>
  19282. <li>off</li>
  19283. <li>block: lock|unlock</li>
  19284. <li>dim: dim/% (Sensor Range: dim = 0 % ... 100 %)</li>
  19285. <li>dimValueLast: dim/%<br>
  19286. Last value received from the bidirectional dimmer.</li>
  19287. <li>dimValueStored: dim/%<br>
  19288. Last value saved by <code>set &lt;name&gt; dim &lt;value&gt;</code>.</li>
  19289. <li>rampTime: t/s (Sensor Range: t = 1 s ... 255 s or 0 if no time specified,
  19290. for Eltako: t = 1 = fast dimming ... 255 = slow dimming or 0 = dimming speed on the dimmer used)</li>
  19291. <li>teach: &lt;result of teach procedure&gt;</li>
  19292. <li>state: on|off</li>
  19293. </ul><br>
  19294. The attr subType must be gateway, gwCmd must be dimming and attr manufID must be 00D
  19295. for Eltako Devices. This is done if the device was created by autocreate.<br>
  19296. For Eltako devices attributes must be set manually. Eltako devices only send on/off and dim.
  19297. </li>
  19298. <br><br>
  19299. <li>Gateway (EEP A5-38-08)<br>
  19300. Setpoint shift<br>
  19301. [untested]<br>
  19302. <ul>
  19303. <li>1/K</li>
  19304. <li>setpointShift: 1/K (Sensor Range: T = -12.7 K ... 12.8 K)</li>
  19305. <li>teach: &lt;result of teach procedure&gt;</li>
  19306. <li>state: 1/K</li>
  19307. </ul><br>
  19308. The attr subType must be gateway, gwCmd must be setpointShift.
  19309. This is done if the device was created by autocreate.
  19310. </li>
  19311. <br><br>
  19312. <li>Gateway (EEP A5-38-08)<br>
  19313. Basic Setpoint<br>
  19314. [untested]<br>
  19315. <ul>
  19316. <li>t/&#176C</li>
  19317. <li>setpoint: t/&#176C (Sensor Range: t = 0 &#176C ... 51.2 &#176C)</li>
  19318. <li>teach: &lt;result of teach procedure&gt;</li>
  19319. <li>state: t/&#176C</li>
  19320. </ul><br>
  19321. The attr subType must be gateway, gwCmd must be setpointBasic.
  19322. This is done if the device was created by autocreate.
  19323. </li>
  19324. <br><br>
  19325. <li>Gateway (EEP A5-38-08)<br>
  19326. Control variable<br>
  19327. [untested]<br>
  19328. <ul>
  19329. <li>auto|heating|cooling|off</li>
  19330. <li>controlVar: cvov (Sensor Range: cvov = 0 % ... 100 %)</li>
  19331. <li>controllerMode: auto|heating|cooling|off</li>
  19332. <li>controllerState: auto|override</li>
  19333. <li>energyHoldOff: normal|holdoff</li>
  19334. <li>presence: present|absent|standby</li>
  19335. <li>teach: &lt;result of teach procedure&gt;</li>
  19336. <li>state: auto|heating|cooling|off</li>
  19337. </ul><br>
  19338. The attr subType must be gateway, gwCmd must be controlVar.
  19339. This is done if the device was created by autocreate.
  19340. </li>
  19341. <br><br>
  19342. <li>Gateway (EEP A5-38-08)<br>
  19343. Fan stage<br>
  19344. [untested]<br>
  19345. <ul>
  19346. <li>0 ... 3|auto</li>
  19347. <li>teach: &lt;result of teach procedure&gt;</li>
  19348. <li>state: 0 ... 3|auto</li>
  19349. </ul><br>
  19350. The attr subType must be gateway, gwCmd must be fanStage.
  19351. This is done if the device was created by autocreate.
  19352. </li>
  19353. <br><br>
  19354. <li>Extended Lighting Control (EEP A5-38-09)<br>
  19355. [untested]<br>
  19356. <ul>
  19357. <li>on</li>
  19358. <li>off</li>
  19359. <li>block: unlock|on|off|local</li>
  19360. <li>blue: &lt;blue channel value&gt; (Range: blue = 0 ... 255)</li>
  19361. <li>dimMax: &lt;maximum dimming value&gt; (Range: dim = 0 ... 255)</li>
  19362. <li>dimMin: &lt;minimum dimming value&gt; (Range: dim = 0 ... 255)</li>
  19363. <li>green: &lt;green channel value&gt; (Range: green = 0 ... 255)</li>
  19364. <li>rampTime: t/s (Range: t = 0 s ... 65535 s)</li>
  19365. <li>red: &lt;red channel value&gt; (Range: red = 0 ... 255)</li>
  19366. <li>rgb: RRGGBB (red (R), green (G) or blue (B) color component values: 00 ... FF)</li>
  19367. <li>teach: &lt;result of teach procedure&gt;</li>
  19368. <li>state: on|off</li>
  19369. </ul><br>
  19370. Another readings, see subtype lightCtrlState.02.<br>
  19371. The attr subType or subTypSet must be lightCtrl.01. This is done if the device was created by autocreate.<br>
  19372. The subType is associated with the subtype lightCtrlState.02.
  19373. </li>
  19374. <br><br>
  19375. <li>Manufacturer Specific Applications (EEP A5-3F-7F)<br><br>
  19376. Wireless Analog Input Module<br>
  19377. [Thermokon SR65 3AI, untested]<br>
  19378. <ul>
  19379. <li>I1: U/V I2: U/V I3: U/V</li>
  19380. <li>input1: U/V (Sensor Range: U = 0 V ... 10 V)</li>
  19381. <li>input2: U/V (Sensor Range: U = 0 V ... 10 V)</li>
  19382. <li>input3: U/V (Sensor Range: U = 0 V ... 10 V)</li>
  19383. <li>teach: &lt;result of teach procedure&gt;</li>
  19384. <li>state: I1: U/V I2: U/V I3: U/V</li>
  19385. </ul><br>
  19386. The attr subType must be manufProfile and attr manufID must be 002
  19387. for Thermokon Devices. This is done if the device was
  19388. created by autocreate.
  19389. </li>
  19390. <br><br>
  19391. <li>Manufacturer Specific Applications (EEP A5-3F-7F)<br><br>
  19392. Thermostat Actuator<br>
  19393. [AWAG omnio UPH230/1x]<br>
  19394. <ul>
  19395. <li>on|off</li>
  19396. <li>emergencyMode&lt;channel&gt;: on|off</li>
  19397. <li>nightReduction&lt;channel&gt;: on|off</li>
  19398. <li>setpointTemp&lt;channel&gt;: t/&#176C</li>
  19399. <li>teach: &lt;result of teach procedure&gt;</li>
  19400. <li>temperature&lt;channel&gt;: t/&#176C</li>
  19401. <li>window&lt;channel&gt;: on|off</li>
  19402. <li>state: on|off</li>
  19403. </ul><br>
  19404. The attr subType must be manufProfile and attr manufID must be 005
  19405. for AWAG omnio Devices. This is done if the device was created by autocreate.
  19406. </li>
  19407. <br><br>
  19408. <li>Manufacturer Specific Applications (EEP A5-3F-7F)<br><br>
  19409. Shutter (EEP F6-02-01 ... F6-02-02)<br>
  19410. [Eltako FSB12, FSB14, FSB61, FSB70]<br>
  19411. <ul>
  19412. <li>open|open_ack<br>
  19413. The status of the device will become "open" after the TOP endpoint is
  19414. reached, or it has finished an "opens" or "position 0" command.</li>
  19415. <li>closed<br>
  19416. The status of the device will become "closed" if the BOTTOM endpoint is
  19417. reached</li>
  19418. <li>stop<br>
  19419. The status of the device become "stop" if stop command is sent.</li>
  19420. <li>not_reached<br>
  19421. The status of the device become "not_reached" between one of the endpoints.</li>
  19422. <li>anglePos: &alpha;/&#176 (Sensor Range: &alpha; = -180 &#176 ... 180 &#176)</li>
  19423. <li>endPosition: open|open_ack|closed|not_reached|not_available</li>
  19424. <li>position: pos/% (Sensor Range: pos = 0 % ... 100 %)</li>
  19425. <li>teach: &lt;result of teach procedure&gt;</li>
  19426. <li>state: open|open_ack|closed|not_reached|stop|teach</li>
  19427. </ul><br>
  19428. The values of the reading position and anglePos are updated automatically,
  19429. if the command position is sent or the reading state was changed
  19430. manually to open or closed.<br>
  19431. Set attr subType manufProfile, attr manufID to 00D and attr model to
  19432. Eltako_FSB14|FSB61|FSB70|FSB_ACK manually.
  19433. If the attribute model is set to Eltako_FSB_ACK, with the status "open_ack" the readings position and anglePos are also updated.<br>
  19434. </li>
  19435. <br><br>
  19436. <li>Electronic switches and dimmers with Energy Measurement and Local Control (D2-01-00 - D2-01-12)<br>
  19437. [Telefunken Funktionsstecker, PEHA Easyclick, AWAG Elektrotechnik AG Omnio UPS 230/xx,UPD 230/xx]<br>
  19438. <ul>
  19439. <li>on</li>
  19440. <li>off</li>
  19441. <li>autoOffTime&lt;1...29|All|Input&gt;: 1/s</li>
  19442. <li>channel&lt;0...29|All|Input&gt;: on|off</li>
  19443. <li>delayOffTime&lt;1...29|All|Input&gt;: 1/s</li>
  19444. <li>dayNight: day|night</li>
  19445. <li>defaultState: on|off|last</li>
  19446. <li>devTemp: t/&#176C|invalid</li>
  19447. <li>devTempState: ok|max|warning</li>
  19448. <li>dim&lt;0...29|Input&gt;: dim/% (Sensor Range: dim = 0 % ... 100 %)</li>
  19449. <li>energy&lt;channel&gt;: E/[Ws|Wh|KWh]</li>
  19450. <li>energyUnit&lt;channel&gt;: Ws|Wh|KWh</li>
  19451. <li>error&lt;channel&gt;: ok|warning|failure|not_supported</li>
  19452. <li>extSwitchMode&lt;1...29|All|Input&gt;: unavailable|switch|pushbutton|auto</li>
  19453. <li>extSwitchType&lt;1...29|All|Input&gt;: toggle|direction</li>
  19454. <li>firmwareVersion: [000000 ... FFFFFF]</li>
  19455. <li>loadClassification: no</li>
  19456. <li>localControl&lt;channel&gt;: enabled|disabled</li>
  19457. <li>loadLink: connected|disconnected</li>
  19458. <li>loadOperation: 3-wire</li>
  19459. <li>loadState: on|off</li>
  19460. <li>measurementMode: energy|power</li>
  19461. <li>measurementReport: auto|query</li>
  19462. <li>measurementReset: not_active|trigger</li>
  19463. <li>measurementDelta: E/[Ws|Wh|KWh|W|KW]</li>
  19464. <li>measurementUnit: Ws|Wh|KWh|W|KW</li>
  19465. <li>overCurrentOff&lt;channel&gt;: executed|ready</li>
  19466. <li>overCurrentShutdown&lt;channel&gt;: off|restart</li>
  19467. <li>overCurrentShutdownReset&lt;channel&gt;: not_active|trigger</li>
  19468. <li>power&lt;channel&gt;: P/[W|KW]</li>
  19469. <li>powerFailure&lt;channel&gt;: enabled|disabled</li>
  19470. <li>powerFailureDetection&lt;channel&gt;: detected|not_detected</li>
  19471. <li>powerUnit&lt;channel&gt;: W|KW</li>
  19472. <li>rampTime&lt;1...3l&gt;: 1/s</li>
  19473. <li>responseTimeMax: 1/s</li>
  19474. <li>responseTimeMin: 1/s</li>
  19475. <li>roomCtrlMode: off|comfort|comfort-1|comfort-2|economy|buildingProtection</li>
  19476. <li>serialNumber: [00000000 ... FFFFFFFF]</li>
  19477. <li>taughtInDevID&lt;00...23&gt;: [00000001 ... FFFFFFFE]</li>
  19478. <li>taughtInDevNum: [0 ... 23]</li>
  19479. <li>teach: &lt;result of teach procedure&gt;</li>
  19480. <li>teachInDev: enabled|disabled</li>
  19481. <li>state: on|off</li>
  19482. </ul>
  19483. <br>
  19484. The attr subType must be actuator.01. This is done if the device was
  19485. created by autocreate. To control the device, it must be bidirectional paired,
  19486. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  19487. </li>
  19488. <br><br>
  19489. <li>Blind Control for Position and Angle (D2-05-00)<br>
  19490. [AWAG Elektrotechnik AG OMNIO UPJ 230/12]<br>
  19491. <ul>
  19492. <li>open<br>
  19493. The status of the device will become "open" after the TOP endpoint is
  19494. reached, or it has finished an "opens" or "position 0" command.</li>
  19495. <li>closed<br>
  19496. The status of the device will become "closed" if the BOTTOM endpoint is
  19497. reached</li>
  19498. <li>stop<br>
  19499. The status of the device become "stop" if stop command is sent.</li>
  19500. <li>not_reached<br>
  19501. The status of the device become "not_reached" between one of the endpoints.</li>
  19502. <li>pos/% (Sensor Range: pos = 0 % ... 100 %)</li>
  19503. <li>anglePos&lt;channel&gt;: &alpha;/% (Sensor Range: &alpha; = 0 % ... 100 %)</li>
  19504. <li>block&lt;channel&gt;: unlock|lock|alarm</li>
  19505. <li>endPosition&lt;channel&gt;: open|closed|not_reached|unknown</li>
  19506. <li>position&lt;channel&gt;: unknown|pos/% (Sensor Range: pos = 0 % ... 100 %)</li>
  19507. <li>teach: &lt;result of teach procedure&gt;</li>
  19508. <li>state: open|closed|in_motion|stopped|pos/% (Sensor Range: pos = 0 % ... 100 %)</li>
  19509. </ul>
  19510. <br>
  19511. The attr subType must be blindsCtrl.00 or blindsCtrl.01. This is done if the device was
  19512. created by autocreate. To control the device, it must be bidirectional paired,
  19513. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  19514. </li>
  19515. <br><br>
  19516. <li>Multisensor Window Handle (D2-06-01)<br>
  19517. [Soda GmbH]<br>
  19518. <ul>
  19519. <li>T: t/&#176C H: -|rH/% E: -|E/lx M: off|on|invalid|not_supported|unknown</li>
  19520. <li>alarms: &lt;alarms&gt; (Range: alarms = 00000000 ... FFFFFFFF)</li>
  19521. <li>battery: ok|low</li>
  19522. <li>batteryLowClick: enabled|disabled</li>
  19523. <li>burglaryAlarm: off|on|invalid|not_supported|unknown</li>
  19524. <li>handle: up|down|left|right|invalid|not_supported|unknown</li>
  19525. <li>blinkInterval: t/s|unknown (Range: t = 3 s ... 255 s)</li>
  19526. <li>blinkIntervalSet: t/s|unknown (Range: t = 3 s ... 255 s)</li>
  19527. <li>brightness: E/lx|over_range|invalid|not_supported|unknown (Sensor Range: E = 0 lx ... 60000 lx)</li>
  19528. <li>buttonLeft: pressed|released|invalid|not_supported|unknown</li>
  19529. <li>buttonLeftPresses: &lt;buttonLeftPresses&gt; (Range: buttonLeftPresses = 00000000 ... FFFFFFFF)</li>
  19530. <li>buttonRight: pressed|released|invalid|not_supported|unknown</li>
  19531. <li>buttonRightPresses: &lt;buttonRightPresses&gt; (Range: buttonRightPresses = 00000000 ... FFFFFFFF)</li>
  19532. <li>energyStorage: 1/%|unknown</li>
  19533. <li>handleClosedClick: enabled|disabled</li>
  19534. <li>handleMoveClosed: &lt;handleMoveClosed&gt; (Range: handleMoveClosed = 00000000 ... FFFFFFFF)</li>
  19535. <li>handleMoveOpend: &lt;handleMoveOpend&gt; (Range: handleMoveOpend = 00000000 ... FFFFFFFF)</li>
  19536. <li>handleMoveTilted: &lt;handleMoveTilted&gt; (Range: handleMoveTilted = 00000000 ... FFFFFFFF)</li>
  19537. <li>humidity: rH/%|invalid|not_supported|unknown</li>
  19538. <li>motion: off|on|invalid|not_supported|unknown</li>
  19539. <li>powerOns: &lt;powerOns&gt; (Range: powerOns = 00000000 ... FFFFFFFF)</li>
  19540. <li>presence: absent|present|invalid|not_supported|unknown</li>
  19541. <li>protectionAlarm: off|on|invalid|not_supported|unknown</li>
  19542. <li>temperature: t/&#176C|invalid|not_supported|unknown (Sensor Range: t = -20 &#176C ... 60 &#176C)</li>
  19543. <li>updateInterval: t/s|unknown (Range: t = 5 s ... 65535 s)</li>
  19544. <li>updateIntervalSet: t/s|unknown (Range: t = 5 s ... 65535 s)</li>
  19545. <li>waitingCmds: &lt;integer number&gt;</li>
  19546. <li>window: undef|not_tilted|tilted|invalid|not_supported|unknown</li>
  19547. <li>windowTilts: &lt;windowTilts&gt; (Range: windowTilts = 00000000 ... FFFFFFFF)</li>
  19548. <li>state: T: t/&#176C H: -|rH/% E: -|E/lx M: off|on|invalid|not_supported|unknown</li>
  19549. </ul><br>
  19550. The attr subType must be multisensor.01. This is done if the device was
  19551. created by autocreate. To control the device, it must be bidirectional paired,
  19552. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  19553. </li>
  19554. <br><br>
  19555. <li>Room Control Panels (D2-10-00 - D2-10-02)<br>
  19556. [Kieback & Peter RBW322-FTL]<br>
  19557. <ul>
  19558. <li>T: t/&#176C H: -|rH/% F: 0 ... 100/% SPT: t/&#176C O: -|absent|present M: -|on|off</li>
  19559. <li>battery: ok|low|empty|-</li>
  19560. <li>cooling: auto|on|off|-</li>
  19561. <li>customWarning[1|2]: on|off</li>
  19562. <li>fanSpeed: 0 ... 100/%</li>
  19563. <li>fanSpeedMode: central|local</li>
  19564. <li>heating: auto|on|off|-</li>
  19565. <li>humidity: -|rH/%</li>
  19566. <li>moldWarning: on|off</li>
  19567. <li>motion: on|off|-</li>
  19568. <li>occupancy: -|absent|present</li>
  19569. <li>roomCtrlMode: buildingProtection|comfort|economy|preComfort</li>
  19570. <li>setpointBuildingProtectionTemp: -|t/&#176C (Range: t = 0 &#176C ... 40 &#176C)</li>
  19571. <li>setpointComfortTemp: -|t/&#176C (Range: t = 0 &#176C ... 40 &#176C)</li>
  19572. <li>setpointEconomyTemp: -|t/&#176C (Range: t = 0 &#176C ... 40 &#176C)</li>
  19573. <li>setpointPreComfortTemp: -|t/&#176C (Range: t = 0 &#176C ... 40 &#176C)</li>
  19574. <li>setpointTemp: t/&#176C (Range: t = 0 &#176C ... 40 &#176C)</li>
  19575. <li>solarPowered: yes|no</li>
  19576. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  19577. <li>window: closed|open</li>
  19578. <li>state: T: t/&#176C H: -|rH/% F: 0 ... 100/% SPT: t/&#176C O: -|absent|present M: -|on|off</li>
  19579. </ul><br>
  19580. The attr subType must be roomCtrlPanel.00. This is done if the device was
  19581. created by autocreate. To control the device, it must be bidirectional paired,
  19582. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  19583. </li>
  19584. <br><br>
  19585. <li>Room Control Panels (D2-11-01 - D2-11-08)<br>
  19586. [Thermokon EasySens SR06 LCD-2T/-2T rh -4T/-4T rh]<br>
  19587. <ul>
  19588. <li>T: t/&#176C H: rH/% SPT: t/&#176C F: auto|off|1|2|3</li>
  19589. <li>cooling: on|off</li>
  19590. <li>fanSpeed: auto|off|1|2|3</li>
  19591. <li>heating: on|off</li>
  19592. <li>humidity: rH/%</li>
  19593. <li>occupancy: occupied|unoccupied</li>
  19594. <li>setpointBase: t/&#176C (Range: t = 15 &#176C ... 30 &#176C)</li>
  19595. <li>setpointShift: t/K (Range: t = -10 K ... 10 K)</li>
  19596. <li>setpointShiftMax: t/K (Range: t = 0 K ... 10 K)</li>
  19597. <li>setpointTemp: t/&#176C (Range: t = 5 &#176C ... 40 &#176C)</li>
  19598. <li>setpointType: setpointTemp|setpointShift</li>
  19599. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  19600. <li>trigger: heartbeat|sensor|input</li>
  19601. <li>window: closed|open</li>
  19602. <li>state: T: t/&#176C H: rH/% SPT: t/&#176C F: auto|off|1|2|3</li>
  19603. </ul><br>
  19604. The attr subType must be roomCtrlPanel.01. This is done if the device was
  19605. created by autocreate. To control the device, it must be bidirectional paired by Smart Ack,
  19606. see <a href="#EnOcean_smartAck">SmartAck Learning</a>.
  19607. </li>
  19608. <br><br>
  19609. <li>Sensor for Smoke, Air quality, Hygrothermal comfort, Temperature and Humidity (D2-14-30)<br>
  19610. [INSAFE+ Origin I870EO untested]<br>
  19611. <ul>
  19612. <li>off|smoke-alarm</li>
  19613. <li>airQuality: optimal|air_dry|humidity_high|teperature_humidity_high|error</li>
  19614. <li>alarm: off|smoke-alarm|dead_sensor</li>
  19615. <li>battery: ok|medium|low|critical</li>
  19616. <li>endOffLife: t/month (Range t = 0...120 month</li>
  19617. <li>humidity: rH/%</li>
  19618. <li>hygrothermalComfort: good|medium|bad|error</li>
  19619. <li>maintenanceLast: t/week (Range t = 0...250 week</li>
  19620. <li>sensorFaultMode: off|on</li>
  19621. <li>smokeAlarmHumidity: ok|not_ok</li>
  19622. <li>smokeAlarmMaintenance: ok|not_done</li>
  19623. <li>smokeAlarmTemperature: ok|not_ok</li>
  19624. <li>teach: &lt;result of teach procedure&gt;</li>
  19625. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 50 &#176C)</li>
  19626. <li>state: off|smoke-alarm</li>
  19627. </ul><br>
  19628. The attr subType must be multiFuncSensor.30. This is done if the device was
  19629. created by autocreate.
  19630. </li>
  19631. <br><br>
  19632. <li>Fan Control (D2-20-00 - D2-20-02)<br>
  19633. [Maico ECA x RC/RCH, ER 100 RC, untested]<br>
  19634. <ul>
  19635. <li>on|off|not_supported</li>
  19636. <li>fanSpeed: 0 ... 100/%</li>
  19637. <li>error: ok|air_filter|hardware|not_supported</li>
  19638. <li>humidity: rH/%|not_supported</li>
  19639. <li>humidityCtrl: disabled|enabled|not_supported</li>
  19640. <li>roomSize: 0...350/m<sup>2</sup>|max</li>
  19641. <li>roomSizeRef: unsed|not_used|not_supported</li>
  19642. <li>setpointTemp: t/&#176C (Range: t = 0 &#176C ... 40 &#176C)</li>
  19643. <li>teach: &lt;result of teach procedure&gt;</li>
  19644. <li>temperature: t/&#176C (Sensor Range: t = 0 &#176C ... 40 &#176C)</li>
  19645. <li>state: on|off|not_supported</li>
  19646. </ul><br>
  19647. The attr subType must be fanCtrl.00. This is done if the device was
  19648. created by autocreate. To control the device, it must be bidirectional paired,
  19649. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  19650. </li>
  19651. <br><br>
  19652. <li>AC Current Clamp (D2-32-00 - D2-32-02)<br>
  19653. [untested]<br>
  19654. <ul>
  19655. <li>I1: I/A I2: I/A I3: I/A</li>
  19656. <li>current1: I/A (Range: I = 0 A ... 4095 A)</li>
  19657. <li>current2: I/A (Range: I = 0 A ... 4095 A)</li>
  19658. <li>current3: I/A (Range: I = 0 A ... 4095 A)</li>
  19659. <li>teach: &lt;result of teach procedure&gt;</li>
  19660. <li>state: I1: I/A I2: I/A I3: I/A</li>
  19661. </ul><br>
  19662. The attr subType must be currentClamp.00|currentClamp.01|currentClamp.02. This is done if the device was
  19663. created by autocreate.
  19664. </li>
  19665. <br><br>
  19666. <li>LED Controller Status (EEP D2-40-00 - D2-40-01)<br>
  19667. [untested]<br>
  19668. <ul>
  19669. <li>on|off</li>
  19670. <li>blue: 0 % ... 100 %</li>
  19671. <li>daylightHarvesting: on|off</li>
  19672. <li>demandResp: on|off</li>
  19673. <li>dim: 0 % ... 100 %</li>
  19674. <li>green: 0 % ... 100 %</li>
  19675. <li>occupany: unoccupied|occupied|unknown</li>
  19676. <li>powerSwitch: on|off</li>
  19677. <li>red: 0 % ... 100 %</li>
  19678. <li>rgb: RRGGBB (red (R), green (G) or blue (B) color component values: 00 ... FF)</li>
  19679. <li>teach: &lt;result of teach procedure&gt;</li>
  19680. <li>telegramType: event|heartbeat</li>
  19681. <li>state: on|off</li>
  19682. </ul><br>
  19683. The attr subType must be ledCtrlState.00|ledCtrlState.01 This is done if the device was
  19684. created by autocreate.
  19685. </li>
  19686. <br><br>
  19687. <li>Heat Recovery Ventilation (D2-50-00 - D2-50-11)<br>
  19688. [untested]<br>
  19689. <ul>
  19690. <li>off|1...4|auto|demand|supplyAir|exhaustAir</li>
  19691. <li>airQualidity1: 1/%</li>
  19692. <li>airQualidity2: 1/%</li>
  19693. <li>airQualidityThreshold: default|1/%</li>
  19694. <li>CO2Threshold: default|1/%</li>
  19695. <li>coolingProtection: on|off</li>
  19696. <li>defrost: on|off</li>
  19697. <li>deviceMode: master|slave</li>
  19698. <li>drainHeater: on|off</li>
  19699. <li>exhaustAirFlap: closed|opend</li>
  19700. <li>exhaustAirFlow: h/m3 (Sensor Range: Q = 0 m3/h ... 1023 m3/h)</li>
  19701. <li>exhaustFanSpeed: min (Sensor Range: n = 0 / min ... 4095 / min)</li>
  19702. <li>exhaustTemp: t/&#176C (Sensor Range: t = -63 &#176C ... 63 &#176C)</li>
  19703. <li>fault: bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb (b = 0|1)</li>
  19704. <li>filterMaintenance: required|not_required</li>
  19705. <li>fireplaceSafetyMode: disabled|enabled</li>
  19706. <li>heatExchangerBypass: closed|opend</li>
  19707. <li>humidityThreshold: default|rH/%</li>
  19708. <li>info: bbbbbbbb bbbbbbbb (b = 0|1)</li>
  19709. <li>input: bbbbbbbb bbbbbbbb (b = 0|1)</li>
  19710. <li>operationHours: [0 ... 589815]</li>
  19711. <li>output: bbbbbbbb bbbbbbbb (b = 0|1)</li>
  19712. <li>outdoorAirHeater: on|off</li>
  19713. <li>outdoorTemp: t/&#176C (Sensor Range: t = -63 &#176C ... 63 &#176C)</li>
  19714. <li>roomTemp: t/&#176C (Sensor Range: t = -63 &#176C ... 63 &#176C)</li>
  19715. <li>roomTempCtrl: on|off</li>
  19716. <li>roomTempSet: default|t/&#176C (Sensor Range: t = -63 &#176C ... 63 &#176C)</li>
  19717. <li>supplyAirFlow: h/m3 (Sensor Range: Q = 0 m3/h ... 1023 m3/h)</li>
  19718. <li>supplyAirFlap: closed|opend</li>
  19719. <li>supplyAirHeater: on|off</li>
  19720. <li>supplyFanSpeed: min (Sensor Range: n = 0 / min ... 4095 / min)</li>
  19721. <li>supplyTemp: t/&#176C (Sensor Range: t = -63 &#176C ... 63 &#176C)</li>
  19722. <li>SWVersion: [0 ... 4095]</li>
  19723. <li>timerMode: on|off</li>
  19724. <li>weeklyTimer: on|off</li>
  19725. <li>state: off|1...4|auto|demand|supplyAir|exhaustAir</li>
  19726. </ul><br>
  19727. The attr subType must be heatRecovery.00. This is done if the device was
  19728. created by autocreate. To control the device, it must be bidirectional paired,
  19729. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  19730. </li>
  19731. <br><br>
  19732. <li>Valve Control (EEP D2-A0-01)<br>
  19733. <ul>
  19734. <li>opens</li>
  19735. <li>open</li>
  19736. <li>closes</li>
  19737. <li>closed</li>
  19738. <li>teach: &lt;result of teach procedure&gt;</li>
  19739. <li>state: opens|open|closes|closed|teachIn|teachOut</li>
  19740. </ul><br>
  19741. The attr subType must be valveCtrl.00. This is done if the device was
  19742. created by autocreate. To control the device, it must be bidirectional paired,
  19743. see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
  19744. </li>
  19745. <br><br>
  19746. <li>Liquid Leakage Sensor (EEP D2-B0-51)<br>
  19747. [untested]<br>
  19748. <ul>
  19749. <li>dry</li>
  19750. <li>wet</li>
  19751. <li>state: dry|wet</li>
  19752. </ul><br>
  19753. The attr subType must be liquidLeakage.51. This is done if the device was
  19754. created by autocreate.
  19755. </li>
  19756. <br><br>
  19757. <li>Generic Profiles<br>
  19758. <ul>
  19759. <li>&lt;00...64&gt;-&lt;channel name&gt;: &lt;value&gt;</li>
  19760. <li>&lt;00...64&gt;-&lt;channel name&gt;Unit: &lt;value&gt;</li>
  19761. <li>&lt;00...64&gt;-&lt;channel name&gt;ValueType: value|setpointAbs|setpointRel</li>
  19762. <li>&lt;00...64&gt;-&lt;channel name&gt;ChannelType: teachIn|data|flag|enum</li>
  19763. <li>teach: &lt;result of teach procedure&gt;</li>
  19764. </ul><br>
  19765. The attr subType must be genericProfile. This is done if the device was
  19766. created by autocreate. If the profile in slave mode is operated, especially the channel
  19767. definition in the gpDef attributes must be entered manually.
  19768. </li>
  19769. <br><br>
  19770. <li>RAW Command<br>
  19771. <ul>
  19772. <li>RORG: 1BS|4BS|ENC|MCS|RPS|SEC|STE|UTE|VLD</li>
  19773. <li>dataSent: data (Range: 1 Byte hex ... 512 Byte hex)</li>
  19774. <li>statusSent: status (Range: 0x00 ... 0xFF)</li>
  19775. <li>state: RORG: rorg DATA: data STATUS: status ODATA: odata</li>
  19776. </ul><br>
  19777. With the help of this command data messages in hexadecimal format can be sent and received.
  19778. The telegram types (RORG) 1BS and RPS are always received protocol-specific.
  19779. For further information, see
  19780. <a href="http://www.enocean-alliance.org/eep/">EnOcean Equipment Profiles (EEP)</a>.
  19781. <br>
  19782. Set attr subType to raw manually.
  19783. </li>
  19784. <br><br>
  19785. <li>Light and Presence Sensor<br>
  19786. [Omnio Ratio eagle-PM101]<br>
  19787. <ul>
  19788. <li>on</li>
  19789. <li>off</li>
  19790. <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx)</li>
  19791. <li>channel1: on|off<br>
  19792. Motion message in depending on the brightness threshold</li>
  19793. <li>channel2: on|off<br>
  19794. Motion message</li>
  19795. <li>motion: on|off<br>
  19796. Channel 2</li>
  19797. <li>state: on|off<br>
  19798. Channel 2</li>
  19799. </ul><br>
  19800. The sensor also sends switching commands (RORG F6) with the SenderID-1.<br>
  19801. Set attr subType to PM101 manually. Automatic teach-in is not possible,
  19802. since no EEP and manufacturer ID are sent.
  19803. </li>
  19804. <br><br>
  19805. <li>Radio Link Test<br>
  19806. <ul>
  19807. <li>standby|active|stopped</li>
  19808. <li>msgLost: msgLost/%</li>
  19809. <li>rssiMasterAvg: LP/dBm</li>
  19810. <li>state: standby|active|stopped<br></li>
  19811. </ul><br>
  19812. The attr subType must be readioLinkTest. This is done if the device was
  19813. created by autocreate or manually by <code>define &lt;name&gt; EnOcean A5-3F-00</code><br>.
  19814. </li>
  19815. </ul>
  19816. </ul>
  19817. =end html
  19818. =cut