| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412 |
- ##############################################
- # $Id: 10_EnOcean.pm 13812 2017-03-27 04:39:32Z klaus.schauer $
- package main;
- use strict;
- use warnings;
- my $cryptFunc;
- eval "use Crypt::Rijndael";
- if ($@) {
- $cryptFunc = 0;
- } else {
- $cryptFunc = 1;
- }
- eval "use Crypt::Random qw(makerandom)";
- if ($@) {
- $cryptFunc = 0;
- } else {
- $cryptFunc = $cryptFunc == 1 ? 1 : 0;
- }
- use SetExtensions;
- sub EnOcean_Define($$);
- sub EnOcean_Initialize($);
- sub EnOcean_Parse($$);
- sub EnOcean_Get($@);
- sub EnOcean_Set($@);
- sub EnOcean_hvac_01Cmd($$$);
- sub EnOcean_roomCtrlPanel_00Snd($$$$$$$$);
- sub EnOcean_CheckSenderID($$$);
- sub EnOcean_SndRadio($$$$$$$$);
- sub EnOcean_ReadingScaled($$$$);
- sub EnOcean_TimerSet($);
- sub EnOcean_Undef($$);
- my %EnO_rorgname = (
- "A5" => "4BS", # 4BS, org 07
- "A6" => "ADT", # adressing destination telegram
- "A7" => "SMREC", # Smart Ack Relaim
- "B0" => "GPTI", # GP teach-in request
- "B1" => "GPTR", # GP teach-in response
- "B2" => "GPCD", # GP complete data
- "B3" => "GPSD", # GP selective data
- "C5" => "SYSEX", # remote management >> packet type 7 used
- "C6" => "SMLRNREQ", # Smart Ack Learn Request
- "C7" => "SMLRNANS", # Smart Ack Learn Answer
- "D0" => "SMMBOX", # Smart Ack Mail Box Functions
- "D1" => "MSC", # MSC
- "D2" => "VLD", # VLD
- "D4" => "UTE", # UTE
- "D5" => "contact", # 1BS, org 06
- "F6" => "switch", # RPS, org 05
- "30" => "SEC", # secure telegram
- "31" => "ENC", # secure telegram with encapsulation
- "32" => "SECD", # decrypted secure telegram
- "35" => "STE", # secure Teach-In
- "40" => "CDM", # chained data message
- );
- # switch commands
- my @EnO_ptm200btn = ("AI", "A0", "BI", "B0", "CI", "C0", "DI", "D0");
- my %EnO_ptm200btn;
- # switch.00 commands
- my %EnO_switch_00Btn = (
- "A0" => 14,
- "AI" => 13,
- "B0" => 12,
- "BI" => 11,
- "A0,B0" => 7,
- "A0,BI" => 10,
- "AI,B0" => 5,
- "AI,BI" => 9,
- "pressed" => 8,
- "pressed34" => 6,
- "released" => 15,
- "teachInSec" => 253,
- "teachOut" => 254,
- "teachIn" => 255
- );
- # gateway commands
- my @EnO_gwCmd = ("switching", "dimming", "setpointShift", "setpointBasic", "controlVar", "fanStage", "blindCmd");
- my %EnO_gwCmd = (
- "switching" => 1,
- "dimming" => 2,
- "setpointShift" => 3,
- "setpointBasic" => 4,
- "controlVar" => 5,
- "fanStage" => 6,
- "blindCmd" => 7,
- );
- # Some Manufacturers (e.g. Jaeger Direkt) also sell EnOcean products without an entry in the table below.
- my %EnO_manuf = (
- "000" => "Reserved",
- "001" => "Peha",
- "002" => "Thermokon",
- "003" => "Servodan",
- "004" => "EchoFlex Solutions",
- "005" => "AWAG Elektrotechnik AG (Omnio)",
- "006" => "Hardmeier electronics",
- "007" => "Regulvar Inc",
- "008" => "Ad Hoc Electronics",
- "009" => "Distech Controls",
- "00A" => "Kieback + Peter",
- "00B" => "EnOcean GmbH",
- "00C" => "Probare",
- "00D" => "Eltako",
- "00E" => "Leviton",
- "00F" => "Honeywell",
- "010" => "Spartan Peripheral Devices",
- "011" => "Siemens",
- "012" => "T-Mac",
- "013" => "Reliable Controls Corporation",
- "014" => "Elsner Elektronik GmbH",
- "015" => "Diehl Controls",
- "016" => "BSC Computer",
- "017" => "S+S Regeltechnik GmbH",
- "018" => "Masco Corporation",
- "019" => "Intesis Software SL",
- "01A" => "Viessmann",
- "01B" => "Lutuo Technology",
- "01C" => "Schneider Electric",
- "01D" => "Sauter",
- "01E" => "Boot-Up",
- "01F" => "Osram Sylvania",
- "020" => "Unotech",
- "021" => "Delta Controls Inc",
- "022" => "Unitronic AG",
- "023" => "NanoSense",
- "024" => "The S4 Group",
- "025" => "MSR Solutions",
- "026" => "GE",
- "027" => "Maico",
- "028" => "Ruskin Company",
- "029" => "Magnum Engery Solutions",
- "02A" => "KM Controls",
- "02B" => "Ecologix Controls",
- "02C" => "Trio 2 Sys",
- "02D" => "Afriso-Euro-Index",
- "030" => "NEC AccessTechnica Ltd",
- "031" => "ITEC Corporation",
- "032" => "Simix Co Ltd",
- "033" => "Permundo GmbH",
- "034" => "Eurotronic Technology GmbH",
- "035" => "Art Japan Co Ltd",
- "036" => "Tiansu Automation Control System Co Ltd",
- "038" => "Gruppo Giordano Idea Spa",
- "039" => "alphaEOS AG",
- "03A" => "Tag Technologies",
- "03C" => "Cloud Buildings Ltd",
- "03E" => "GIGA Concept",
- "03F" => "Sensortec",
- "040" => "Jaeger Direkt",
- "041" => "Air System Components Inc",
- "043" => "SODA GmbH",
- "045" => "Holter",
- "046" => "ID-RF",
- "049" => "Micropelt GmbH",
- "7FF" => "Multi user Manufacturer ID",
- );
- my %EnO_eepConfig = (
- "A5.02.01" => {attr => {subType => "tempSensor.01"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.02" => {attr => {subType => "tempSensor.02"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.03" => {attr => {subType => "tempSensor.03"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.04" => {attr => {subType => "tempSensor.04"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.05" => {attr => {subType => "tempSensor.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.06" => {attr => {subType => "tempSensor.06"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.07" => {attr => {subType => "tempSensor.07"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.08" => {attr => {subType => "tempSensor.08"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.09" => {attr => {subType => "tempSensor.09"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.0A" => {attr => {subType => "tempSensor.0A"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.0B" => {attr => {subType => "tempSensor.0B"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.10" => {attr => {subType => "tempSensor.10"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.11" => {attr => {subType => "tempSensor.11"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.12" => {attr => {subType => "tempSensor.12"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.13" => {attr => {subType => "tempSensor.13"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.14" => {attr => {subType => "tempSensor.14"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.15" => {attr => {subType => "tempSensor.15"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.16" => {attr => {subType => "tempSensor.16"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.17" => {attr => {subType => "tempSensor.17"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.18" => {attr => {subType => "tempSensor.18"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.19" => {attr => {subType => "tempSensor.19"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.1A" => {attr => {subType => "tempSensor.1A"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.1B" => {attr => {subType => "tempSensor.1B"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.20" => {attr => {subType => "tempSensor.20"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.02.30" => {attr => {subType => "tempSensor.30"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.04.01" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
- "A5.04.02" => {attr => {subType => "tempHumiSensor.02"}, GPLOT => "EnO_temp4humi6:Temp/Humi,EnO_voltage4:Voltage,"},
- "A5.04.03" => {attr => {subType => "tempHumiSensor.03"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
- "A5.05.01" => {attr => {subType => "baroSensor.01"}, GPLOT => "EnO_airPressure4:Airpressure,"},
- "A5.06.01" => {attr => {subType => "lightSensor.01"}, GPLOT => "EnO_brightness4:Brightness,EnO_voltage4:Voltage,"},
- "A5.06.02" => {attr => {subType => "lightSensor.02"}, GPLOT => "EnO_brightness4:Brightness,EnO_voltage4:Voltage,"},
- "A5.06.03" => {attr => {subType => "lightSensor.03"}, GPLOT => "EnO_brightness4:Brightness,"},
- "A5.06.04" => {attr => {subType => "lightSensor.04"}, GPLOT => "EnO_temp4brightness4:Temp/Brightness,"},
- "A5.06.05" => {attr => {subType => "lightSensor.05"}, GPLOT => "EnO_brightness4:Brightness,EnO_voltage4:Voltage,"},
- "A5.07.01" => {attr => {subType => "occupSensor.01"}, GPLOT => "EnO_motion:Motion,EnO_voltage4current4:Voltage/Current,"},
- "A5.07.02" => {attr => {subType => "occupSensor.02"}, GPLOT => "EnO_motion:Motion4brightness4:Motion/Brightness,EnO_voltage4:Voltage,"},
- "A5.07.03" => {attr => {subType => "occupSensor.03"}, GPLOT => "EnO_motion:Motion4brightness4:Motion/Brightness,EnO_voltage4:Voltage,"},
- "A5.08.01" => {attr => {subType => "lightTempOccupSensor.01"}, GPLOT => "EnO_temp4brightness4:Temp/Brightness,EnO_voltage4:Voltage,"},
- "A5.08.02" => {attr => {subType => "lightTempOccupSensor.02"}, GPLOT => "EnO_temp4brightness4:Temp/Brightness,EnO_voltage4:Voltage,"},
- "A5.08.03" => {attr => {subType => "lightTempOccupSensor.03"}, GPLOT => "EnO_temp4brightness4:Temp/Brightness,EnO_voltage4:Voltage,"},
- "A5.09.01" => {attr => {subType => "COSensor.01"}, GPLOT => "EnO_A5-09-01:CO/Temp,"},
- "A5.09.02" => {attr => {subType => "COSensor.02"}, GPLOT => "EnO_A5-09-02:CO/Temp,EnO_voltage4:Voltage,"},
- "A5.09.04" => {attr => {subType => "tempHumiCO2Sensor.01"}, GPLOT => "EnO_CO2:CO2,EnO_temp4humi6:Temp/Humi,"},
- "A5.09.05" => {attr => {subType => "vocSensor.01"}, GPLOT => "EnO_A5-09-05:Concentration,"},
- "A5.09.06" => {attr => {subType => "radonSensor.01"}, GPLOT => "EnO_A5-09-06:Radon,"},
- "A5.09.07" => {attr => {subType => "particlesSensor.01"}, GPLOT => "EnO_A5-09-07:Particles,"},
- "A5.09.08" => {attr => {subType => "CO2Sensor.01"}, GPLOT => "EnO_CO2:CO2,"},
- "A5.09.09" => {attr => {subType => "CO2Sensor.01"}, GPLOT => "EnO_CO2:CO2,"},
- "A5.09.0A" => {attr => {subType => "HSensor.01"}, GPLOT => "EnO_A5-09-0A:H/Temp,EnO_voltage4:Voltage,"},
- "A5.09.0B" => {attr => {subType => "radiationSensor.01"}, GPLOT => "EnO_radioactivity4/Radioactivity,EnO_voltage4:Voltage,"},
- "A5.09.0C" => {attr => {subType => "vocSensor.01"}, GPLOT => "EnO_A5-09-05:Concentration,"},
- "A5.10.01" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.02" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.03" => {attr => {subType => "roomSensorControl.05", comMode => "confirm", subDef => "getNextID"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.04" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.05" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.06" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.07" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.08" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.09" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.0A" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.0B" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.0C" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.0D" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.10" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
- "A5.10.11" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
- "A5.10.12" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
- "A5.10.13" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
- "A5.10.14" => {attr => {subType => "roomSensorControl.01"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
- "A5.10.15" => {attr => {subType => "roomSensorControl.02"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.16" => {attr => {subType => "roomSensorControl.02"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.17" => {attr => {subType => "roomSensorControl.02"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.18" => {attr => {subType => "roomSensorControl.18"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.19" => {attr => {subType => "roomSensorControl.19"}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
- "A5.10.1A" => {attr => {subType => "roomSensorControl.1A"}, GPLOT => "EnO_temp4:Temp,EnO_voltage4:Voltage,"},
- "A5.10.1B" => {attr => {subType => "roomSensorControl.1B"}, GPLOT => "EnO_temp4:Temp,EnO_voltage4:Voltage,"},
- "A5.10.1C" => {attr => {subType => "roomSensorControl.1C"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.1D" => {attr => {subType => "roomSensorControl.1D"}, GPLOT => "EnO_temp4humi6:Temp/Humi"},
- "A5.10.1E" => {attr => {subType => "roomSensorControl.1B"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.1F" => {attr => {subType => "roomSensorControl.1F"}, GPLOT => "EnO_temp4:Temp,"},
- "A5.10.20" => {attr => {subType => "roomSensorControl.20"}, GPLOT => "EnO_temp4humi4:Temp/Humi,"},
- "A5.10.21" => {attr => {subType => "roomSensorControl.20"}, GPLOT => "EnO_temp4humi4:Temp/Humi,"},
- "A5.10.22" => {attr => {subType => "roomSensorControl.22"}, GPLOT => "EnO_temp4humi4:Temp/Humi,"},
- "A5.10.23" => {attr => {subType => "roomSensorControl.22"}, GPLOT => "EnO_temp4humi4:Temp/Humi,"},
- "A5.11.01" => {attr => {subType => "lightCtrlState.01"}, GPLOT => "EnO_A5-11-01:Dim/Brightness,"},
- "A5.11.02" => {attr => {subType => "tempCtrlState.01"}, GPLOT => "EnO_A5-11-02:SetpointTemp/ControlVar,"},
- "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,"},
- "A5.11.04" => {attr => {subType => "lightCtrlState.02", subDef => "getNextID", subTypeSet => "lightCtrl.01", webCmd => "on:off:dim:rgb"}, GPLOT => "EnO_dimFFRGB:DimRGB,"},
- "A5.11.05" => {attr => {subType => "switch.05"}},
- "A5.12.00" => {attr => {subType => "autoMeterReading.00"}, GPLOT => "EnO_A5-12-00:Value/Counter,"},
- "A5.12.01" => {attr => {subType => "autoMeterReading.01"}, GPLOT => "EnO_power4energy4:Power/Energie,"},
- "A5.12.02" => {attr => {subType => "autoMeterReading.02"}, GPLOT => "EnO_A5-12-02:Flowrate/Consumption,"},
- "A5.12.03" => {attr => {subType => "autoMeterReading.03"}, GPLOT => "EnO_A5-12-03:Flowrate/Consumption,"},
- "A5.12.04" => {attr => {subType => "autoMeterReading.04"}, GPLOT => "EnO_A5-12-04:Weight,EnO_A5-12-04_2:Temperature/Battery,"},
- "A5.12.05" => {attr => {subType => "autoMeterReading.05"}, GPLOT => "EnO_A5-12-05:Amount,EnO_A5-12-05_2:Temperature/Battery,"},
- "A5.12.10" => {attr => {subType => "autoMeterReading.10"}, GPLOT => "EnO_A5-12-10:Current/Change,"},
- "A5.13.01" => {attr => {subType => "environmentApp"}, GPLOT => "EnO_A5-13-01:WindSpeed/Raining,EnO_temp4brightness4:Temp/Brightness,"},
- "A5.13.02" => {attr => {subType => "environmentApp"}, GPLOT => "EnO_A5-13-01:SunIntensity,"},
- "A5.13.03" => {attr => {subType => "environmentApp"}},
- "A5.13.04" => {attr => {subType => "environmentApp"}},
- "A5.13.05" => {attr => {subType => "environmentApp"}},
- "A5.13.06" => {attr => {subType => "environmentApp"}},
- "A5.13.07" => {attr => {subType => "windSensor.01"}, GPLOT => "EnO_A5-13-07:WindSpeed,"},
- "A5.13.08" => {attr => {subType => "rainSensor.01"}, GPLOT => "EnO_A5-13-08:Raining,"},
- "A5.13.10" => {attr => {subType => "environmentApp"}, GPLOT => "EnO_solarRadiation4:SolarRadiation,"},
- "A5.14.01" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
- "A5.14.02" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
- "A5.14.03" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
- "A5.14.04" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
- "A5.14.05" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
- "A5.14.06" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
- "A5.14.07" => {attr => {subType => "doorContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
- "A5.14.08" => {attr => {subType => "doorContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
- "A5.14.09" => {attr => {subType => "windowContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
- "A5.14.0A" => {attr => {subType => "windowContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"},
- "A5.20.01" => {attr => {subType => "hvac.01", webCmd => "setpointTemp"}, GPLOT => "EnO_A5-20-01:Temp/SetpointTemp/Setpoint,EnO_A5-20-01_2:PID,"},
- #"A5.20.02" => {attr => {subType => "hvac.02"}},
- #"A5.20.03" => {attr => {subType => "hvac.03"}},
- "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,"},
- "A5.20.10" => {attr => {subType => "hvac.10", comMode => "biDir", destinationID => "unicast", subDef => "getNextID"}, GPLOT => "EnO_A5-20-10:FanSpeed,"},
- "A5.20.11" => {attr => {subType => "hvac.11", comMode => "biDir", destinationID => "unicast", subDef => "getNextID"}},
- #"A5.20.12" => {attr => {subType => "hvac.12"}},
- "A5.30.01" => {attr => {subType => "digitalInput.01"}, GPLOT => "EnO_A5-30-01:Contact/Battery,"},
- "A5.30.02" => {attr => {subType => "digitalInput.02"}, GPLOT => "EnO_A5-30-02:Contact,"},
- "A5.30.03" => {attr => {subType => "digitalInput.03"}, GPLOT => "EnO_A5-30-03:Contact,EnO_temp4:Temp,"},
- "A5.30.04" => {attr => {subType => "digitalInput.04"}, GPLOT => "EnO_A5-30-04:Contact/Digital,"},
- "A5.30.05" => {attr => {subType => "digitalInput.05"}, GPLOT => "EnO_A5-30-05:Contact/Voltage,"},
- "A5.37.01" => {attr => {subType => "energyManagement.01", webCmd => "level:max"}, GPLOT => "EnO_A5-37-01:Level,"},
- "A5.38.08" => {attr => {subType => "gateway"}},
- "A5.38.09" => {attr => {subType => "lightCtrl.01"}, GPLOT => "EnO_dimFFRGB:DimRGB,"},
- "A5.3F.00" => {attr => {subType => "radioLinkTest", comMode => "biDir", destinationID => "unicast", subDef => "getNextID"}},
- "A5.3F.7F" => {attr => {subType => "manufProfile"}},
- "B0.00.00" => {attr => {subType => "genericProfile"}},
- "C5.00.00" => {attr => {subType => "remote", manufID => "7FF"}},
- "D2.01.00" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
- "D2.01.01" => {attr => {subType => "actuator.01", defaultChannel => 0}},
- "D2.01.02" => {attr => {subType => "actuator.01", defaultChannel => 0, webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,EnO_power4energy4:Power/Energie,"},
- "D2.01.03" => {attr => {subType => "actuator.01", defaultChannel => 0, webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,"},
- "D2.01.04" => {attr => {subType => "actuator.01", defaultChannel => 0, webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,EnO_power4energy4:Power/Energie,"},
- "D2.01.05" => {attr => {subType => "actuator.01", defaultChannel => 0, webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,EnO_power4energy4:Power/Energie,"},
- "D2.01.06" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
- "D2.01.07" => {attr => {subType => "actuator.01", defaultChannel => 0}},
- "D2.01.08" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
- "D2.01.09" => {attr => {subType => "actuator.01", defaultChannel => 0, webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,EnO_power4energy4:Power/Energie,"},
- "D2.01.0A" => {attr => {subType => "actuator.01", defaultChannel => 0}},
- "D2.01.0B" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
- "D2.01.0C" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
- "D2.01.0D" => {attr => {subType => "actuator.01", defaultChannel => 0}},
- "D2.01.0E" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
- "D2.01.0F" => {attr => {subType => "actuator.01", defaultChannel => 0}},
- "D2.01.10" => {attr => {subType => "actuator.01", defaultChannel => 0}, GPLOT => "EnO_power4energy4:Power/Energie,"},
- "D2.01.11" => {attr => {subType => "actuator.01", defaultChannel => 0}},
- "D2.01.12" => {attr => {subType => "actuator.01", defaultChannel => 0}},
- "D2.01.13" => {attr => {subType => "actuator.01", defaultChannel => 0}},
- "D2.01.14" => {attr => {subType => "actuator.01", defaultChannel => 0}},
- "D2.03.00" => {attr => {subType => "switch.00"}},
- "D2.03.10" => {attr => {subType => "windowHandle.10"}, GPLOT => "EnO_windowHandle:WindowHandle,"},
- "D2.05.00" => {attr => {subType => "blindsCtrl.00", webCmd => "opens:stop:closes:position"}, GPLOT => "EnO_position4angle4:Position/AnglePos,"},
- "D2.05.01" => {attr => {subType => "blindsCtrl.01", webCmd => "opens:stop:closes:position"}},
- "D2.05.02" => {attr => {subType => "blindsCtrl.00", webCmd => "opens:stop:closes:position"}, GPLOT => "EnO_position4angle4:Position/AnglePos,"},
- "D2.06.01" => {attr => {subType => "multisensor.01"}, GPLOT => "EnO_temp4humi4:Temp/Humi,EnO_brightness4:Brightness,"},
- "D2.10.00" => {attr => {subType => "roomCtrlPanel.00", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.10.01" => {attr => {subType => "roomCtrlPanel.00", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.10.02" => {attr => {subType => "roomCtrlPanel.00", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.11.01" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.11.02" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.11.03" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.11.04" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.11.05" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.11.06" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.11.07" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.11.08" => {attr => {subType => "roomCtrlPanel.01", comMode => "biDir", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
- "D2.20.00" => {attr => {subType => "fanCtrl.00", webCmd => "fanSpeed"}, GPLOT => "EnO_fanSpeed4humi4:FanSpeed/Humi,"},
- "D2.32.00" => {attr => {subType => "currentClamp.00"}, GPLOT => "EnO_D2-32-xx:Current,"},
- "D2.32.01" => {attr => {subType => "currentClamp.01"}, GPLOT => "EnO_D2-32-xx:Current,"},
- "D2.32.02" => {attr => {subType => "currentClamp.02"}, GPLOT => "EnO_D2-32-xx:Current,"},
- "D2.40.00" => {attr => {subType => "ledCtrlState.00"}, GPLOT => "EnO_dim4:Dim,"},
- "D2.40.01" => {attr => {subType => "ledCtrlState.01"}, GPLOT => "EnO_dim4RGB:DimRGB,"},
- "D2.50.00" => {attr => {subType => "heatRecovery.00", webCmd => "ventilation"}, GPLOT => "EnO_D2-50-xx:Temp/AirQuality,EnO_D2-50-xx_2:AirFlow/FanSpeed,"},
- "D2.50.01" => {attr => {subType => "heatRecovery.00", webCmd => "ventilation"}, GPLOT => "EnO_D2-50-xx:Temp/AirQuality,EnO_D2-50-xx_2:AirFlow/FanSpeed,"},
- "D2.50.10" => {attr => {subType => "heatRecovery.00", webCmd => "ventilation"}, GPLOT => "EnO_D2-50-xx:Temp/AirQuality,EnO_D2-50-xx_2:AirFlow/FanSpeed,"},
- "D2.50.11" => {attr => {subType => "heatRecovery.00", webCmd => "ventilation"}, GPLOT => "EnO_D2-50-xx:Temp/AirQuality,EnO_D2-50-xx_2:AirFlow/FanSpeed,"},
- "D2.A0.01" => {attr => {subType => "valveCtrl.00", defaultChannel => 0, webCmd => "opens:closes"}, GPLOT => "EnO_valveCtrl:Valve,"},
- "D2.B0.51" => {attr => {subType => "liquidLeakage.51"}, GPLOT => "EnO_liquidLeakage:LiquidLeakage,"},
- "D5.00.01" => {attr => {subType => "contact", manufID => "7FF"}, GPLOT => "EnO_contact:Contact,"},
- "F6.01.01" => {attr => {subType => "switch", sensorMode => "pushbutton"}},
- "F6.02.01" => {attr => {subType => "switch"}},
- "F6.02.02" => {attr => {subType => "switch"}},
- "F6.02.03" => {attr => {subType => "switch"}},
- #"F6.02.04" => {attr => {subType => "switch.04"}},
- "F6.03.01" => {attr => {subType => "switch"}},
- "F6.03.02" => {attr => {subType => "switch"}},
- "F6.04.01" => {attr => {subType => "keycard"}, GPLOT => "EnO_keycard:Keycard,"},
- #"F6.04.02" => {attr => {subType => "keycard.02"}, GPLOT => "EnO_keycard:Keycard,"},
- "F6.05.00" => {attr => {subType => "windSpeed.00"}},
- "F6.05.01" => {attr => {subType => "liquidLeakage"}, GPLOT => "EnO_liquidLeakage:LiquidLeakage,"},
- "F6.05.02" => {attr => {subType => "smokeDetector.02"}},
- "F6.10.00" => {attr => {subType => "windowHandle"}, GPLOT => "EnO_windowHandle:WindowHandle,"},
- #"F6.10.01" => {attr => {subType => "windowHandle.01"}, GPLOT => "EnO_windowHandle:WindowHandle,"},
- "F6.3F.7F" => {attr => {subType => "switch.7F"}},
- # special profiles
- "G5.07.01" => {attr => {subType => "occupSensor.01", eep => "A5-07-01", manufID => "00D", model => 'tracker'}, GPLOT => "EnO_motion:Motion,EnO_voltage4current4:Voltage/Current,"},
- "G5.10.12" => {attr => {subType => "roomSensorControl.01", eep => "A5-10-12", manufID => "00D", scaleMax => 40, scaleMin => 0, scaleDecimals => 1}, GPLOT => "EnO_temp4humi6:Temp/Humi,"},
- "G5.38.08" => {attr => {subType => "gateway", eep => "A5-38-08", gwCmd => "dimming", manufID => "00D", webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,"},
- "H5.38.08" => {attr => {subType => "gateway", comMode => "confirm", eep => "A5-38-08", gwCmd => "dimming", manufID => "00D", model => "TF", teachMethod => "confirm", webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,"},
- "G5.3F.7F" => {attr => {subType => "manufProfile", eep => "A5-3F-7F", manufID => "00D", webCmd => "opens:stop:closes"}},
- "H5.3F.7F" => {attr => {subType => "manufProfile", comMode => "confirm", eep => "A5-3F-7F", manufID => "00D", model => "TF", sensorMode => 'pushbutton', settingAccuracy => "high", teachMethod => "confirm", webCmd => "opens:stop:closes"}},
- "M5.38.08" => {attr => {subType => "gateway", eep => "A5-38-08", gwCmd => "switching", manufID => "00D", webCmd => "on:off"}},
- "N5.38.08" => {attr => {subType => "gateway", comMode => "confirm", eep => "A5-38-08", gwCmd => "switching", manufID => "00D", model => "TF", teachMethod => "confirm", webCmd => "on:off"}},
- "G5.ZZ.ZZ" => {attr => {subType => "PM101", manufID => "005"}, GPLOT => "EnO_motion:Motion,EnO_brightness4:Brightness,"},
- "L6.02.01" => {attr => {subType => "smokeDetector.02", eep => "F6-05-02", manufID => "00D"}},
- "ZZ.ZZ.ZZ" => {attr => {subType => "raw"}},
- );
- my %EnO_extendedRemoteFunctionCode = (
- 0x210 => "remoteLinkTableInfo", # get
- 0x211 => "remoteLinkTable", # get
- 0x212 => "remoteLinkTable", # set
- 0x213 => "remoteLinkTableGP", # get
- 0x214 => "remoteLinkTableGP", # set
- 0x220 => "remoteLearnMode", # set
- 0x221 => "remoteTeach", # set
- 0x224 => "remoteReset", # set
- 0x225 => "remoteRLT", # set
- 0x226 => "remoteApplyChanges", # set
- 0x227 => "remoteProductID", # get
- 0x230 => "remoteDevCfg", # get
- 0x231 => "remoteDevCfg", # set
- 0x232 => "remoteLinkCfg", # get
- 0x233 => "remoteLinkCfg", # set
- 0x240 => "remoteAck", # parse
- 0x250 => "remoteRepeater", # get
- 0x251 => "remoteRepeater", # set
- 0x252 => "remoteRepeaterFilter" # set
- );
- my @EnO_models = qw (
- other
- FAE14 FHK14 FHK61
- FSA12 FSB14 FSB61 FSB70
- FSM12 FSM61
- FT55
- FTS12
- TF
- OEM
- tracker
- );
- my @EnO_defaultChannel = ("all", "input", 0..29);
- my %wakeUpCycle = (
- 10 => 0,
- 60 => 1,
- 90 => 2,
- 120 => 3,
- 150 => 4,
- 180 => 5,
- 210 => 6,
- 240 => 7,
- 270 => 8,
- 300 => 9,
- 330 => 10,
- 360 => 11,
- 390 => 12,
- 420 => 13,
- 450 => 14,
- 480 => 15,
- 510 => 16,
- 540 => 17,
- 570 => 18,
- 600 => 19,
- 630 => 20,
- 660 => 21,
- 690 => 22,
- 720 => 23,
- 750 => 24,
- 780 => 25,
- 810 => 26,
- 840 => 27,
- 870 => 28,
- 900 => 29,
- 930 => 30,
- 960 => 31,
- 990 => 32,
- 1020 => 33,
- 1050 => 34,
- 1080 => 35,
- 1110 => 36,
- 1140 => 37,
- 1170 => 38,
- 1200 => 39,
- 1230 => 40,
- 1260 => 41,
- 1290 => 42,
- 1320 => 43,
- 1350 => 44,
- 1380 => 45,
- 1410 => 46,
- 1440 => 47,
- 1470 => 48,
- 1500 => 49,
- 10800 => 50,
- 21600 => 51,
- 32400 => 52,
- 43200 => 53,
- 54000 => 54,
- 64800 => 55,
- 75600 => 56,
- 86400 => 57,
- 97200 => 58,
- 108000 => 59,
- 118800 => 60,
- 129600 => 61,
- 140400 => 62,
- 151200 => 63,
- );
- my %wakeUpCycleInv = (
- 0 => 10,
- 1 => 60,
- 2 => 90,
- 3 => 120,
- 4 => 150,
- 5 => 180,
- 6 => 210,
- 7 => 240,
- 8 => 270,
- 9 => 300,
- 10 => 330,
- 11 => 360,
- 12 => 390,
- 13 => 420,
- 14 => 450,
- 15 => 480,
- 16 => 510,
- 17 => 540,
- 18 => 570,
- 19 => 600,
- 20 => 630,
- 21 => 660,
- 22 => 690,
- 23 => 720,
- 24 => 750,
- 25 => 780,
- 26 => 810,
- 27 => 840,
- 28 => 870,
- 29 => 900,
- 30 => 930,
- 31 => 960,
- 32 => 990,
- 33 => 1020,
- 34 => 1050,
- 35 => 1080,
- 36 => 1110,
- 37 => 1140,
- 38 => 1170,
- 39 => 1200,
- 40 => 1230,
- 41 => 1260,
- 42 => 1290,
- 43 => 1320,
- 44 => 1350,
- 45 => 1380,
- 46 => 1410,
- 47 => 1440,
- 48 => 1470,
- 49 => 1500,
- 50 => 10800,
- 51 => 21600,
- 52 => 32400,
- 53 => 43200,
- 54 => 54000,
- 55 => 64800,
- 56 => 75600,
- 57 => 86400,
- 58 => 97200,
- 59 => 108000,
- 60 => 118800,
- 61 => 129600,
- 62 => 140400,
- 63 => 151200,
- );
- my @EnO_resolution = (1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32);
- my @EnO_scaling = (0, 1, 10, 100, 1000, 10000, 100000, 1e6, 1e7, 0.1, 0.01, 0.001, 1e-6, 1e-9);
- my %EnO_gpValueEnum = (
- 1 => {name => "multipurpose"},
- 2 => {name => "buildingMode", enum => {0 => "in_use", 1 => "not_used", 2 => "protection"},
- enumInv => {"in_use" => 0, "not_used" => 1, "protection" => 2}},
- 3 => {name => "occupanyMode", enum => {0 => "occupied", 1 => "standby", 2 => "not_occupied"},
- enumInv => {"occupied" => 0,"standby" => 1,"not_occupied" => 2}},
- 4 => {name => "hvacMode", enum => {0 => "auto", 1 => "comfort", 2 => "standby", 3 => "economy", 4 => "building_protection"},
- enumInv => {"auto" => 0, "comfort" => 1, "standby" => 2, "economy" => 3, "building_protection" => 4}},
- 5 => {name => "changeoverMode", enum => {0 => "auto", 1 => "cooling", 2 => "heating"},
- enumInv => {"auto" => 0,"cooling" => 1,"heating" => 2}},
- );
- my %EnO_gpValueFlag = (
- 1 => {name => "auto/man", flag => {0 => "man", 1 => "auto"},
- flagInv => {"man" => 0,"auto" => 1}},
- 2 => {name => "buttonPressed", flag => {0 => "not_pressed", 1 => "pressed"},
- flagInv => {"not_pressed" => 0,"pressed" => 1}},
- 3 => {name => "buttonChanged", flag => {0 => "no_change", 1 => "change"},
- flagInv => {"no_change" => 0,"change" => 1}},
- 4 => {name => "day/night", flag => {0 => "night", 1 => "day"},
- flagInv => {"night" => 0,"day" => 1}},
- 5 => {name => "down", flag => {0 => "no_change", 1 => "down"},
- flagInv => {"no_change" => 0,"down" => 1}},
- 6 => {name => "generalAlarm", flag => {0 => "manual", 1 => "alarm"},
- flagInv => {"manual" => 0,"alarm" => 1}},
- 7 => {name => "heat/cool", flag => {0 => "cool", 1 => "heat"},
- flagInv => {"cool" => 0,"heat" => 1}},
- 8 => {name => "high/low", flag => {0 => "low", 1 => "high"},
- flagInv => {"low" => 0,"high" => 1}},
- 9 => {name => "occupancy", flag => {0 => "unoccupied", 1 => "occupied"},
- flagInv => {"unoccupied" => 0,"occupied" => 1}},
- 10 => {name => "on/off", flag => {0 => "off", 1 => "on"},
- flagInv => {"off" => 0,"on" => 1}},
- 11 => {name => "open/closed", flag => {0 => "closed", 1 => "open"},
- flagInv => {"closed" => 0,"open" => 1}},
- 12 => {name => "powerAlarm", flag => {0 => "no_change", 1 => "alarm"},
- flagInv => {"no_change" => 0,"alarm" => 1}},
- 13 => {name => "start/stop", flag => {0 => "stop", 1 => "start"},
- flagInv => {"stop" => 0,"start" => 1}},
- 14 => {name => "up", flag => {0 => "no_change", 1 => "up"},
- flagInv => {"no_change" => 0,"up" => 1}},
- );
- my %EnO_gpValueData = (
- 1 => {name => "acceleration", unit => "m/s2"},
- 2 => {name => "angle", unit => "deg"},
- 3 => {name => "angular_velocity", unit => "rad/s"},
- 4 => {name => "area", unit => "m²"},
- 5 => {name => "concentration", unit => "ppm"},
- 6 => {name => "current", unit => "A"},
- 7 => {name => "distance", unit => "m"},
- 8 => {name => "electric_field_strength", unit => "V/m"},
- 9 => {name => "energy", unit => "J"},
- 10 => {name => "number", unit => "N/A"},
- 11 => {name => "force", unit => "N"},
- 12 => {name => "frequency", unit => "Hz"},
- 13 => {name => "heat_flux_density", unit => "W/m2"},
- 14 => {name => "impulse", unit => "Ns"},
- 15 => {name => "luminance_intensity", unit => "lux"},
- 16 => {name => "magnetic_field_strength", unit => "A/m"},
- 17 => {name => "mass", unit => "kg"},
- 18 => {name => "mass_density", unit => "kg/m2"},
- 19 => {name => "mass_flow", unit => "kg/s"},
- 20 => {name => "power", unit => "W"},
- 21 => {name => "pressure", unit => "Pa"},
- 22 => {name => "relative_humidity", unit => "%"},
- 23 => {name => "resistance", unit => "Ohm"},
- 24 => {name => "temperature", unit => "C"},
- 25 => {name => "time", unit => "s"},
- 26 => {name => "torque", unit => "Nm"},
- 27 => {name => "velocity", unit => "m/s"},
- 28 => {name => "voltage", unit => "V"},
- 29 => {name => "volume", unit => "m3"},
- 30 => {name => "volumetric_flow", unit => "m3/s"},
- );
- # Initialize
- sub
- EnOcean_Initialize($)
- {
- my ($hash) = @_;
- my %subTypeList;
- my @subTypeList;
- foreach my $eep (keys %EnO_eepConfig){
- push @subTypeList, $EnO_eepConfig{$eep}{attr}{subType};
- }
- my $subTypeList = join(",", sort grep { !$subTypeList{$_}++ } @subTypeList);
- $hash->{AutoCreate} = {"EnO.*" => {ATTR => "creator:autocreate", FILTER => "%NAME"}};
- $hash->{noAutocreatedFilelog} = 1;
- $hash->{Match} = "^EnOcean:";
- $hash->{DefFn} = "EnOcean_Define";
- $hash->{DeleteFn} = "EnOcean_Delete";
- $hash->{UndefFn} = "EnOcean_Undef";
- $hash->{ParseFn} = "EnOcean_Parse";
- $hash->{SetFn} = "EnOcean_Set";
- #$hash->{StateFn} = "EnOcean_State";
- $hash->{GetFn} = "EnOcean_Get";
- $hash->{NotifyFn} = "EnOcean_Notify";
- $hash->{AttrFn} = "EnOcean_Attr";
- $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 dummy:0,1 " .
- "showtime:1,0 " .
- "actualTemp angleMax:slider,-180,20,180 alarmAction " .
- "angleMin:slider,-180,20,180 " .
- "angleTime setCmdTrigger:man,refDev blockUnknownMSC:no,yes blockMotion:no,yes " .
- "blockTemp:no,yes blockDisplay:no,yes blockDateTime:no,yes " .
- "blockTimeProgram:no,yes blockOccupancy:no,yes blockSetpointTemp:no,yes " .
- "blockFanSpeed:no,yes blockKey:no,yes comMode:confirm,biDir,uniDir creator:autocreate,manual " .
- "daylightSavingTime:supported,not_supported dataEnc:VAES,AES-CBC " .
- "defaultChannel:" . join(",", @EnO_defaultChannel) . " " .
- "demandRespAction demandRespRefDev demandRespMax:A0,AI,B0,BI,C0,CI,D0,DI ".
- "demandRespMin:A0,AI,B0,BI,C0,CI,D0,DI demandRespRandomTime " .
- "demandRespThreshold:slider,0,1,15 demandRespTimeoutLevel:max,last destinationID " .
- "devChannel devMode:master,slave devUpdate:off,auto,demand,polling,interrupt " .
- "dimMax dimMin dimValueOn disable:0,1 disabledForIntervals " .
- "displayContent:default,humidity,off,setpointTemp,tempertureExtern,temperatureIntern,time,no_change " .
- "displayOrientation:0,90,180,270 " .
- "eep gpDef gwCmd:" . join(",", sort @EnO_gwCmd) . " humitity humidityRefDev " .
- "keyRcv keySnd macAlgo:no,3,4 measurementCtrl:disable,enable " .
- "manufID:" . join(",", sort keys %EnO_manuf) . " " .
- "model:" . join(",", @EnO_models) . " " .
- "observe:on,off observeCmdRepetition:1,2,3,4,5 observeErrorAction observeInterval observeLogic:and,or " .
- #observeCmds observeExeptions
- "observeRefDev pidActorErrorAction:errorPos,freeze pidActorCallBeforeSetting pidActorErrorPos " .
- "pidActorLimitLower pidActorLimitUpper pidCtrl:on,off pidDeltaTreshold pidFactor_D pidFactor_I " .
- "pidFactor_P pidIPortionCallBeforeSetting pidSensorTimeout " .
- "pollInterval postmasterID productID rampTime rcvRespAction ".
- "releasedChannel:A,B,C,D,I,0,auto repeatingAllowed:yes,no remoteCode remoteEEP remoteID remoteManufID " .
- "remoteManagement:client,manager,off rlcAlgo:no,2++,3++ rlcRcv rlcSnd rlcTX:true,false " .
- "reposition:directly,opens,closes rltRepeat:16,32,64,128,256 rltType:1BS,4BS " .
- "scaleDecimals:0,1,2,3,4,5,6,7,8,9 scaleMax scaleMin secMode:rcv,snd,bidir " .
- "secLevel:encapsulation,encryption,off sendDevStatus:no,yes sensorMode:switch,pushbutton " .
- "serviceOn:no,yes settingAccuracy:high,low setpointRefDev setpointSummerMode:slider,0,5,100 " .
- "setpointTempRefDev shutTime shutTimeCloses subDef " .
- "subDef0 subDefI subDefA subDefB subDefC subDefD subDefH subDefW " .
- "subType:$subTypeList subTypeSet:$subTypeList subTypeReading:$subTypeList " .
- "summerMode:off,on switchMode:switch,pushbutton " .
- "switchHysteresis switchType:direction,universal,channel,central " .
- "teachMethod:1BS,4BS,confirm,GP,RPS,smartAck,STE,UTE temperatureRefDev " .
- "temperatureScale:C,F,default,no_change timeNotation:12,24,default,no_change " .
- "timeProgram1 timeProgram2 timeProgram3 timeProgram4 trackerWakeUpCycle:10,20,30,40,60,120,180,240,3600,86400 updateState:default,yes,no " .
- "uteResponseRequest:yes,no " .
- "wakeUpCycle:" . join(",", keys %wakeUpCycle) . " " .
- $readingFnAttributes;
- for (my $i = 0; $i < @EnO_ptm200btn; $i++) {
- $EnO_ptm200btn{$EnO_ptm200btn[$i]} = "$i:30";
- }
- $EnO_ptm200btn{released} = "0:20";
- if ($cryptFunc == 1){
- Log3 undef, 2, "EnOcean Cryptographic functions available.";
- } else {
- Log3 undef, 2, "EnOcean Cryptographic functions are not available.";
- }
- return undef;
- }
- # Define
- sub EnOcean_Define($$) {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- my $name = $hash->{NAME};
- my ($autocreateFilelog, $autocreateHash, $autocreateName, $autocreateDeviceRoom, $autocreateWeblinkRoom) =
- ('./log/' . $name . '-%Y.log', undef, 'autocreate', 'EnOcean', 'Plots');
- my ($cmd, $eep, $ret);
- my $filelogName = "FileLog_$name";
- $def = "00000000";
- if(@a > 2 && @a < 5) {
- # find autocreate device
- while (($autocreateName, $autocreateHash) = each(%defs)) {
- last if ($defs{$autocreateName}{TYPE} eq "autocreate");
- }
- $autocreateDeviceRoom = AttrVal($autocreateName, "device_room", $autocreateDeviceRoom);
- $autocreateDeviceRoom = 'EnOcean' if ($autocreateDeviceRoom eq '%TYPE');
- $autocreateDeviceRoom = $name if ($autocreateDeviceRoom eq '%NAME');
- $autocreateDeviceRoom = AttrVal($name, "room", $autocreateDeviceRoom);
- if ($init_done) {
- Log3 $name, 2, "EnOcean define " . join(' ', @a);
- if (!defined(AttrVal($autocreateName, "disable", undef)) && !exists($defs{$filelogName})) {
- # create FileLog
- $autocreateFilelog = $attr{$autocreateName}{filelog} if (exists $attr{$autocreateName}{filelog});
- $autocreateFilelog =~ s/%NAME/$name/g;
- $cmd = "$filelogName FileLog $autocreateFilelog $name";
- Log3 $filelogName, 2, "EnOcean define $cmd";
- $ret = CommandDefine(undef, $cmd);
- if($ret) {
- Log3 $filelogName, 2, "EnOcean ERROR: $ret";
- } else {
- $attr{$filelogName}{room} = $autocreateDeviceRoom;
- $attr{$filelogName}{logtype} = 'text';
- }
- }
- }
- if ($a[2] eq "getNextID") {
- AssignIoPort($hash) if (!exists $hash->{IODev});
- if (exists $hash->{OLDDEF}) {
- delete $modules{EnOcean}{defptr}{$hash->{OLDDEF}};
- }
- $hash->{DEF} = $def;
- $def = EnOcean_CheckSenderID("getNextID", $hash->{IODev}{NAME}, "00000000");
- $hash->{DEF} = $def;
- $modules{EnOcean}{defptr}{$def} = $hash;
- $attr{$name}{manufID} = "7FF" if (!exists $attr{$name}{manufID});
- $attr{$name}{room} = $autocreateDeviceRoom;
- $attr{$name}{subType} = "raw" if (!exists $attr{$name}{subType});
- } elsif ($a[2] =~ m/^[A-Fa-f0-9]{8}$/i) {
- # DestinationID
- $def = uc($a[2]);
- $hash->{DEF} = $def;
- if (defined($a[3]) && $a[3] =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i) {
- # EEP
- my ($rorg, $func, $type) = (uc($1), uc($2), uc($3));
- $rorg = "F6" if ($rorg eq "05");
- $rorg = "D5" if ($rorg eq "06");
- $rorg = "A5" if ($rorg eq "07");
- $eep = "$rorg.$func.$type";
- if (exists $EnO_eepConfig{$eep}) {
- if ($eep eq 'A5.3F.00') {
- my ($rltHash, $rltName);
- foreach my $dev (keys %defs) {
- next if ($defs{$dev}{TYPE} ne 'EnOcean');
- next if (!exists($attr{$dev}{subType}));
- next if ($attr{$dev}{subType} ne 'radioLinkTest');
- $rltHash = $defs{$dev};
- $rltName = $rltHash->{NAME};
- last;
- }
- return "Radio Link Test device already defined, use $rltName" if ($rltHash);
- }
- AssignIoPort($hash) if (!exists $hash->{IODev});
- if (exists $hash->{OLDDEF}) {
- delete $modules{EnOcean}{defptr}{$hash->{OLDDEF}};
- }
- $modules{EnOcean}{defptr}{$def} = $hash;
- if (exists($attr{$name}{eep}) && $attr{$name}{eep} ne "$rorg-$func-$type") {
- delete $attr{$name};
- $attr{$filelogName}{logtype} = $EnO_eepConfig{$eep}{GPLOT} . 'text'
- if (exists $attr{$filelogName}{logtype});
- # delete SVG devices
- my ($weblinkName, $weblinkHash);
- while (($weblinkName, $weblinkHash) = each(%defs)) {
- if ($weblinkName =~ /^SVG_$name.*/) {
- CommandDelete(undef, $weblinkName);
- Log3 $hash->{NAME}, 2, "EnOcean $weblinkName deleted";
- }
- }
- }
- $attr{$name}{eep} = "$rorg-$func-$type";
- $attr{$name}{manufID} = "7FF" if (!exists $attr{$name}{manufID});
- $attr{$name}{room} = $autocreateDeviceRoom;
- foreach my $attrCntr (keys %{$EnO_eepConfig{$eep}{attr}}) {
- if ($attrCntr eq "subDef") {
- if (!exists $attr{$name}{$attrCntr}) {
- $attr{$name}{$attrCntr} = EnOcean_CheckSenderID($EnO_eepConfig{$eep}{attr}{$attrCntr}, $hash->{IODev}{NAME}, "00000000");
- }
- } else {
- $attr{$name}{$attrCntr} = $EnO_eepConfig{$eep}{attr}{$attrCntr};
- }
- }
- EnOcean_CreateSVG(undef, $hash, $a[3]);
- } else {
- return "EEP $rorg-$func-$type not supported";
- }
- } elsif (defined($a[3]) && $a[3] =~ m/^EnOcean:.*/) {
- # autocreate: parse received device telegram
- AssignIoPort($hash) if (!exists $hash->{IODev});
- $modules{EnOcean}{defptr}{$def} = $hash;
- my @msg = split(':', $a[3]);
- my $packetType = hex $msg[1];
- if ($packetType == 1) {
- my ($data, $rorg, $status);
- #EnOcean:PacketType:RORG:MessageData:SourceID:Status:OptionalData
- (undef, undef, $rorg, $data, undef, $status, undef) = @msg;
- $attr{$name}{subType} = $EnO_rorgname{$rorg};
- if ($attr{$name}{subType} eq "switch") {
- my $nu = (hex($status) & 0x10) >> 4;
- my $t21 = (hex($status) & 0x20) >> 5;
- $attr{$name}{manufID} = "7FF";
- if ($t21 && $nu) {
- $attr{$name}{eep} = "F6-02-01";
- readingsSingleUpdate($hash, "teach", "RPS teach-in accepted EEP F6-02-01 Manufacturer: no ID", 1);
- $attr{$name}{teachMethod} = 'RPS';
- Log3 $name, 2, "EnOcean $name teach-in EEP F6-02-01 Manufacturer: no ID";
- } elsif (!$t21 && $nu) {
- $attr{$name}{eep} = "F6-03-01";
- readingsSingleUpdate($hash, "teach", "RPS teach-in accepted EEP F6-03-01 Manufacturer: no ID", 1);
- $attr{$name}{teachMethod} = 'RPS';
- Log3 $name, 2, "EnOcean $name teach-in EEP F6-03-01 Manufacturer: no ID";
- } elsif ($t21 && !$nu) {
- $attr{$name}{subType} = "windowHandle";
- $attr{$name}{eep} = "F6-10-00";
- readingsSingleUpdate($hash, "teach", "RPS teach-in accepted EEP F6-10-00 Manufacturer: no ID", 1);
- $attr{$name}{teachMethod} = 'RPS';
- Log3 $name, 2, "EnOcean $name teach-in EEP F6-10-00 Manufacturer: no ID";
- }
- } elsif ($attr{$name}{subType} eq "contact" && hex($data) & 8) {
- $attr{$name}{eep} = "D5-00-01";
- $attr{$name}{manufID} = "7FF";
- readingsSingleUpdate($hash, "teach", "1BS teach-in accepted EEP D5-00-01 Manufacturer: no ID", 1);
- $attr{$name}{teachMethod} = '1BS';
- Log3 $name, 2, "EnOcean $name teach-in EEP D5-00-01 Manufacturer: no ID";
- } elsif ($attr{$name}{subType} eq "4BS" && hex(substr($data, 6, 2)) & 8) {
- $hash->{helper}{teachInWait} = "4BS";
- readingsSingleUpdate($hash, "teach", "4BS teach-in is missing", 1);
- Log3 $name, 2, "EnOcean $name 4BS teach-in is missing";
- } elsif ($attr{$name}{subType} eq "UTE") {
- $hash->{helper}{teachInWait} = "UTE";
- } elsif ($attr{$name}{subType} eq "VLD") {
- $hash->{helper}{teachInWait} = "UTE";
- readingsSingleUpdate($hash, "teach", "UTE teach-in is missing", 1);
- Log3 $name, 2, "EnOcean $name UTE teach-in is missing";
- } elsif ($attr{$name}{subType} eq "MSC") {
- readingsSingleUpdate($hash, "teach", "MSC not supported", 1);
- Log3 $name, 2, "EnOcean $name MSC not supported";
- } elsif ($attr{$name}{subType} =~ m/^SEC|ENC$/) {
- $hash->{helper}{teachInWait} = "STE";
- readingsSingleUpdate($hash, "teach", "STE teach-in is missing", 1);
- Log3 $name, 2, "EnOcean $name STE teach-in is missing";
- } elsif ($attr{$name}{subType} =~ m/^GPCD|GPSD$/) {
- $hash->{helper}{teachInWait} = "GPTI";
- readingsSingleUpdate($hash, "teach", "GP teach-in is missing", 1);
- Log3 $name, 2, "EnOcean $name GP teach-in is missing";
- }
- } elsif ($packetType == 4) {
- $hash->{helper}{smartAckLearnWait} = $name;
- } elsif ($packetType == 7) {
- # remote management
- #EnOcean:PacketType:RORG:MessageData:SourceID:DestinationID:FunctionNumber:ManufacturerID:RSSI:Delay
- if (hex($msg[6]) < 0x600) {
- $attr{$name}{remoteManagement} = 'client';
- # unlock device to send acknowledgment telegram
- $hash->{RemoteClientUnlock} = 1;
- my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 1, 'EnOcean_cdmClearHashVal', \%functionHash, 0);
- } else {
- $attr{$name}{remoteManagement} = 'manager';
- }
- $attr{$name}{eep} = 'C5-00-00';
- $attr{$name}{manufID} = substr($msg[7], 1);
- $attr{$name}{remoteEEP} = 'C5-00-00';
- $attr{$name}{remoteID} = $msg[4];
- $attr{$name}{remoteManufID} = substr($msg[7], 1);
- $attr{$name}{subType} = 'remote';
- $modules{EnOcean}{defptr}{$msg[4]} = $hash;
- }
- EnOcean_Parse($hash, $a[3]);
- if (exists $attr{$name}{eep}) {
- $attr{$name}{eep} =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i;
- $eep = uc("$1.$2.$3");
- if (exists($attr{$filelogName}{logtype}) && exists($EnO_eepConfig{$eep}{GPLOT})) {
- $attr{$filelogName}{logtype} = $EnO_eepConfig{$eep}{GPLOT} . 'text';
- EnOcean_CreateSVG(undef, $hash, undef);
- }
- }
- } else {
- # no device infos
- AssignIoPort($hash) if (!exists $hash->{IODev});
- # assign defptr
- if (exists $hash->{OLDDEF}) {
- delete $modules{EnOcean}{defptr}{$hash->{OLDDEF}};
- }
- $modules{EnOcean}{defptr}{$def} = $hash;
- $attr{$name}{manufID} = "7FF" if (!exists $attr{$name}{manufID});
- $attr{$name}{room} = $autocreateDeviceRoom;
- $attr{$name}{subType} = "raw" if (!exists $attr{$name}{subType});
- }
- } elsif ($a[2] =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i) {
- # EEP
- my ($rorg, $func, $type) = (uc($1), uc($2), uc($3));
- $rorg = "F6" if ($rorg eq "05");
- $rorg = "D5" if ($rorg eq "06");
- $rorg = "A5" if ($rorg eq "07");
- $eep = "$rorg.$func.$type";
- if (exists $EnO_eepConfig{$eep}) {
- if ($eep eq 'A5.3F.00') {
- # radio link test device
- my ($rltHash, $rltName);
- foreach my $dev (keys %defs) {
- next if ($defs{$dev}{TYPE} ne 'EnOcean');
- next if (!exists($attr{$dev}{subType}));
- next if ($attr{$dev}{subType} ne 'radioLinkTest');
- $rltHash = $defs{$dev};
- $rltName = $rltHash->{NAME};
- last;
- }
- return "Radio Link Test device already defined, use $rltName" if ($rltHash);
- }
- AssignIoPort($hash) if (!exists $hash->{IODev});
- if (exists($hash->{OLDDEF}) && $hash->{OLDDEF} =~ m/^[A-Fa-f0-9]{8}$/i) {
- delete $modules{EnOcean}{defptr}{$hash->{OLDDEF}};
- if ($hash->{DEF} =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i) {
- $def = $hash->{OLDDEF};
- $hash->{DEF} = $hash->{OLDDEF};
- }
- } else {
- $hash->{DEF} = $def;
- if ($eep eq 'A5.3F.00') {
- $attr{$name}{subDef} = EnOcean_CheckSenderID("getNextID", $hash->{IODev}{NAME}, "00000000");
- } elsif ($eep eq 'C5.00.00') {
- # remote management
- } else {
- $def = EnOcean_CheckSenderID("getNextID", $hash->{IODev}{NAME}, "00000000");
- $hash->{DEF} = $def;
- }
- }
- $modules{EnOcean}{defptr}{$def} = $hash;
- if (exists($attr{$name}{eep}) && $attr{$name}{eep} ne "$rorg-$func-$type") {
- delete $attr{$name};
- if (exists $attr{$filelogName}{logtype}) {
- if (exists$EnO_eepConfig{$eep}{GPLOT}) {
- $attr{$filelogName}{logtype} = $EnO_eepConfig{$eep}{GPLOT} . 'text';
- } else {
- $attr{$filelogName}{logtype} = 'text';
- }
- }
- }
- $attr{$name}{eep} = "$rorg-$func-$type";
- $attr{$name}{manufID} = "7FF" if (!exists $attr{$name}{manufID});
- $attr{$name}{room} = $autocreateDeviceRoom;
- foreach my $attrCntr (keys %{$EnO_eepConfig{$eep}{attr}}) {
- if ($attrCntr ne "subDef") {
- $attr{$name}{$attrCntr} = $EnO_eepConfig{$eep}{attr}{$attrCntr};
- }
- }
- EnOcean_CreateSVG('del', $hash, $a[2]);
- } else {
- return "EEP $rorg-$func-$type not supported";
- }
- } else {
- return "wrong syntax: define <name> EnOcean <8-digit-hex-code> [<EEP>]|getNextID|<EEP>";
- }
- } else {
- return "wrong syntax: define <name> EnOcean <8-digit-hex-code> [<EEP>]|getNextID|<EEP>";
- }
- # device specific actions
- if (exists($attr{$name}{subType}) && $attr{$name}{subType} =~ m/^hvac\.0(1|4)$/) {
- # pid parameter
- @{$hash->{helper}{calcPID}} = (undef, $hash, 'defined');
- $hash->{helper}{stopped} = 0;
- #$hash->{helper}{adjust} = '';
- }
- # all notifys needed
- #$hash->{NOTIFYDEV} = "global";
- Log3 $name, 5, "EnOcean_define for device $name executed.";
- return undef;
- }
- # Get
- sub EnOcean_Get($@)
- {
- my ($hash, @a) = @_;
- return "no get value specified" if (@a < 2);
- my $name = $hash->{NAME};
- if (IsDisabled($name)) {
- Log3 $name, 4, "EnOcean $name get commands disabled.";
- return;
- }
- my $cmdID;
- my $cmdList = "";
- my $data;
- my $destinationID = AttrVal($name, "destinationID", undef);
- if (AttrVal($name, "comMode", "uniDir") eq "biDir") {
- $destinationID = defined(AttrVal($name, "subDef", undef)) ? $hash->{DEF} : "FFFFFFFF";
- $destinationID = "FFFFFFFF" if (uc(AttrVal($name, "subDef", $hash->{DEF})) eq uc($hash->{DEF}));
- } elsif (!defined $destinationID || $destinationID eq "multicast") {
- $destinationID = "FFFFFFFF";
- } elsif ($destinationID eq "unicast") {
- $destinationID = defined(AttrVal($name, "subDef", undef)) ? $hash->{DEF} : "FFFFFFFF";
- $destinationID = "FFFFFFFF" if (uc(AttrVal($name, "subDef", $hash->{DEF})) eq uc($hash->{DEF}));
- } elsif ($destinationID !~ m/^[\dA-Fa-f]{8}$/) {
- return "DestinationID $destinationID wrong, choose <8-digit-hex-code>.";
- }
- $destinationID = uc($destinationID);
- my $eep = uc(AttrVal($name, "eep", "00-00-00"));
- if ($eep =~ m/^([A-Fa-f0-9]{2})-([A-Fa-f0-9]{2})-([A-Fa-f0-9]{2})$/i) {
- $eep = (((hex($1) << 6) | hex($2)) << 7) | hex($3);
- } else {
- $eep = (((hex("FF") << 6) | hex("3F")) << 7) | hex("7F");
- }
- my $manufID = uc(AttrVal($name, "manufID", ""));
- my $model = AttrVal($name, "model", "");
- my $packetType = 1;
- $packetType = 0x0A if (ReadingsVal($hash->{IODev}{NAME}, "mode", "00") eq "01");
- my $remoteID = AttrVal($name, "remoteID", undef);
- my $remoteManufID = uc(AttrVal($name, "remoteManufID", AttrVal($name, "manufID", "")));
- my $rorg;
- my $status = '00';
- my $st = AttrVal($name, "subType", "");
- my $stSet = AttrVal($name, "subTypeSet", undef);
- if (defined $stSet) {$st = $stSet;}
- my $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
- if ($subDef !~ m/^[\dA-F]{8}$/) {return "SenderID $subDef wrong, choose <8-digit-hex-code>.";}
- my $timeNow = TimeNow();
- if (AttrVal($name, "remoteManagement", "off") eq "manager") {
- # Remote Management
- $cmdList = "remoteDevCfg remoteFunctions:noArg remoteID:noArg remoteLinkCfg remoteLinkTableInfo:noArg remoteLinkTable remoteLinkTableGP remotePing:noArg remoteProductID:noArg remoteRepeater:noArg remoteStatus:noArg ";
- }
- # control get actions
- # $updateState = -1: no get commands available e. g. sensors
- # 0: execute get commands
- # 1: execute get commands and and update reading state
- # 2: execute get commands delayed
- my $updateState = 1;
- #Log3 $name, 5, "EnOcean $name EnOcean_Get command: " . join(" ", @a);
- shift @a;
- for (my $i = 0; $i < @a; $i++) {
- my $cmd = $a[$i];
- if ($cmd eq "remoteID") {
- $cmdID = 4;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- shift(@a);
- $data = sprintf "0004%04X%06X", $manufID, ($eep << 3);
- $destinationID = "FFFFFFFF";
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x604}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x604);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "remotePing") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- $cmdID = 6;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- shift(@a);
- $data = sprintf "0006%04X", $manufID;
- $destinationID = $remoteID;
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x606}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x606);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "remoteFunctions") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- $cmdID = 7;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- shift(@a);
- $data = sprintf "0007%04X", $manufID;
- $destinationID = $remoteID;
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x607}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x607);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "remoteStatus") {
- $cmdID = 8;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- shift(@a);
- $data = sprintf "0008%04X", $manufID;
- if (defined $remoteID) {
- $destinationID = $remoteID;
- } else {
- $destinationID = 'F' x 8;
- }
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x608}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x608);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "remoteLinkTableInfo") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- $cmdID = 0x210;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- shift(@a);
- $data = sprintf "%04X%04X", $cmdID, $manufID;
- $destinationID = $remoteID;
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x810}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x810);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "remoteLinkTable") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- $cmdID = 0x211;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- my $startRef;
- my $endRef;
- my $direction;
- 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}$/) {
- shift(@a);
- $direction = shift(@a);
- $direction = $direction eq 'out' ? '80' : '00';
- $startRef = uc(shift(@a));
- $endRef = uc(shift(@a));
- ($startRef, $endRef) = ($endRef, $startRef) if (hex($startRef) > hex($endRef));
- } else {
- return "Wrong parameter or direction/startRef/endRef not defined.";
- }
- $data = sprintf "%04X%04X%2s%2s%2s", $cmdID, $manufID, $direction, $startRef, $endRef;
- $destinationID = $remoteID;
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x811}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x811);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "remoteLinkTableGP") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- $cmdID = 0x213;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- my $direction;
- my $index;
- if (defined($a[1]) && defined($a[2]) && $a[1] =~ m/^in|out$/ && $a[2] =~ m/^[\dA-Fa-f]{2}$/) {
- shift(@a);
- $direction = shift(@a);
- $direction = $direction eq 'out' ? '80' : '00';
- $index = uc(shift(@a));
- } else {
- return "Wrong parameter or direction/index not defined.";
- }
- $data = sprintf "%04X%04X%2s%2s", $cmdID, $manufID, $direction, $index;
- $destinationID = $remoteID;
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x813}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x813);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "remoteProductID") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- $cmdID = 0x227;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- shift(@a);
- $data = sprintf "%04X%04X", $cmdID, $manufID;
- $destinationID = $remoteID;
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x827}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x827);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 5.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "remoteDevCfg") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- $cmdID = 0x230;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- my $startRef;
- my $endRef;
- my $paraLen = '00';
- if (defined($a[1]) && defined($a[2]) && $a[1] =~ m/^[\dA-Fa-f]{4}$/ && $a[2] =~ m/^[\dA-Fa-f]{4}$/) {
- shift(@a);
- $startRef = uc(shift(@a));
- $endRef = uc(shift(@a));
- ($startRef, $endRef) = ($endRef, $startRef) if (hex($startRef) > hex($endRef));
- if (defined($a[0]) && $a[0] =~ m/^[\dA-Fa-f]{2}$/) {
- $paraLen = uc(shift(@a));
- }
- } else {
- return "Wrong parameter or startRef/endRef not defined.";
- }
- $data = sprintf "%04X%04X%4s%4s%2s", $cmdID, $manufID, $startRef, $endRef, $paraLen;
- $destinationID = $remoteID;
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x830}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x830);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "remoteLinkCfg") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- $cmdID = 0x232;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- my $direction;
- my $idx;
- my $startRef;
- my $endRef;
- my $paraLen = '00';
- if (defined($a[1]) && defined($a[2]) && defined($a[3]) && defined($a[4])
- && $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}$/) {
- shift(@a);
- $direction = shift(@a);
- $direction = $direction eq 'out' ? '80' : '00';
- $idx = uc(shift(@a));
- $startRef = uc(shift(@a));
- $endRef = uc(shift(@a));
- ($startRef, $endRef) = ($endRef, $startRef) if (hex($startRef) > hex($endRef));
- if (defined($a[0]) && $a[0] =~ m/^[\dA-Fa-f]{2}$/) {
- $paraLen = uc(shift(@a));
- }
- } else {
- return "Wrong parameter or startRef/endRef not defined.";
- }
- $data = sprintf "%04X%04X%2s%2s%4s%4s%2s", $cmdID, $manufID, $direction, $idx, $startRef, $endRef, $paraLen;
- $destinationID = $remoteID;
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x832}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x832);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "remoteRepeater") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- $cmdID = 0x250;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- shift(@a);
- $data = sprintf "%04X%04X", $cmdID, $manufID;
- $destinationID = $remoteID;
- $status = '0F';
- $hash->{IODev}{helper}{remoteAnswerWait}{0x850}{hash} = $hash;
- my %functionHash = (hash => $hash, param => 0x850);
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 5.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($st eq "switch.05") {
- # Dual Channel Switch Actuator
- # (A5-11-05)
- $rorg = "A5";
- shift(@a);
- $updateState = 0;
- if ($cmd eq "state") {
- # query state
- Log3 $name, 3, "EnOcean get $name $cmd";
- $data = "00000008";
- } else {
- $cmdList .= "state:noArg";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- } elsif ($st eq "lightCtrl.01") {
- # Central Command, Extended Lighting-Control
- # (A5-38-09)
- $rorg = "A5";
- shift(@a);
- $updateState = 0;
- if ($cmd eq "state") {
- # query state
- Log3 $name, 3, "EnOcean get $name $cmd";
- $data = "00000008";
- } else {
- $cmdList .= "state:noArg";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- } elsif ($st eq "actuator.01") {
- # Electronic switches and dimmers with Energy Measurement and Local Control
- # (D2-01-00 - D2-01-12)
- $rorg = "D2";
- shift(@a);
- my $channel;
- if ($cmd !~ m/^roomCtrlMode$/) {
- $channel = shift(@a);
- $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
- if (!defined $channel || $channel eq "all") {
- $channel = 30;
- } elsif ($channel eq "input" || $channel + 0 == 31) {
- $channel = 31;
- } elsif ($channel + 0 >= 30) {
- $channel = 30;
- } elsif ($channel + 0 >= 0 && $channel + 0 <= 29) {
- } else {
- return "$cmd <channel> wrong, choose 0...29|all|input.";
- }
- }
- if ($cmd eq "state") {
- $cmdID = 3;
- Log3 $name, 3, "EnOcean get $name $cmd $channel";
- $data = sprintf "%02X%02X", $cmdID, $channel;
- } elsif ($cmd eq "measurement") {
- $cmdID = 6;
- my $query = shift(@a);
- if (!defined $query) {
- return "$cmd <channel> <query> wrong, choose 0...30|all|input energy|power.";
- } elsif ($query eq "energy") {
- $query = 0;
- } elsif ($query eq "power") {
- $query = 1;
- } else {
- return "$cmd <channel> <query> wrong, choose 0...30|all|input energy|power.";
- }
- Log3 $name, 3, "EnOcean get $name $cmd $channel $query";
- $data = sprintf "%02X%02X", $cmdID, $query << 5 | $channel;
- } elsif ($cmd eq "roomCtrlMode") {
- Log3 $name, 3, "EnOcean get $name $cmd";
- $data = '09';
- } elsif ($cmd eq "settings") {
- $cmdID = 10;
- Log3 $name, 3, "EnOcean get $name $cmd $channel";
- $data = sprintf "%02X%02X", $cmdID, $channel;
- } elsif ($cmd eq "special") {
- $rorg = "D1";
- my $query = shift(@a);
- if ($manufID eq "033") {
- if (!defined $query) {
- return "$cmd <channel> <query> wrong, choose health|load|voltage|serialNumber.";
- } elsif ($query eq "health") {
- $query = 7;
- } elsif ($query eq "load") {
- $query = 8;
- } elsif ($query eq "voltage") {
- $query = 9;
- } elsif ($query eq "serialNumber") {
- $query = 0x81;
- } else {
- return "$cmd <channel> <query> wrong, choose health|load|voltage|serialNumber.";
- }
- $data = sprintf "0331%02X", $query;
- Log3 $name, 3, "EnOcean get $name $cmd $channel $query";
- readingsSingleUpdate($hash, "getParam", $query, 0);
- } elsif ($manufID eq "046") {
- if (!defined $query) {
- return "$cmd <channel> <query> wrong, choose reset|firmwareVersion|taughtInDevNum|taughtInDevID.";
- } elsif ($query eq "reset") {
- $query = 1;
- $data = sprintf "0046%02X", $query;
- } elsif ($query eq "firmwareVersion") {
- $query = 2;
- $data = sprintf "0046%02X", $query;
- } elsif ($query eq "taughtInDevNum") {
- $query = 4;
- $data = sprintf "0046%02X", $query;
- } elsif ($query eq "taughtInDevID") {
- $query = 6;
- my $taughtInDevNum = shift(@a);
- if (defined $taughtInDevNum && $taughtInDevNum =~ m/^\d+$/ && $taughtInDevNum >= 0 && $taughtInDevNum <= 23) {
- $data = sprintf "0046%02X%02X", $query, $taughtInDevNum;
- } else {
- return "Usage: taughtInDevID is not numeric or out of range";
- }
- } else {
- return "$cmd <channel> <query> wrong, choose reset|firmwareVersion|taughtInDevNum|taughtInDevID.";
- }
- Log3 $name, 3, "EnOcean get $name $cmd $channel $query";
- readingsSingleUpdate($hash, "getParam", $query, 0);
- }
- } else {
- if ($manufID =~ m/^033|046$/) {
- return "Unknown argument $cmd, choose one of " . $cmdList . "state measurement roomCtrlMode:noArg special settings";
- } else {
- return "Unknown argument $cmd, choose one of " . $cmdList . "state measurement roomCtrlMode:noArg settings";
- }
- }
- } elsif ($st =~ m/^blindsCtrl\.0[01]$/) {
- # Blinds Control for Position and Angle
- # (D2-05-xx)
- $rorg = "D2";
- shift(@a);
- $updateState = 0;
- if ($cmd eq "position") {
- # query position and angle
- $cmdID = 3;
- my $channel = shift(@a);
- if (!defined $channel) {
- $channel = AttrVal($name, "defaultChannel", 'all');
- $channel = $channel eq "all" ? 15 : $channel - 1;
- } elsif ($channel =~ m/^all$/) {
- $channel = 15;
- } elsif ($channel =~ m/^[1234]$/) {
- $channel -= 1;
- } else {
- return "$cmd parameter wrong, choose one of 1|2|3|4|all";
- }
- Log3 $name, 3, "EnOcean get $name $cmd";
- $data = sprintf "%02X", $channel << 4 | $cmdID;
- } else {
- $cmdList .= "position";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- } elsif ($st eq "multisensor.01") {
- #####
- # Multisensor Windows Handle
- # (D2-06-01)
- $rorg = "D2";
- shift(@a);
- $updateState = 2;
- my $waitingCmds = ReadingsVal($name, "waitingCmds", undef);
- if (defined $waitingCmds) {
- # check presence state
- $waitingCmds = ReadingsVal($name, "presence", "present") eq "absent" ? $waitingCmds & 0xDF | 32 : $waitingCmds & 0xDF;
- } else {
- $waitingCmds = ReadingsVal($name, "presence", "present") eq "absent" ? 32 : 0;
- }
- if ($cmd eq "config") {
- # query config
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds | 0x80, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "log") {
- # query log
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds | 0x40, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } else {
- $cmdList .= "config:noArg log:noArg";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- } elsif ($st eq "roomCtrlPanel.00") {
- # Room Control Panel
- # (D2-10-00 - D2-10-02)
- $rorg = "D2";
- shift(@a);
- $updateState = 2;
- if ($cmd eq "data") {
- # data request
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 1, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "config") {
- # configuration request
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 32, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "roomCtrl") {
- # room control setup request
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 128, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "timeProgram") {
- # time program request
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 256, 0);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } else {
- $cmdList .= "data:noArg config:noArg roomCtrl:noArg timeProgram:noArg";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- } elsif ($st eq "fanCtrl.00") {
- # Fan Control
- # (D2-20-00 - D2-20-02)
- $rorg = "D2";
- shift(@a);
- $updateState = 0;
- if ($cmd eq "state") {
- # query position and angle
- $data = "F6FFFFFF";
- Log3 $name, 3, "EnOcean get $name $cmd DATA: $data";
- } else {
- $cmdList .= "state:noArg";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- } elsif ($st eq "heatRecovery.00") {
- # heat recovery ventilation
- # (D2-50-00)
- $rorg = "D2";
- shift(@a);
- $updateState = 0;
- if ($cmd eq "basicState") {
- # query switch state
- $data = "00";
- Log3 $name, 3, "EnOcean get $name $cmd";
- } elsif ($cmd eq "extendedState") {
- # query switch state
- $data = "01";
- Log3 $name, 3, "EnOcean get $name $cmd";
- } else {
- $cmdList .= "basicState:noArg extendedState:noArg";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- } elsif ($st eq "valveCtrl.00" && AttrVal($name, "devMode", "master") eq "master") {
- # Valve Control
- # (D2-A0-01)
- $rorg = "D2";
- shift(@a);
- $updateState = 0;
- if ($cmd eq "state") {
- # query switch state
- $data = "00";
- readingsSingleUpdate($hash, "state", $cmd, 1);
- Log3 $name, 3, "EnOcean get $name $cmd";
- } else {
- $cmdList .= "state:noArg";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- } elsif ($st eq "remote") {
- return "Unknown argument $cmd, choose one of $cmdList";
- } else {
- # subtype does not support get commands
- if (AttrVal($name, "remoteManagement", "off") eq "manager") {
- return "Unknown argument $cmd, choose one of $cmdList";
- } else {
- return;
- }
- }
- if($updateState != 2) {
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, $status, $destinationID);
- }
- }
- }
- # Set
- sub EnOcean_Set($@)
- {
- my ($hash, @a) = @_;
- return "no set value specified" if (@a < 2);
- my $name = $hash->{NAME};
- if (IsDisabled($name)) {
- Log3 $name, 4, "EnOcean $name set commands disabled.";
- return;
- }
- my $cmdID;
- my $cmdList = "";
- my @cmdObserve = @a;
- my ($ctrl, $data, $err, $logLevel, $response);
- my $destinationID = AttrVal($name, "destinationID", undef);
- if (AttrVal($name, "comMode", "uniDir") eq "biDir") {
- $destinationID = defined(AttrVal($name, "subDef", undef)) ? $hash->{DEF} : "FFFFFFFF";
- $destinationID = "FFFFFFFF" if (uc(AttrVal($name, "subDef", $hash->{DEF})) eq uc($hash->{DEF}));
- } elsif (!defined $destinationID || $destinationID eq "multicast") {
- $destinationID = "FFFFFFFF";
- } elsif ($destinationID eq "unicast") {
- $destinationID = defined(AttrVal($name, "subDef", undef)) ? $hash->{DEF} : "FFFFFFFF";
- $destinationID = "FFFFFFFF" if (uc(AttrVal($name, "subDef", $hash->{DEF})) eq uc($hash->{DEF}));
- } elsif ($destinationID !~ m/^[\dA-Fa-f]{8}$/) {
- return "DestinationID $destinationID wrong, choose <8-digit-hex-code>.";
- }
- $destinationID = uc($destinationID);
- my $IODev = $hash->{IODev}{NAME};
- my $IOHash = $defs{$IODev};
- my $manufID = uc(AttrVal($name, "manufID", ""));
- my $model = AttrVal($name, "model", "");
- my $packetType = 1;
- $packetType = 0x0A if (ReadingsVal($IODev, "mode", "00") eq "01");
- my $remoteID = AttrVal($name, "remoteID", undef);
- my $remoteManufID = uc(AttrVal($name, "remoteManufID", AttrVal($name, "manufID", "")));
- my $rorg;
- my $sendCmd = 1;
- my $status = "00";
- my $st = AttrVal($name, "subType", "");
- my $stSet = AttrVal($name, "subTypeSet", undef);
- if (defined $stSet) {$st = $stSet;}
- my $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
- if ($subDef !~ m/^[\dA-F]{8}$/) {return "SenderID $subDef wrong, choose <8-digit-hex-code>.";}
- my $switchMode = AttrVal($name, "switchMode", "switch");
- my $timeNow = TimeNow();
- my $remoteManagement = AttrVal($name, "remoteManagement", "off");
- if ($remoteManagement eq "manager") {
- # Remote Management Manager
- $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 ";
- } elsif ($remoteManagement eq "client" || $st eq "remote") {
- # Remote Management Client
- $cmdList .= "remoteLock:noArg remoteUnlock ";
- }
- # control set actions
- # $updateState = -1: no set commands available e. g. sensors
- # 0: execute set commands
- # 1: execute set commands and and update reading state
- # 2: execute set commands delayed
- # 3: internal command
- my $updateState = AttrVal($name, "comMode", "uniDir") eq "uniDir" ? 1 : 0;
- my $updateStateAttr = AttrVal($name, "updateState", "default");
- shift @a;
- for (my $i = 0; $i < @a; $i++) {
- my $cmd = $a[$i];
- my ($cmd1, $cmd2);
- if ($cmd eq "remoteUnlock") {
- if ($remoteManagement eq "manager") {
- # manager
- $cmdID = 1;
- my $remoteCode = AttrVal($name, "remoteCode", undef);
- return "Security Code not defined, set attr $name remoteCode <00000001 ... FFFFFFFE>!" if (!defined($remoteCode));
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $data = sprintf "%04X%04X%s", $cmdID, $manufID, uc($remoteCode);
- if (defined $remoteID) {
- $destinationID = $remoteID;
- } else {
- $destinationID = 'F' x 8;
- }
- $updateState = 0;
- } else {
- # client
- my $remoteUnlockPeriod = 1800;
- if (defined $a[1]) {
- if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 1800) {
- $remoteUnlockPeriod = $a[1];
- shift(@a);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- $hash->{RemoteClientUnlock} = 1;
- my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + $remoteUnlockPeriod, 'EnOcean_cdmClearHashVal', \%functionHash, 0);
- $updateState = 3;
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($cmd eq "remoteLock") {
- if ($remoteManagement eq "manager") {
- # manager
- $cmdID = 2;
- my $remoteCode = AttrVal($name, "remoteCode", undef);
- return "Security Code not defined, set attr $name remoteCode <00000001 ... FFFFFFFE>!" if (!defined($remoteCode));
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $data = sprintf "%04X%04X%s", $cmdID, $manufID, uc($remoteCode);
- if (defined $remoteID) {
- $destinationID = $remoteID;
- } else {
- $destinationID = 'F' x 8;
- }
- $updateState = 0;
- } else {
- # client
- delete $hash->{RemoteClientUnlock};
- my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
- RemoveInternalTimer(\%functionHash);
- $updateState = 3;
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($cmd eq "remoteSetCode") {
- $cmdID = 3;
- my $remoteCode = AttrVal($name, "remoteCode", undef);
- return "Security Code not defined, set attr $name remoteCode <00000001 ... FFFFFFFE>!" if (!defined($remoteCode));
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $data = sprintf "%04X%04X%s", $cmdID, $manufID, uc($remoteCode);
- if (defined $remoteID) {
- $destinationID = $remoteID;
- } else {
- $destinationID = 'F' x 8;
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- } elsif ($cmd eq "remoteAction") {
- $cmdID = 5;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $data = sprintf "%04X%04X", $cmdID, $manufID;
- if (defined $remoteID) {
- $destinationID = $remoteID;
- } else {
- $destinationID = 'F' x 8;
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- } elsif ($cmd eq "remoteLinkTable") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- if (defined $a[1] && defined $a[2] && defined $a[3] && defined $a[4] && defined $a[5] &&
- $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}$/) {
- $cmdID = 0x212;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $a[4] =~ m/^(..)-(..)-(..)$/;
- $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]);
- splice(@a,0,5);
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- } else {
- return "Usage: $cmd arguments needed or wrong.";
- }
- } elsif ($cmd eq "remoteLinkTableGP") {
- # gpDef example: ch2:O:1:24:1:7:-40:1:40:1
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- if (defined $a[1] && defined $a[2] && defined $a[3] && $a[1] =~ m/^in|out$/ && $a[2] =~ m/^[\dA-Fa-f]{2}$/) {
- $cmdID = 0x214;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- my $direction = $a[1] eq 'out' ? '80' : '00';
- my $gpDef;
- my ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
- split(':', $a[3]);
- if ($channelDir eq "O" && $a[1] eq 'out' || $channelDir eq "I" && $a[1] eq 'in') {
- # add channel-, signal- and valuetype
- $gpDef .= substr(unpack('B8', pack('C', $channelType)), 6) .
- unpack('B8', pack('C', $signalType)) .
- substr(unpack('B8', pack('C', $valueType)), 6);
- if ($channelType == 1 || $channelType == 3) {
- # data, enumeration: add resolution
- $gpDef .= substr(unpack('B8', pack('C', $resolution)), 4);
- }
- if ($channelType == 1) {
- # data: add engineering and scaling
- $gpDef .= unpack('B8', pack('c', $engMin)) .
- substr(unpack('B8', pack('C', $scalingMin)), 4) .
- unpack('B8', pack('c', $engMax)) .
- substr(unpack('B8', pack('C', $scalingMax)), 4);
- }
- } else {
- return "Usage: Link Table GP Entry Direction wrong.";
- }
- if (length($gpDef) % 8) {
- # fill with trailing zeroes to x bytes
- $gpDef .= 0 x (8 - length($gpDef) % 8);
- }
- $data = sprintf "%04X%04X%s%s%s", $cmdID, $manufID, $a[1] eq 'out' ? '80' : '00', uc($a[2]), EnOcean_convBitToHex($gpDef);
- splice(@a,0,3);
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- } else {
- return "Usage: $cmd arguments needed or wrong.";
- }
- } elsif ($cmd eq "remoteLearnMode") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- if (defined $a[1]) {
- $cmdID = 0x220;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $destinationID = $remoteID;
- my $learnMode;
- my $cmdVal = 0;
- if ($a[1] eq 'in') {
- $learnMode = '00';
- shift(@a);
- } elsif ($a[1] eq 'out') {
- $learnMode = '40';
- shift(@a);
- } elsif ($a[1] eq 'off') {
- $learnMode = '80';
- shift(@a);
- } else {
- return "Usage: $cmd $a[1] argument unknown.";
- }
- if (defined $a[1] && $a[1] =~ m/^[\dA-Fa-f]{2}$/) {
- $cmdVal = $a[1];
- shift(@a);
- } else {
- return "Usage: $cmd <learnMode> argument needed or wrong.";
- }
- $data = sprintf "%04X%04X%s%s", $cmdID, $manufID, $learnMode, $cmdVal;
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- }
- } elsif ($cmd eq "remoteTeach") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- if (defined $a[1] && $a[1] =~ m/^[\dA-Fa-f]{2}$/) {
- $cmdID = 0x221;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $destinationID = $remoteID;
- $data = sprintf "%04X%04X%s", $cmdID, $manufID, $a[1];
- shift(@a);
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- if (!exists($hash->{IODev}{Teach})) {
- # enable teach-in receiving for 3 sec
- $hash->{IODev}{Teach} = 1;
- my %functionHash = (hash => $IOHash, param => 'Teach');
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 3, 'EnOcean_cdmClearHashVal', \%functionHash, 0);
- }
- } else {
- return "Usage: $cmd argument needed or wrong.";
- }
- } elsif ($cmd eq "remoteReset") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- if (defined $a[1] && $a[1] =~ m/^devCfg|linkTableIn|linkTableOut|no_change$/) {
- $cmdID = 0x224;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $destinationID = $remoteID;
- my %changeCmd = ('devCfg' => '80', 'linkTableIn' => '40', 'linkTableOut' => '20', 'no_change' => '00');
- $data = sprintf "%04X%04X%s", $cmdID, $manufID, $changeCmd{$a[1]};
- shift(@a);
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- } else {
- return "Usage: $cmd argument needed or wrong.";
- }
- } elsif ($cmd eq "remoteRLT") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- if (defined $a[1] && defined $a[2] && $a[1] =~ m/^off|on$/ && $a[2] =~ m/^[0-7][1-9A-Fa-f]$/) {
- $cmdID = 0x225;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $destinationID = $remoteID;
- my $rltMode = hex $a[2];
- $rltMode |= 0x80 if ($a[1] eq 'on');
- $data = sprintf "%04X%04X%02X", $cmdID, $manufID, $rltMode;
- splice(@a,0,2);
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- }
- } elsif ($cmd eq "remoteApplyChanges") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- if (defined $a[1] && $a[1] =~ m/^devCfg|linkTable|no_change$/) {
- $cmdID = 0x226;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $destinationID = $remoteID;
- my %changeCmd = ('devCfg' => '40', 'linkTable' => '80', 'no_change' => '00');
- $data = sprintf "%04X%04X%s", $cmdID, $manufID, $changeCmd{$a[1]};
- shift(@a);
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- } else {
- return "Usage: $cmd argument needed or wrong.";
- }
- } elsif ($cmd eq "remoteDevCfg") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- 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) {
- $cmdID = 0x231;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $destinationID = $remoteID;
- $data = sprintf "%04X%04X%s%02X%s", $cmdID, $manufID, uc($a[1]), length($a[2]) / 2, uc($a[2]);
- splice(@a,0,2);
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- } else {
- return "Usage: $cmd arguments needed or wrong.";
- }
- } elsif ($cmd eq "remoteLinkCfg") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- if (defined $a[1] && defined $a[2] && defined $a[3] && defined $a[4] &&
- $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]{2}[\dA-Fa-f]*$/ && length($a[4]) % 2 == 0) {
- $cmdID = 0x233;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- my $direction = $a[1] eq 'out' ? '80' : '00';
- $destinationID = $remoteID;
- $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]);
- splice(@a,0,4);
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- } else {
- return "Usage: $cmd arguments needed or wrong.";
- }
- } elsif ($cmd eq "remoteRepeater") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- if (defined $a[1] && defined $a[2] && defined $a[3] &&
- $a[1] =~ m/^on|off|filter$/ && $a[2] =~ m/^[1-2]$/ && $a[3] =~ m/^AND|OR$/) {
- $cmdID = 0x251;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- my %repFunc = ('on' => 0x40, 'off' => 0, 'filter' => 0x80);
- my $cmdVal = $repFunc{$a[1]} | ($a[2] == 2 ? 0x20 : 0x10) | ($a[3] eq 'OR' ? 8 : 0);
- $destinationID = $remoteID;
- $data = sprintf "%04X%04X%02X", $cmdID, $manufID, $cmdVal;
- splice(@a,0,3);
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- } else {
- return "Usage: $cmd arguments needed or wrong.";
- }
- } elsif ($cmd eq "remoteRepeaterFilter") {
- return "Attribute remoteID is missing, please define it." if (!defined $remoteID);
- if (defined $a[1] && defined $a[2] &&
- $a[1] =~ m/^apply|block|delete|deleteAll$/ && $a[2] =~ m/^destinationID|sourceID|rorg|rssi$/) {
- my %repFilterCtrl = ('deleteAll' => 0x30, 'delete' => 0x20, 'apply' => 0x10, 'block' => 0);
- my $cmdVal = $repFilterCtrl{$a[1]};
- my $cmdAttr;
- if (defined $a[3]) {
- if ($a[2] =~ m/^destinationID$/ && $a[3] =~ m/^[\dA-Fa-f]{8}$/) {
- $cmdVal |= 3;
- $cmdAttr = $a[3];
- } elsif ($a[2] =~ m/^sourceID$/ && $a[3] =~ m/^[\dA-Fa-f]{8}$/) {
- $cmdVal |= 0;
- $cmdAttr = $a[3];
- } elsif ($a[2] =~ m/^rorg$/ && $a[3] =~ m/^[\dA-Fa-f]{2}$/) {
- $cmdVal |= 1;
- $cmdAttr = '0' x 6 . $a[3];
- } elsif ($a[2] =~ m/^rssi$/ && $a[3] =~ m/^-?\d+$/ && abs($a[3]) >= 0 && abs($a[3]) <= 255) {
- $cmdVal |= 2;
- $cmdAttr = sprintf "000000%02X", abs($a[3]);
- } else {
- return "Usage: $cmd $a[2] argument wrong.";
- }
- } else {
- return "Usage: $cmd $a[2] argument needed.";
- }
- $cmdID = 0x252;
- $manufID = 0x7FF;
- $packetType = 7;
- $rorg = "C5";
- $destinationID = $remoteID;
- $data = sprintf "%04X%04X%02X%s", $cmdID, $manufID, $cmdVal, $cmdAttr;
- splice(@a,0,3);
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 0;
- } else {
- return "Usage: $cmd arguments needed or wrong.";
- }
- } elsif ($st eq "switch") {
- # Rocker Switch, simulate a PTM200 switch module
- # separate first and second action
- ($cmd1, $cmd2) = split(",", $cmd, 2);
- # check values
- if (!defined($EnO_ptm200btn{$cmd1}) || ($cmd2 && !defined($EnO_ptm200btn{$cmd2}))) {
- $cmdList .= join(":noArg ", sort keys %EnO_ptm200btn) . ':noArg';
- return SetExtensions($hash, $cmdList, $name, @a);
- }
- my $channelA = ReadingsVal($name, "channelA", undef);
- my $channelB = ReadingsVal($name, "channelB", undef);
- my $channelC = ReadingsVal($name, "channelC", undef);
- my $channelD = ReadingsVal($name, "channelD", undef);
- my $lastChannel = ReadingsVal($name, ".lastChannel", "A");
- my $releasedChannel = AttrVal($name, "releasedChannel", "auto");
- my $subDefA = AttrVal($name, "subDefA", $subDef);
- my $subDefB = AttrVal($name, "subDefB", $subDef);
- my $subDefC = AttrVal($name, "subDefC", $subDef);
- my $subDefD = AttrVal($name, "subDefD", $subDef);
- my $subDefI = AttrVal($name, "subDefI", $subDef);
- my $subDef0 = AttrVal($name, "subDef0", $subDef);
- my $switchType = AttrVal($name, "switchType", "direction");
- # first action
- if ($cmd1 eq "released") {
- if ($switchType eq "central") {
- if ($releasedChannel eq "auto") {
- if ($lastChannel =~ m/0|I/) {
- $releasedChannel = $lastChannel;
- } else {
- $releasedChannel = 0;
- }
- } elsif ($releasedChannel !~ m/0|I/) {
- $releasedChannel = 0;
- }
- } elsif ($switchType eq "channel") {
- if ($releasedChannel eq "auto") {
- if ($lastChannel =~ m/A|B|C|D/) {
- $releasedChannel = $lastChannel;
- } else {
- $releasedChannel = "A";
- }
- } elsif ($releasedChannel !~ m/A|B|C|D/) {
- $releasedChannel = "A";
- }
- }
- $subDef = AttrVal($name, "subDef" . $releasedChannel, $subDef);
- } elsif ($switchType eq "central") {
- if ($cmd1 =~ m/.0/) {
- $subDef = $subDef0;
- $lastChannel = 0;
- } elsif ($cmd1 =~ m/.I/) {
- $subDef = $subDefI;
- $lastChannel = "I";
- }
- } elsif ($switchType eq "channel") {
- $lastChannel = substr($cmd1, 0, 1);
- $subDef = AttrVal($name, "subDef" . $lastChannel, $subDefA);
- } else {
- $lastChannel = substr($cmd1, 0, 1);
- }
- if ($switchType eq "universal") {
- if ($cmd1 =~ m/A./ && (!defined($channelA) || $cmd1 ne $channelA)) {
- $cmd1 = "A0";
- } elsif ($cmd1 =~ m/B./ && (!defined($channelB) || $cmd1 ne $channelB)) {
- $cmd1 = "B0";
- } elsif ($cmd1 =~ m/C./ && (!defined($channelC) || $cmd1 ne $channelC)) {
- $cmd1 = "C0";
- } elsif ($cmd1 =~ m/D./ && (!defined($channelD) || $cmd1 ne $channelD)) {
- $cmd1 = "D0";
- } elsif ($cmd1 eq "released") {
- } else {
- $sendCmd = undef;
- }
- }
- # second action
- if ($cmd2 && $switchType eq "universal") {
- if ($cmd2 =~ m/A./ && (!defined($channelA) || $cmd2 ne $channelA)) {
- $cmd2 = "A0";
- } elsif ($cmd2 =~ m/B./ && (!defined($channelB) || $cmd2 ne $channelB)) {
- $cmd2 = "B0";
- } elsif ($cmd2 =~ m/C./ && (!defined($channelC) || $cmd2 ne $channelC)) {
- $cmd2 = "C0";
- } elsif ($cmd2 =~ m/D./ && (!defined($channelD) || $cmd2 ne $channelD)) {
- $cmd2 = "D0";
- } else {
- $cmd2 = undef;
- }
- if ($cmd2 && undef($sendCmd)) {
- # only second action has changed, send as first action
- $cmd1 = $cmd2;
- $cmd2 = undef;
- $sendCmd = 1;
- }
- }
- # convert and send first and second command
- my $switchCmd;
- ($switchCmd, $status) = split(':', $EnO_ptm200btn{$cmd1}, 2);
- # reset T21 status flag if 4 rocker
- $status = '10' if ($switchCmd > 3);
- $switchCmd <<= 5;
- if ($cmd1 ne "released") {
- # set the pressed flag
- $switchCmd |= 0x10 ;
- }
- if($cmd2) {
- # execute second action
- if ($switchType =~ m/^central|channel$/) {
- # second action not supported
- $cmd = $cmd1;
- } else {
- my ($d2, undef) = split(':', $EnO_ptm200btn{$cmd2}, 2);
- # reset T21 status flag if 4 rocker
- $status = '10' if ($d2 > 3);
- $switchCmd |= ($d2 << 1) | 0x01;
- }
- }
- if (defined $sendCmd) {
- $data = sprintf "%02X", $switchCmd;
- $rorg = "F6";
- #####
- SetExtensionsCancel($hash);
- Log3 $name, 3, "EnOcean set $name $cmd";
- if ($updateState) {
- readingsSingleUpdate($hash, "channel" . $1, $cmd1, 1) if ($cmd1 =~ m/^([A-D])./);
- readingsSingleUpdate($hash, "channel" . $1, $cmd2, 1) if ($cmd2 && $cmd2 =~ m/^([A-D])./);
- }
- readingsSingleUpdate($hash, ".lastChannel", $lastChannel, 0);
- }
- } elsif ($st eq "switch.00") {
- my $switchCmd = join(",", sort split(",", $cmd, 2));
- ($cmd1, $cmd2) = split(",", $switchCmd, 2);
- $cmd = $switchCmd;
- if ((!defined($switchCmd)) || (!defined($EnO_switch_00Btn{$switchCmd}))) {
- # check values
- $cmdList .= join(":noArg ", keys %EnO_switch_00Btn);
- return SetExtensions($hash, $cmdList, $name, @a);
- } elsif ($switchCmd eq "teachIn") {
- ($err, $rorg, $data) = EnOcean_sndUTE(undef, $hash, AttrVal($name, "comMode", "uniDir"),
- AttrVal($name, "uteResponseRequest", "no"), "in", 0, "D2-03-00");
- $updateState = 0;
- } elsif ($switchCmd eq "teachInSec") {
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- ($err, $response, $logLevel) = EnOcean_sec_createTeachIn(undef, $hash, "uniDir", "VAES", "D2-03-00", 3,
- "2++", "false", "encryption", $subDef, $destinationID);
- if ($err) {
- Log3 $name, $logLevel, "EnOcean $name Error: $err";
- return $err;
- } else {
- EnOcean_CommandSave(undef, undef);
- Log3 $name, $logLevel, "EnOcean $name $response";
- readingsSingleUpdate($hash, "teach", "STE teach-in sent", 1);
- return(undef);
- }
- } elsif ($switchCmd eq "teachOut") {
- ($err, $rorg, $data) = EnOcean_sndUTE(undef, $hash, AttrVal($name, "comMode", "uniDir"),
- AttrVal($name, "uteResponseRequest", "no"), "out", 0, "D2-03-00");
- $updateState = 0;
- } else {
- $data = sprintf "%02X", $EnO_switch_00Btn{$switchCmd};
- $rorg = "D2";
- #####
- SetExtensionsCancel($hash);
- }
- Log3 $name, 3, "EnOcean set $name $switchCmd";
- } elsif ($st eq "roomSensorControl.01") {
- # Room Sensor and Control Unit (EEP A5-04-01, A5-10-10 ... A5-10-14)
- # [Thermokon SR04 * rH, Thanus SR *, untested]
- # $db[3] is the setpoint where 0x00 = min ... 0xFF = max
- # $db[2] is the humidity where 0x00 = 0%rH ... 0xFA = 100%rH
- # $db[1] is the temperature where 0x00 = 0°C ... 0xFA = +40°C
- # $db[0] bit D0 is the occupy button, pushbutton or slide switch
- $rorg = "A5";
- # primarily temperature from the reference device then the attribute actualTemp is read
- my $temperatureRefDev = AttrVal($name, "temperatureRefDev", undef);
- my $actualTemp = AttrVal($name, "actualTemp", 20);
- $actualTemp = ReadingsVal($temperatureRefDev, "temperature", 20) if (defined $temperatureRefDev);
- $actualTemp = 20 if ($actualTemp !~ m/^[+-]?\d+(\.\d+)?$/);
- $actualTemp = 0 if ($actualTemp < 0);
- $actualTemp = 40 if ($actualTemp > 40);
- $actualTemp = sprintf "%0.1f", $actualTemp;
- # primarily humidity from the reference device then the attribute humidity is read
- my $humidityRefDev = AttrVal($name, "humidityRefDev", undef);
- my $humidity = AttrVal($name, "humidity", 0);
- $humidity = ReadingsVal($humidityRefDev, "humidity", 0) if (defined $humidityRefDev);
- $humidity = 0 if ($humidity !~ m/^\d+(\.\d+)?$/);
- $humidity = 0 if ($humidity < 0);
- $humidity = 100 if ($humidity > 100);
- $humidity = sprintf "%d", $humidity;
- my $setCmd = 8;
- my $setpoint = ReadingsVal($name, "setpoint", 125);
- my $setpointScaled = ReadingsVal($name, "setpointScaled", undef);
- my $switch = ReadingsVal($name, "switch", "off");
- $setCmd |= 1 if ($switch eq "on");
- if ($cmd eq "teach") {
- if ($manufID eq "00D") {
- # teach-in EEP A5-10-12, Manufacturer "Eltako"
- $data = "40900D80";
- $attr{$name}{eep} = "A5-10-12";
- } else {
- # teach-in EEP A5-10-10, Manufacturer "Multi user Manufacturer ID"
- $data = "4087FF80";
- $attr{$name}{eep} = "A5-10-10";
- }
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- } elsif ($cmd eq "setpoint") {
- #
- if (defined $a[1]) {
- if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 255)) {
- $setpoint = $a[1];
- shift(@a);
- if (defined $setpointScaled) {
- $setpointScaled = EnOcean_ReadingScaled($hash, $setpoint, 0, 255);
- }
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "temperature", $actualTemp);
- readingsBulkUpdate($hash, "humidity", $humidity);
- readingsBulkUpdate($hash, "setpointScaled", $setpointScaled) if (defined $setpointScaled);
- readingsBulkUpdate($hash, "setpoint", $setpoint);
- readingsBulkUpdate($hash, "switch", $switch);
- readingsBulkUpdate($hash, "state", "T: $actualTemp H: $humidity SP: $setpoint SW: $switch");
- readingsEndUpdate($hash, 1);
- $actualTemp = $actualTemp / 40 * 250;
- $humidity = $humidity / 100 * 250;
- $updateState = 0;
- $data = sprintf "%02X%02X%02X%02X", $setpoint, $humidity, $actualTemp, $setCmd;
- } elsif ($cmd eq "setpointScaled") {
- #
- if (defined $a[1]) {
- my $scaleMin = AttrVal($name, "scaleMin", undef);
- my $scaleMax = AttrVal($name, "scaleMax", undef);
- my ($rangeMin, $rangeMax);
- if (defined $scaleMax && defined $scaleMin &&
- $scaleMax =~ m/^[+-]?\d+(\.\d+)?$/ && $scaleMin =~ m/^[+-]?\d+(\.\d+)?$/) {
- if ($scaleMin > $scaleMax) {
- ($rangeMin, $rangeMax)= ($scaleMax, $scaleMin);
- } else {
- ($rangeMin, $rangeMax)= ($scaleMin, $scaleMax);
- }
- } else {
- return "Usage: Attributes scaleMin and/or scaleMax not defined or not numeric.";
- }
- if ($a[1] =~ m/^[+-]?\d+(\.\d+)?$/ && $a[1] >= $rangeMin && $a[1] <= $rangeMax) {
- $setpointScaled = $a[1];
- shift(@a);
- $setpoint = sprintf "%d", 255 * $scaleMin/($scaleMin-$scaleMax) - 255/($scaleMin-$scaleMax) * $setpointScaled;
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "temperature", $actualTemp);
- readingsBulkUpdate($hash, "humidity", $humidity);
- readingsBulkUpdate($hash, "setpointScaled", $setpointScaled) if (defined $setpointScaled);
- readingsBulkUpdate($hash, "setpoint", $setpoint);
- readingsBulkUpdate($hash, "switch", $switch);
- readingsBulkUpdate($hash, "state", "T: $actualTemp H: $humidity SP: $setpoint SW: $switch");
- readingsEndUpdate($hash, 1);
- $actualTemp = $actualTemp / 40 * 250;
- $humidity = $humidity / 100 * 250;
- $updateState = 0;
- $data = sprintf "%02X%02X%02X%02X", $setpoint, $humidity, $actualTemp, $setCmd;
- } elsif ($cmd eq "switch") {
- #
- if (defined $a[1]) {
- if ($a[1] eq "on") {
- $switch = $a[1];
- $setCmd |= 1;
- shift(@a);
- } elsif ($a[1] eq "off"){
- $switch = $a[1];
- shift(@a);
- } else {
- return "Usage: $a[1] is unknown";
- }
- }
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "temperature", $actualTemp);
- readingsBulkUpdate($hash, "humidity", $humidity);
- readingsBulkUpdate($hash, "setpointScaled", $setpointScaled) if (defined $setpointScaled);
- readingsBulkUpdate($hash, "setpoint", $setpoint);
- readingsBulkUpdate($hash, "switch", $switch);
- readingsBulkUpdate($hash, "state", "T: $actualTemp H: $humidity SP: $setpoint SW: $switch");
- readingsEndUpdate($hash, 1);
- $actualTemp = $actualTemp / 40 * 250;
- $humidity = $humidity / 100 * 250;
- $updateState = 0;
- $data = sprintf "%02X%02X%02X%02X", $setpoint, $humidity, $actualTemp, $setCmd;
- } else {
- return "Unknown argument " . $cmd . ", choose one of " . $cmdList . " setpoint:slider,0,1,255 setpointScaled switch:on,off teach:noArg"
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "roomSensorControl.05") {
- # Room Sensor and Control Unit (EEP A5-10-01 ... A5-10-0D)
- # [Eltako FTR55D, FTR55H, Thermokon SR04 *, Thanos SR *, untested]
- # $db[3] is the fan speed or night reduction for Eltako
- # $db[2] is the setpoint where 0x00 = min ... 0xFF = max or
- # reference temperature for Eltako where 0x00 = 0°C ... 0xFF = 40°C
- # $db[1] is the temperature where 0x00 = +40°C ... 0xFF = 0°C
- # $db[1]_bit_1 is blocking the aditional Room Sensor and Control Unit for Eltako FVS
- # $db[0]_bit_0 is the slide switch
- $rorg = "A5";
- # primarily temperature from the reference device then the attribute actualTemp is read
- my $temperatureRefDev = AttrVal($name, "temperatureRefDev", undef);
- my $actualTemp = AttrVal($name, "actualTemp", 20);
- $actualTemp = ReadingsVal($temperatureRefDev, "temperature", 20) if (defined $temperatureRefDev);
- $actualTemp = 20 if ($actualTemp !~ m/^[+-]?\d+(\.\d+)?$/);
- $actualTemp = 0 if ($actualTemp < 0);
- $actualTemp = 40 if ($actualTemp > 40);
- $actualTemp = sprintf "%0.1f", $actualTemp;
- my $setCmd = 8;
- if ($manufID eq "00D") {
- # EEP A5-10-06 plus DB3 [Eltako FVS]
- my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
- my $nightReduction = ReadingsVal($name, "nightReduction", 0);
- my $block = ReadingsVal($name, "block", "unlock");
- if ($cmd eq "teach") {
- # teach-in EEP A5-10-06 plus "FVS", Manufacturer "Eltako"
- $data = "40300D85";
- $attr{$name}{eep} = "A5-10-06";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- } elsif ($cmd eq "desired-temp" || $cmd eq "setpointTemp") {
- #
- if (defined $a[1]) {
- if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 40)) {
- $setpointTemp = sprintf "%0.1f", $a[1];
- shift(@a);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- if (defined $a[1]) {
- if (($a[1] =~ m/^(lock|unlock)$/) ) {
- $block = $a[1];
- shift(@a);
- } else {
- return "Usage: $a[1] is unknown";
- }
- }
- readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
- readingsSingleUpdate($hash, "setpointTemp", $setpointTemp, 1);
- readingsSingleUpdate($hash, "nightReduction", $nightReduction, 1);
- readingsSingleUpdate($hash, "block", $block, 1);
- readingsSingleUpdate($hash, "state", "T: $actualTemp SPT: $setpointTemp NR: $nightReduction", 1);
- if ($nightReduction == 5) {
- $nightReduction = 31;
- } elsif ($nightReduction == 4) {
- $nightReduction = 25;
- } elsif ($nightReduction == 3) {
- $nightReduction = 19;
- } elsif ($nightReduction == 2) {
- $nightReduction = 12;
- } elsif ($nightReduction == 1) {
- $nightReduction = 6;
- } else {
- $nightReduction = 0;
- }
- $actualTemp = (40 - $actualTemp) / 40 * 255;
- $setpointTemp = $setpointTemp * 255 / 40;
- # control of the aditional Room Sensor and Control Unit
- if ($block eq "lock") {
- # temperature setting is locked
- $setCmd = 0x0D;
- } else {
- # setpointTemp may be subject to change at +/-3 K
- $setCmd = 0x0F;
- }
- $updateState = 0;
- $data = sprintf "%02X%02X%02X%02X", $nightReduction, $setpointTemp, $actualTemp, $setCmd;
- } elsif ($cmd eq "nightReduction") {
- #
- if (defined $a[1]) {
- if ($a[1] =~ m/^[0-5]$/) {
- $nightReduction = $a[1];
- shift(@a);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- if (defined $a[1]) {
- if (($a[1] =~ m/^(lock|unlock)$/) ) {
- $block = $a[1];
- shift(@a);
- } else {
- return "Usage: $a[1] is unknown";
- }
- }
- readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
- readingsSingleUpdate($hash, "setpointTemp", $setpointTemp, 1);
- readingsSingleUpdate($hash, "nightReduction", $nightReduction, 1);
- readingsSingleUpdate($hash, "block", $block, 1);
- readingsSingleUpdate($hash, "state", "T: $actualTemp SPT: $setpointTemp NR: $nightReduction", 1);
- if ($nightReduction == 5) {
- $nightReduction = 31;
- } elsif ($nightReduction == 4) {
- $nightReduction = 25;
- } elsif ($nightReduction == 3) {
- $nightReduction = 19;
- } elsif ($nightReduction == 2) {
- $nightReduction = 12;
- } elsif ($nightReduction == 1) {
- $nightReduction = 6;
- } else {
- $nightReduction = 0;
- }
- $actualTemp = (40 - $actualTemp) / 40 * 254;
- $setpointTemp = $setpointTemp * 254 / 40;
- # control of the aditional Room Sensor and Control Unit
- if ($block eq "lock") {
- # temperature setting is locked
- $setCmd = 0x0D;
- } else {
- # setpointTemp may be subject to change at +/-3 K
- $setCmd = 0x0F;
- }
- $updateState = 0;
- $data = sprintf "%02X%02X%02X%02X", $nightReduction, $setpointTemp, $actualTemp, $setCmd;
- } else {
- return "Unknown argument " . $cmd . ", choose one of setpointTemp:slider,0,1,40 desired-temp nightReduction:0,1,2,3,4,5 teach:noArg"
- }
- } else {
- # EEP A5-10-02 or EEP A5-10-03
- my $setpoint = ReadingsVal($name, "setpoint", 128);
- my $setpointScaled = ReadingsVal($name, "setpointScaled", undef);
- my $fanStage = ReadingsVal($name, "fanStage", "auto");
- my $switch = ReadingsVal($name, "switch", "off");
- $setCmd |= 1 if ($switch eq "on");
- if ($cmd eq "teach") {
- if ($manufID eq "019") {
- # teach-in EEP A5-10-03, Manufacturer "Multi user Manufacturer ID"
- $data = "401FFF80";
- $attr{$name}{eep} = "A5-10-03";
- } else {
- # teach-in EEP A5-10-02, Manufacturer "Multi user Manufacturer ID"
- $data = "4017FF80";
- $attr{$name}{eep} = "A5-10-02";
- }
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- } elsif ($cmd eq "fanStage") {
- #
- if (defined $a[1] && ($a[1] =~ m/^[0-3]$/ || $a[1] eq "auto")) {
- $fanStage = $a[1];
- shift(@a);
- readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
- readingsSingleUpdate($hash, "setpointScaled", $setpointScaled, 1) if (defined $setpointScaled);
- readingsSingleUpdate($hash, "setpoint", $setpoint, 1);
- readingsSingleUpdate($hash, "fanStage", $fanStage, 1);
- readingsSingleUpdate($hash, "switch", $switch, 1);
- readingsSingleUpdate($hash, "state", "T: $actualTemp SP: $setpoint F: $fanStage SW: $switch", 1);
- if ($fanStage eq "auto"){
- $fanStage = 255;
- } elsif ($fanStage == 0) {
- $fanStage = 209;
- } elsif ($fanStage == 1) {
- $fanStage = 189;
- } elsif ($fanStage == 2) {
- $fanStage = 164;
- } else {
- $fanStage = 144;
- }
- } else {
- return "Usage: $a[1] is not numeric, out of range or unknown";
- }
- $actualTemp = (40 - $actualTemp) / 40 * 254;
- $updateState = 0;
- $data = sprintf "%02X%02X%02X%02X", $fanStage, $setpoint, $actualTemp, $setCmd;
- } elsif ($cmd eq "setpoint") {
- #
- if (defined $a[1]) {
- if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 255)) {
- $setpoint = $a[1];
- shift(@a);
- if (defined $setpointScaled) {
- $setpointScaled = EnOcean_ReadingScaled($hash, $setpoint, 0, 255);
- }
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
- readingsSingleUpdate($hash, "setpointScaled", $setpointScaled, 1) if (defined $setpointScaled);
- readingsSingleUpdate($hash, "setpoint", $setpoint, 1);
- readingsSingleUpdate($hash, "fanStage", $fanStage, 1);
- readingsSingleUpdate($hash, "switch", $switch, 1);
- readingsSingleUpdate($hash, "state", "T: $actualTemp SP: $setpoint F: $fanStage SW: $switch", 1);
- if ($fanStage eq "auto"){
- $fanStage = 255;
- } elsif ($fanStage == 0) {
- $fanStage = 209;
- } elsif ($fanStage == 1) {
- $fanStage = 189;
- } elsif ($fanStage == 2) {
- $fanStage = 164;
- } else {
- $fanStage = 144;
- }
- $actualTemp = (40 - $actualTemp) / 40 * 255;
- $updateState = 0;
- $data = sprintf "%02X%02X%02X%02X", $fanStage, $setpoint, $actualTemp, $setCmd;
- } elsif ($cmd eq "setpointScaled") {
- #
- if (defined $a[1]) {
- my $scaleMin = AttrVal($name, "scaleMin", undef);
- my $scaleMax = AttrVal($name, "scaleMax", undef);
- my ($rangeMin, $rangeMax);
- if (defined $scaleMax && defined $scaleMin &&
- $scaleMax =~ m/^[+-]?\d+(\.\d+)?$/ && $scaleMin =~ m/^[+-]?\d+(\.\d+)?$/) {
- if ($scaleMin > $scaleMax) {
- ($rangeMin, $rangeMax)= ($scaleMax, $scaleMin);
- } else {
- ($rangeMin, $rangeMax)= ($scaleMin, $scaleMax);
- }
- } else {
- return "Usage: Attributes scaleMin and/or scaleMax not defined or not numeric.";
- }
- if ($a[1] =~ m/^[+-]?\d+(\.\d+)?$/ && $a[1] >= $rangeMin && $a[1] <= $rangeMax) {
- $setpointScaled = $a[1];
- shift(@a);
- $setpoint = sprintf "%d", 255 * $scaleMin/($scaleMin-$scaleMax) - 255/($scaleMin-$scaleMax) * $setpointScaled;
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
- readingsSingleUpdate($hash, "setpointScaled", $setpointScaled, 1);
- readingsSingleUpdate($hash, "setpoint", $setpoint, 1);
- readingsSingleUpdate($hash, "fanStage", $fanStage, 1);
- readingsSingleUpdate($hash, "switch", $switch, 1);
- readingsSingleUpdate($hash, "state", "T: $actualTemp SP: $setpoint F: $fanStage SW: $switch", 1);
- if ($fanStage eq "auto"){
- $fanStage = 255;
- } elsif ($fanStage == 0) {
- $fanStage = 209;
- } elsif ($fanStage == 1) {
- $fanStage = 189;
- } elsif ($fanStage == 2) {
- $fanStage = 164;
- } else {
- $fanStage = 144;
- }
- $actualTemp = (40 - $actualTemp) / 40 * 255;
- $updateState = 0;
- $data = sprintf "%02X%02X%02X%02X", $fanStage, $setpoint, $actualTemp, $setCmd;
- } elsif ($cmd eq "switch") {
- #
- if (defined $a[1]) {
- if ($a[1] eq "on") {
- $switch = $a[1];
- $setCmd |= 1;
- shift(@a);
- } elsif ($a[1] eq "off"){
- $switch = $a[1];
- shift(@a);
- } else {
- return "Usage: $a[1] is unknown";
- }
- }
- readingsSingleUpdate($hash, "temperature", $actualTemp, 1);
- readingsSingleUpdate($hash, "setpointScaled", $setpointScaled, 1) if (defined $setpointScaled);
- readingsSingleUpdate($hash, "setpoint", $setpoint, 1);
- readingsSingleUpdate($hash, "fanStage", $fanStage, 1);
- readingsSingleUpdate($hash, "switch", $switch, 1);
- readingsSingleUpdate($hash, "state", "T: $actualTemp SP: $setpoint F: $fanStage SW: $switch", 1);
- if ($fanStage eq "auto"){
- $fanStage = 255;
- } elsif ($fanStage == 0) {
- $fanStage = 209;
- } elsif ($fanStage == 1) {
- $fanStage = 189;
- } elsif ($fanStage == 2) {
- $fanStage = 164;
- } else {
- $fanStage = 144;
- }
- $actualTemp = (40 - $actualTemp) / 40 * 255;
- $updateState = 0;
- $data = sprintf "%02X%02X%02X%02X", $fanStage, $setpoint, $actualTemp, $setCmd;
- } else {
- 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"
- }
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "hvac.01" || $st eq "MD15") {
- # Battery Powered Actuator (EEP A5-20-01)
- # [Kieback&Peter MD15-FTL-xx]
- $rorg = "A5";
- my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
- my $temperature = ReadingsVal($name, "temperature", 20);
- if ($cmd eq "setpoint") {
- if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "setpointSet", $a[1]);
- readingsBulkUpdate($hash, "waitingCmds", $cmd);
- readingsEndUpdate($hash, 0);
- # stop PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- CommandDeleteReading(undef, "$name setpointTempSet");
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 2;
- } elsif ($cmd =~ m/^desired-temp|setpointTemp$/) {
- if (defined $a[1] && $a[1] =~ m/^\d+(\.\d)?$/ && $a[1] >= 10 && $a[1] <= 30) {
- $cmd = "setpointTemp";
- $setpointTemp = $a[1];
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "setpointTempSet", sprintf("%0.1f", $setpointTemp));
- readingsBulkUpdate($hash, "waitingCmds", "setpointTemp");
- readingsEndUpdate($hash, 0);
- # PID regulator active
- my $activatePID = AttrVal($name, 'pidCtrl', 'off') eq 'on' ? 'start' : 'stop';
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, $activatePID, '');
- CommandDeleteReading(undef, "$name setpointSet");
- Log3 $name, 3, "EnOcean set $name $cmd $setpointTemp";
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 2;
- } elsif ($cmd =~ m/^liftSet|runInit|valveOpens|valveCloses$/) {
- # unattended?
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "waitingCmds", $cmd);
- readingsEndUpdate($hash, 0);
- # stop PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- CommandDeleteReading(undef, "$name setpointSet");
- CommandDeleteReading(undef, "$name setpointTempSet");
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 2;
- } else {
- $cmdList .= "setpointTemp:slider,0,1,40 " if (AttrVal($name, "pidCtrl", 'on') eq 'on' || AttrVal($name, "manufID", '7FF') ne '049');
- $cmdList .= "setpoint:slider,0,5,100 desired-temp liftSet:noArg runInit:noArg valveOpens:noArg valveCloses:noArg";
- return "Unknown command " . $cmd . ", choose one of " . $cmdList;
- }
- } elsif ($st eq "hvac.04") {
- # heating radiator valve actuating drive (EEP A5-20-04)
- $rorg = "A5";
- my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
- my $temperature = ReadingsVal($name, "temperature", 20);
- if ($cmd eq "setpoint") {
- if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "setpointSet", $a[1]);
- readingsBulkUpdate($hash, "waitingCmds", $cmd);
- readingsEndUpdate($hash, 0);
- # stop PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- CommandDeleteReading(undef, "$name setpointTempSet");
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 2;
- } elsif ($cmd =~ m/^desired-temp|setpointTemp$/) {
- if (defined $a[1] && $a[1] =~ m/^\d+(\.\d)?$/ && $a[1] >= 10 && $a[1] <= 30) {
- $cmd = "setpointTemp";
- $setpointTemp = $a[1];
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "setpointTempSet", sprintf("%0.1f", $setpointTemp));
- readingsBulkUpdate($hash, "waitingCmds", "setpointTemp");
- readingsEndUpdate($hash, 0);
- # PID regulator active
- #####
- my $activatePID = AttrVal($name, 'pidCtrl', 'on') eq 'on' ? 'start' : 'stop';
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, $activatePID, '');
- CommandDeleteReading(undef, "$name setpointSet");
- Log3 $name, 3, "EnOcean set $name $cmd $setpointTemp";
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 2;
- } elsif ($cmd =~ m/^runInit|valveOpens|valveCloses$/) {
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "waitingCmds", $cmd);
- readingsEndUpdate($hash, 0);
- # stop PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- CommandDeleteReading(undef, "$name setpointSet");
- CommandDeleteReading(undef, "$name setpointTempSet");
- Log3 $name, 3, "EnOcean set $name $cmd";
- $updateState = 2;
- } else {
- $cmdList .= "setpointTemp:slider,10,1,30 " if (AttrVal($name, "pidCtrl", 'on') eq 'on' || AttrVal($name, "model", '') eq 'OEM');
- $cmdList .= "setpoint:slider,0,5,100 " if (AttrVal($name, "pidCtrl", 'on') eq 'off' && AttrVal($name, "model", '') ne 'OEM');
- $cmdList .= "runInit:noArg valveCloses:noArg valveOpens:noArg";
- return "Unknown command " . $cmd . ", choose one of " . $cmdList;
- }
- } elsif ($st eq "hvac.10") {
- # Generic HVAC Interface (EEP A5-20-10)
- $rorg = "A5";
- my %ctrlFunc = (
- "off" => 1,
- "on" => 2,
- "occupancy" => 3,
- "ctrl" => 4,
- "fanSpeed" => 5,
- "vanePosition" => 6,
- "mode" => 7,
- "teach" => 255,
- );
- my %mode = (
- "auto" => 0,
- "heat" => 1,
- "morning_warmup" => 2,
- "cool" => 3,
- "night_purge" => 4,
- "precool" => 5,
- "off" => 6,
- "test" => 7,
- "emergency_heat" => 8,
- "fan_only" => 9,
- "free_cool" => 10,
- "ice" => 11,
- "max_heat" => 12,
- "eco" => 13,
- "dehumidification" => 14,
- "calibration" => 15,
- "emergency_cool" => 16,
- "emergency_stream" => 17,
- "max_cool" => 18,
- "hvc_load" => 19,
- "no_load" => 20,
- "auto_heat" => 31,
- "auto_cool" => 32,
- );
- my %vanePosition = (
- "auto" => 0,
- "horizontal" => 1,
- "position_2" => 2,
- "position_3" => 3,
- "position_4" => 4,
- "vertical" => 5,
- "swing" => 6,
- "vertical_swing" => 11,
- "horizontal_swing" => 12,
- "hor_vert_swing" => 13,
- "stop_swing" => 14,
- );
- my $ctrlFuncID;
- if (defined $ctrlFunc{$cmd}) {
- $ctrlFuncID = $ctrlFunc{$cmd};
- } else {
- $cmdList .= "ctrl mode:" . join(",", sort keys %mode) . " fanSpeed:auto,1,2,3,4,5,6,7,8,9,10,11,12,13,14 " .
- "occupancy:occupied,off,standby,unoccupied on:noArg off:noArg teach:noArg vanePosition:" .
- join(",", sort keys %vanePosition);
- return SetExtensions ($hash, $cmdList, $name, @a);
- }
- $ctrl = ReadingsVal($name, "ctrl", "auto");
- my $fanSpeed = ReadingsVal($name, "fanSpeed", "auto");
- my $mode = ReadingsVal($name, "mode", "off");
- my $occupancy = ReadingsVal($name, "occupancy", "off");
- my $powerSwitch = ReadingsVal($name, "powerSwitch", "off");
- my $vanePosition = ReadingsVal($name, "vanePosition", "auto");
- my ($ctrlParam1, $ctrlParam2, $ctrlParam3, $setCmd) = (0, 0, 0, 8);
- if($ctrlFuncID == 255) {
- # teach-in EEP A5-20-10, Manufacturer "Multi user Manufacturer ID"
- $ctrlParam1 = 0x80;
- $ctrlParam2 = 0x87;
- $ctrlParam3 = 0xFF;
- $setCmd = 0x80;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
- EnOcean_4BSRespWait(undef, $hash, $subDef);
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent, response requested", 1);
- $updateState = 0;
- } elsif ($ctrlFuncID == 1) {
- # off
- $powerSwitch = "off";
- $updateState = 0;
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "powerSwitch", "off");
- readingsBulkUpdate($hash, "state", "off");
- readingsEndUpdate($hash, 0);
- #####
- SetExtensionsCancel($hash);
- } elsif ($ctrlFuncID == 2) {
- # on
- $powerSwitch = "on";
- $updateState = 0;
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "powerSwitch", "on");
- readingsBulkUpdate($hash, "state", "off");
- readingsEndUpdate($hash, 0);
- #####
- SetExtensionsCancel($hash);
- } elsif ($ctrlFuncID == 3) {
- # occupancy
- if (defined $a[1] && $a[1] =~ m/^occupied|standby|unoccupied|off$/) {
- $occupancy = $a[1];
- readingsSingleUpdate($hash, "occupancy", $a[1], 0);
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 0;
- } elsif ($ctrlFuncID == 4) {
- # ctrl
- if (defined $a[1] && ($a[1] =~ m/^auto$/ || $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 100)) {
- $ctrl = $a[1];
- readingsSingleUpdate($hash, "ctrl", $a[1], 0);
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 0;
- } elsif ($ctrlFuncID == 5) {
- # fanSpeed
- if (defined $a[1] && ($a[1] =~ m/^auto$/ || $a[1] =~ m/^\d+$/ && $a[1] > 0 && $a[1] < 15)) {
- $fanSpeed = $a[1];
- readingsSingleUpdate($hash, "fanSpeed", $a[1], 0);
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 0;
- } elsif ($ctrlFuncID == 6) {
- # vanePosition
- my $vanePositionValues = join("|", keys %vanePosition);
- if (defined $a[1] && $a[1] =~ m/^$vanePositionValues$/) {
- $vanePosition = $a[1];
- readingsSingleUpdate($hash, "vanePosition", $a[1], 0);
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 0;
- } elsif ($ctrlFuncID == 7) {
- # mode
- my $modeValues = join("|", keys %mode);
- if (defined $a[1] && $a[1] =~ m/^$modeValues$/) {
- $mode = $a[1];
- readingsSingleUpdate($hash, "mode", $a[1], 0);
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 0;
- }
- if ($cmd ne "teach") {
- $ctrlParam1 = $mode{$mode};
- $vanePosition = $vanePosition{$vanePosition};
- $fanSpeed = 0 if ($fanSpeed eq "auto");
- $ctrlParam2 = $vanePosition << 4 || $fanSpeed;
- $ctrl = 255 if ($ctrl eq "auto");
- $ctrlParam3 = $ctrl;
- if ($occupancy eq "occupied") {
- $occupancy = 0;
- } elsif ($occupancy eq "standby") {
- $occupancy = 1;
- } elsif ($occupancy eq "unoccupied") {
- $occupancy = 2;
- } else {
- $occupancy = 3;
- }
- $powerSwitch = $powerSwitch eq "on" ? 1 : 0;
- $setCmd |= $occupancy << 1 | $powerSwitch;
- }
- $data = sprintf "%02X%02X%02X%02X", $ctrlParam1, $ctrlParam2, $ctrlParam3, $setCmd;
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "hvac.11") {
- # Generic HVAC Interface - Error Control (EEP A5-20-11)
- $rorg = "A5";
- my %ctrlFunc = (
- "externalDisable" => 1,
- "remoteCtrl" => 2,
- "window" => 3,
- "teach" => 255,
- );
- my $ctrlFuncID;
- if (defined $ctrlFunc{$cmd}) {
- $ctrlFuncID = $ctrlFunc{$cmd};
- } else {
- $cmdList .= "externalDisable:disabled,enabled remoteCtrl:disabled,enabled teach:noArg window:closed,opened";
- return "Unknown argument " . $cmd . ", choose one of " . $cmdList;
- }
- my $extern = ReadingsVal($name, "externalDisable", "enable");
- my $remote = ReadingsVal($name, "remoteCtrl", "enable");
- my $window = ReadingsVal($name, "window", "closed");
- my ($ctrlParam1, $ctrlParam2, $ctrlParam3, $setCmd) = (0, 0, 0, 8);
- if($ctrlFuncID == 255) {
- # teach-in EEP A5-20-11, Manufacturer "Multi user Manufacturer ID"
- $ctrlParam1 = 0x80;
- $ctrlParam2 = 0x8F;
- $ctrlParam3 = 0xFF;
- $setCmd = 0x80;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
- EnOcean_4BSRespWait(undef, $hash, $subDef);
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent, response requested", 1);
- $updateState = 0;
- } elsif ($ctrlFuncID == 1) {
- # external disablement
- if (defined $a[1] && $a[1] =~ m/^disabled|enabled$/) {
- $extern = $a[1];
- readingsSingleUpdate($hash, "externalDisable", $a[1], 0);
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 0;
- } elsif ($ctrlFuncID == 2) {
- # disable remote controller
- if (defined $a[1] && $a[1] =~ m/^disabled|enabled$/) {
- $remote = $a[1];
- readingsSingleUpdate($hash, "remoteCtrl", $a[1], 0);
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 0;
- } elsif ($ctrlFuncID == 3) {
- # window contact
- if (defined $a[1] && $a[1] =~ m/^closed|opened$/) {
- $window = $a[1];
- readingsSingleUpdate($hash, "window", $a[1], 0);
- shift(@a);
- } else {
- return "Usage: $cmd value wrong.";
- }
- $updateState = 0;
- }
- if ($cmd ne "teach") {
- $ctrlParam3 = $extern eq "disabled" ? 1 : 0;
- $setCmd |= ($remote eq "disabled" ? 1 : 0) << 2 | ($window eq "closed" ? 1 : 0) << 1;
- }
- $data = sprintf "%02X%02X%02X%02X", $ctrlParam1, $ctrlParam2, $ctrlParam3, $setCmd;
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "gateway") {
- # Gateway (EEP A5-38-08)
- # select Command from attribute gwCmd or command line
- my $gwCmd = AttrVal($name, "gwCmd", undef);
- if ($gwCmd && $EnO_gwCmd{$gwCmd}) {
- # command from attribute gwCmd
- if ($EnO_gwCmd{$cmd}) {
- # shift $cmd
- $cmd = $a[1];
- shift(@a);
- }
- } elsif ($EnO_gwCmd{$cmd}) {
- # command from command line
- $gwCmd = $cmd;
- $cmd = $a[1];
- shift(@a);
- } else {
- return "Unknown Gateway command " . $cmd . ", choose one of " . $cmdList . join(" ", sort keys %EnO_gwCmd);
- }
- my $gwCmdID;
- $rorg = "A5";
- my $setCmd = 0;
- my $time = 0;
- if ($gwCmd eq "switching") {
- # Switching
- $gwCmdID = 1;
- if($cmd eq "teach") {
- # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
- #$data = sprintf "%02X000000", $gwCmdID;
- if ($model eq 'TF') {
- $data = "E0400D80";
- } else {
- $data = "E047FF80";
- }
- $attr{$name}{eep} = "A5-38-08";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- EnOcean_setTeachConfirmWaitHash(undef, $hash);
- } elsif ($cmd eq "on") {
- $setCmd = 9;
- readingsSingleUpdate($hash, "block", "unlock", 1);
- if ($a[1]) {
- return "Usage: $cmd [lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock"));
- if ($a[1] eq "lock") {
- $setCmd = $setCmd | 4;
- readingsSingleUpdate($hash, "block", "lock", 1);
- }
- shift(@a);
- }
- #$updateState = 0;
- #####
- SetExtensionsCancel($hash);
- $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd;
- } elsif ($cmd eq "off") {
- if ($model eq "FSA12") {
- $setCmd = 0x0E;
- } else {
- $setCmd = 8;
- }
- readingsSingleUpdate($hash, "block", "unlock", 1);
- if ($a[1]) {
- return "Usage: $cmd [lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock"));
- if ($a[1] eq "lock") {
- $setCmd = $setCmd | 4;
- readingsSingleUpdate($hash, "block", "lock", 1);
- }
- shift(@a);
- }
- #$updateState = 0;
- #####
- SetExtensionsCancel($hash);
- $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd;
- } elsif ($cmd eq "local") {
- if ($a[1]) {
- return "Usage: $cmd [learn]" if ($a[1] ne "learn");
- if ($a[1] eq "learn") {
- $cmd = 'off';
- $setCmd = $setCmd | 0x28;
- readingsSingleUpdate($hash, "block", "unlock", 1);
- }
- shift(@a);
- }
- #$updateState = 0;
- $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd;
- } else {
- my $cmdList = "local:learn on:noArg off:noArg teach:noArg";
- return SetExtensions ($hash, $cmdList, $name, @a);
- }
- } elsif ($gwCmd eq "dimming") {
- # Dimming
- $gwCmdID = 2;
- my $dimMax = AttrVal($name, "dimMax", 255);
- my $dimMin = AttrVal($name, "dimMin", "off");
- if ($dimMax =~ m/^\d+$/ && $dimMin =~ m/^\d+$/ && $dimMin > $dimMax) {
- ($dimMax, $dimMin) = ($dimMin , $dimMax);
- }
- my $dimVal = ReadingsVal($name, "dim", undef);
- my $rampTime = AttrVal($name, "rampTime", 1);
- my $sendDimCmd = 0;
- $setCmd = 9;
- if ($cmd =~ m/^\d+$/) {
- # interpretive numeric value as dimming
- unshift(@a, 'dim');
- $cmd = 'dim';
- }
- if ($cmd eq "teach") {
- # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
- #$data = "E047FF80";
- # teach-in Eltako
- if ($model eq 'TF') {
- $data = "E0400D80";
- } else {
- $data = "02000000";
- }
- $attr{$name}{eep} = "A5-38-08";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- EnOcean_setTeachConfirmWaitHash(undef, $hash);
- } elsif ($cmd eq "dim") {
- return "Usage: $cmd dim/% [rampTime/s lock|unlock]"
- if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 100);
- # for eltako relative (0-100) (but not compliant to EEP because DB0.2 is 0)
- # >> if manufID needed: set DB2.0
- $dimVal = $a[1];
- if ($dimVal > 0) {
- readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
- }
- shift(@a);
- if (defined($a[1])) {
- return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^\d+$/);
- $rampTime = $a[1];
- shift(@a);
- }
- $sendDimCmd = 1;
- } elsif ($cmd eq "dimup") {
- return "Usage: $cmd dim/% [rampTime/s lock|unlock]"
- if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 100);
- $dimVal += $a[1];
- if ($dimVal > 0) {
- readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
- }
- shift(@a);
- if (defined($a[1])) {
- return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^\d+$/);
- $rampTime = $a[1];
- shift(@a);
- }
- $sendDimCmd = 1;
- } elsif ($cmd eq "dimdown") {
- return "Usage: $cmd dim/% [rampTime/s lock|unlock]"
- if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 100);
- $dimVal -= $a[1];
- if ($dimVal > 0) {
- readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
- }
- shift(@a);
- if (defined($a[1])) {
- return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^\d+$/);
- $rampTime = $a[1];
- shift(@a);
- }
- $sendDimCmd = 1;
- } elsif ($cmd eq "on") {
- $rampTime = 1;
- my $dimValueOn = AttrVal($name, "dimValueOn", 100);
- if ($dimValueOn eq "stored") {
- $dimVal = ReadingsVal($name, "dimValueStored", 100);
- if ($dimVal < 1) {
- $dimVal = 100;
- readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1);
- }
- } elsif ($dimValueOn eq "last") {
- $dimVal = ReadingsVal ($name, "dimValueLast", 100);
- if ($dimVal < 1) { $dimVal = 100; }
- } else {
- if ($dimValueOn !~ m/^\d+$/) {
- $dimVal = 100;
- } elsif ($dimValueOn > 100) {
- $dimVal = 100;
- } elsif ($dimValueOn < 1) {
- $dimVal = 1;
- } else {
- $dimVal = $dimValueOn;
- }
- }
- $sendDimCmd = 1;
- } elsif ($cmd eq "off") {
- $dimVal = 0;
- $rampTime = 1;
- $setCmd = 8;
- $sendDimCmd = 1;
- } elsif ($cmd eq "local") {
- if ($a[1]) {
- return "Usage: $cmd [learn]" if ($a[1] ne "learn");
- if ($a[1] eq "learn") {
- $cmd = 'off';
- $dimVal = 0;
- $rampTime = 1;
- $setCmd = 0x2C;
- readingsSingleUpdate($hash, "block", "lock", 1);
- }
- shift(@a);
- }
- #$updateState = 0;
- #####
- SetExtensionsCancel($hash);
- $data = sprintf "%02X%02X%02X%02X", $gwCmdID, $dimVal, $rampTime, $setCmd;
- } else {
- my $cmdList = "dim:slider,0,1,100 local:learn on:noArg off:noArg teach:noArg";
- return SetExtensions ($hash, $cmdList, $name, @a);
- }
- if ($sendDimCmd) {
- readingsSingleUpdate($hash, "block", "unlock", 1);
- if (defined $a[1]) {
- return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] ne "lock" && $a[1] ne "unlock");
- # Eltako devices: lock dimming value
- if ($manufID eq "00D" && $a[1] eq "lock" ) {
- $setCmd = $setCmd | 4;
- readingsSingleUpdate($hash, "block", "lock", 1);
- }
- shift(@a);
- } else {
- # Dimming value relative
- if ($manufID ne "00D") {$setCmd = $setCmd | 4;}
- }
- if ($cmd eq "off" && $dimMin =~ m/^\d+$/ && $dimMin == 0) {
- # switch off
- } elsif ($cmd eq "off" && $dimMin =~ m/^\d+$/) {
- $dimVal = $dimMin;
- $setCmd = 9;
- } elsif ($dimMax eq "off" || $dimVal == 0 && $dimMin eq "off" || $dimVal < 0) {
- # switch off
- $dimVal = 0;
- $setCmd = 8;
- } elsif ($dimMin eq "off") {
- } elsif ($dimVal < $dimMin) {
- $dimVal = $dimMin;
- }
- $dimVal = $dimMax if ($dimVal > $dimMax);
- $dimVal = 100 if ($dimVal > 100);
- $rampTime = 0 if ($rampTime < 0);
- $rampTime = 255 if ($rampTime > 255);
- #$updateState = 0;
- readingsSingleUpdate ($hash, "dim", $dimVal, 1);
- $data = sprintf "%02X%02X%02X%02X", $gwCmdID, $dimVal, $rampTime, $setCmd;
- }
- } elsif ($gwCmd eq "setpointShift") {
- $gwCmdID = 3;
- if ($cmd eq "teach") {
- # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
- $data = "E047FF80";
- $attr{$name}{eep} = "A5-38-08";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- EnOcean_setTeachConfirmWaitHash(undef, $hash);
- } elsif ($cmd eq "shift") {
- if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= -12.7) && ($a[1] <= 12.8)) {
- #$updateState = 0;
- $data = sprintf "%02X00%02X08", $gwCmdID, ($a[1] + 12.7) * 10;
- shift(@a);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- } else {
- return "Unknown argument $cmd, choose one of teach:noArg shift";
- }
- } elsif ($gwCmd eq "setpointBasic") {
- $gwCmdID = 4;
- if($cmd eq "teach") {
- # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
- $data = "E047FF80";
- $attr{$name}{eep} = "A5-38-08";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- EnOcean_setTeachConfirmWaitHash(undef, $hash);
- } elsif ($cmd eq "basic") {
- if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 51.2)) {
- #$updateState = 0;
- $data = sprintf "%02X00%02X08", $gwCmdID, $a[1] * 5;
- shift(@a);
- } else {
- return "Usage: $cmd parameter is not numeric or out of range.";
- }
- } else {
- return "Unknown argument $cmd, choose one of teach:noArg basic";
- }
- } elsif ($gwCmd eq "controlVar") {
- $gwCmdID = 5;
- my $controlVar = ReadingsVal($name, "controlVar", 0);
- if($cmd eq "teach") {
- # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
- $data = "E047FF80";
- $attr{$name}{eep} = "A5-38-08";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- EnOcean_setTeachConfirmWaitHash(undef, $hash);
- } elsif ($cmd eq "presence") {
- if ($a[1] eq "standby") {
- $setCmd = 0x0A;
- } elsif ($a[1] eq "absent") {
- $setCmd = 9;
- } elsif ($a[1] eq "present") {
- $setCmd = 8;
- } else {
- return "Usage: $cmd parameter unknown.";
- }
- shift(@a);
- $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd;
- } elsif ($cmd eq "energyHoldOff") {
- if ($a[1] eq "normal") {
- $setCmd = 8;
- } elsif ($a[1] eq "holdoff") {
- $setCmd = 0x0C;
- } else {
- return "Usage: $cmd parameter unknown.";
- }
- shift(@a);
- $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd;
- } elsif ($cmd eq "controllerMode") {
- if ($a[1] eq "auto") {
- $setCmd = 8;
- } elsif ($a[1] eq "heating") {
- $setCmd = 0x28;
- } elsif ($a[1] eq "cooling") {
- $setCmd = 0x48;
- } elsif ($a[1] eq "off" || $a[1] eq "BI") {
- $setCmd = 0x68;
- } else {
- return "Usage: $cmd parameter unknown.";
- }
- shift(@a);
- $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd;
- } elsif ($cmd eq "controllerState") {
- if ($a[1] eq "auto") {
- $setCmd = 8;
- } elsif ($a[1] eq "override") {
- $setCmd = 0x18;
- if (defined $a[2] && ($a[2] =~ m/^[+-]?\d+$/) && ($a[2] >= 0) && ($a[2] <= 100) ) {
- $controlVar = $a[2] * 255;
- shift(@a);
- } else {
- return "Usage: Control Variable Override is not numeric or out of range.";
- }
- } else {
- return "Usage: $cmd parameter unknown.";
- }
- shift(@a);
- #$updateState = 0;
- $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd;
- } else {
- return "Unknown argument, choose one of teach:noArg presence:absent,present,standby energyHoldOff:holdoff,normal controllerMode:cooling,heating,off controllerState:auto,override";
- }
- } elsif ($gwCmd eq "fanStage") {
- $gwCmdID = 6;
- if($cmd eq "teach") {
- # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
- $data = "E047FF80";
- $attr{$name}{eep} = "A5-38-08";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- EnOcean_setTeachConfirmWaitHash(undef, $hash);
- } elsif ($cmd eq "stage") {
- if ($a[1] eq "auto") {
- #$updateState = 0;
- $data = sprintf "%02X00%02X08", $gwCmdID, 255;
- } elsif ($a[1] && $a[1] =~ m/^[0-3]$/) {
- #$updateState = 0;
- $data = sprintf "%02X00%02X08", $gwCmdID, $a[1];
- } else {
- return "Usage: $cmd parameter is not numeric or out of range"
- }
- shift(@a);
- } else {
- return "Unknown argument, choose one of teach:noArg stage:auto,0,1,2,3";
- }
- } elsif ($gwCmd eq "blindCmd") {
- $gwCmdID = 7;
- my %blindFunc = (
- "status" => 0,
- "stop" => 1,
- "opens" => 2,
- "closes" => 3,
- "position" => 4,
- "up" => 5,
- "down" => 6,
- "runtimeSet" => 7,
- "angleSet" => 8,
- "positionMinMax" => 9,
- "angleMinMax" => 10,
- "positionLogic" => 11,
- "teach" => 255,
- );
- my @blindFunc = (
- "position:slider,0,1,100",
- "opens:noArg",
- "closes:noArg",
- "up",
- "down",
- "stop:noArg",
- "status:noArg",
- "runtimeSet",
- "angleSet",
- "positionMinMax",
- "angleMinMax",
- "positionLogic:normal,inverse",
- "teach:noArg",
- );
- my $blindFuncID;
- if (defined $blindFunc {$cmd}) {
- $blindFuncID = $blindFunc {$cmd};
- } elsif ($cmd =~ m/^\d+$/) {
- # interpretive numeric value as position
- unshift(@a, 'position');
- $cmd = 'position';
- $blindFuncID = 4;
- } else {
- return "Unknown Gateway Blind Central Function " . $cmd . ", choose one of ". join(" ", @blindFunc);
- }
- my $blindParam1 = 0;
- my $blindParam2 = 0;
- $setCmd = $blindFuncID << 4 | 8;
- if($blindFuncID == 255) {
- # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID"
- $gwCmdID = 0xE0;
- $blindParam1 = 0x47;
- $blindParam2 = 0xFF;
- $setCmd = 0x80;
- $attr{$name}{eep} = "A5-38-08";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- } elsif ($blindFuncID == 0) {
- # status
- $updateState = 0;
- } elsif ($blindFuncID == 1) {
- # stop
- $updateState = 0;
- } elsif ($blindFuncID == 2) {
- # opens
- $updateState = 0;
- } elsif ($blindFuncID == 3) {
- # closes
- $updateState = 0;
- } elsif ($blindFuncID == 4) {
- # position
- if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
- $blindParam1 = $a[1];
- shift(@a);
- if (defined $a[1]) {
- if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= -180 && $a[1] <= 180) {
- # set angle
- $blindParam2 = abs($a[1]) / 2;
- if ($a[1] < 0) {$blindParam2 |= 0x80;}
- shift(@a);
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- } else {
- # set angle defaults
- my $positionLogic = ReadingsVal($name, 'positionLogic', 'normal');
- my $angleMin = ReadingsVal($name, 'angleMin', -180);
- my $angleMax = ReadingsVal($name, 'angleMax', 180);
- if ($blindParam1 == 0) {
- $blindParam2 = $positionLogic eq 'normal' ? $angleMax : $angleMin;
- } elsif ($blindParam1 == 100) {
- $blindParam2 = $positionLogic eq 'normal' ? $angleMin : $angleMax;
- } else {
- $blindParam2 = $angleMin + ($angleMax - $angleMin) / 2;
- }
- if ($blindParam2 < 0) {
- $blindParam2 = abs($blindParam2) / 2;
- $blindParam2 |= 0x80;
- } else {
- $blindParam2 = $blindParam2 / 2;
- }
- }
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- # angle und position value available
- $setCmd |= 2;
- $updateState = 0;
- } elsif ($blindFuncID == 5 || $blindFuncID == 6) {
- # up / down
- if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
- $blindParam1 = $a[1];
- if (defined $a[2] && $a[2] =~ m/^[+-]?\d+(\.\d+)?$/ && $a[2] >= 0 && $a[2] <= 25.5) {
- $blindParam2 = $a[2] * 10;
- shift(@a);
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- shift(@a);
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- $updateState = 0;
- } elsif ($blindFuncID == 7) {
- # runtimeSet
- if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
- $blindParam1 = $a[1];
- if (defined $a[2] && $a[2] =~ m/^[+-]?\d+$/ && $a[2] >= 0 && $a[2] <= 255) {
- $blindParam2 = $a[2];
- shift(@a);
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- shift(@a);
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- readingsSingleUpdate($hash, "runTimeUp", $blindParam1, 1);
- readingsSingleUpdate($hash, "runTimeDown", $blindParam2, 1);
- $updateState = 0;
- } elsif ($blindFuncID == 8) {
- # angleSet
- if (defined $a[1] && $a[1] =~ m/^[+-]?\d+(\.\d+)?$/ && $a[1] >= 0 && $a[1] <= 25.5) {
- $blindParam1 = $a[1] * 10;
- ##
- readingsSingleUpdate($hash, "angleTime", (sprintf "%0.1f", $a[1]), 1);
- shift(@a);
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- $updateState = 0;
- } elsif ($blindFuncID == 9) {
- # positionMinMax
- if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
- $blindParam1 = $a[1];
- if (defined $a[2] && $a[2] =~ m/^[+-]?\d+$/ && $a[2] >= 0 && $a[2] <= 100) {
- $blindParam2 = $a[2];
- shift(@a);
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- # angle und position value available
- $setCmd |= 2;
- shift(@a);
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- if ($blindParam1 > $blindParam2) {($blindParam1, $blindParam2) = ($blindParam2, $blindParam1);}
- readingsSingleUpdate($hash, "positionMin", $blindParam1, 1);
- readingsSingleUpdate($hash, "positionMax", $blindParam2, 1);
- $updateState = 0;
- } elsif ($blindFuncID == 10) {
- # angleMinMax
- if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= -180 && $a[1] <= 180) {
- if (!defined $a[2] || $a[2] !~ m/^[+-]?\d+$/ || $a[2] < -180 || $a[2] > 180) {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- if ($a[1] > $a[2]) {($a[1], $a[2]) = ($a[2], $a[1]);}
- $blindParam1 = abs($a[1]) / 2;
- if ($a[1] < 0) {$blindParam1 |= 0x80;}
- $blindParam2 = abs($a[2]) / 2;
- if ($a[2] < 0) {$blindParam2 |= 0x80;}
- # angle und position value available
- $setCmd |= 2;
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- readingsSingleUpdate($hash, "angleMin", $a[1], 1);
- readingsSingleUpdate($hash, "angleMax", $a[2], 1);
- splice (@a, 0, 2);
- shift(@a);
- $updateState = 0;
- } elsif ($blindFuncID == 11) {
- # positionLogic
- if ($a[1] eq "normal") {
- $blindParam1 = 0;
- } elsif ($a[1] eq "inverse") {
- $blindParam1 = 1;
- } else {
- return "Usage: $cmd variable is unknown.";
- }
- readingsSingleUpdate($hash, "positionLogic", $a[1], 1);
- shift(@a);
- $updateState = 0;
- } else {
- }
- $setCmd |= 4 if (AttrVal($name, "sendDevStatus", "no") eq "yes");
- $setCmd |= 1 if (AttrVal($name, "serviceOn", "no") eq "yes");
- $data = sprintf "%02X%02X%02X%02X", $gwCmdID, $blindParam1, $blindParam2, $setCmd;
- } else {
- return "Unknown Gateway command " . $cmd . ", choose one of ". $cmdList . join(" ", sort keys %EnO_gwCmd);
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "energyManagement.01") {
- # Energy Management, Demand Response (A5-37-01)
- $rorg = "A5";
- $updateState = 0;
- my $drLevel = 15;
- my $powerUsage = 100;
- my $powerUsageLevel = 1;
- my $powerUsageScale = 0;
- my $randomStart = 0;
- my $randomEnd = 0;
- my $randomTime = rand(AttrVal($name, "demandRespRandomTime", 1));
- my $setpoint = 255;
- my $timeout = 0;
- my $threshold = AttrVal($name, "demandRespThreshold", 8);
- if($cmd eq "teach") {
- # teach-in EEP A5-37-01, Manufacturer "Multi user Manufacturer ID"
- $data = "DC0FFF80";
- $attr{$name}{eep} = "A5-37-01";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($cmd eq "level") {
- return "Usage: $cmd 0...15 [max|rel [yes|no [yes|no [timeout/min]]]]"
- if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 15 );
- $drLevel = $a[1];
- $powerUsage = $a[1] / 15 * 100;
- $powerUsageLevel = $drLevel >= $threshold ? 1 : 0;
- $setpoint = $a[1] * 17;
- shift(@a);
- } elsif ($cmd eq "max") {
- } elsif ($cmd eq "min") {
- $drLevel = 0;
- $powerUsage = 0;
- $powerUsageLevel = 0;
- $setpoint = 0;
- } elsif ($cmd eq "power") {
- return "Usage: $cmd 0...100 [max|rel [yes|no [yes|no [timeout/min]]]]"
- if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 100);
- $drLevel = $a[1] / 100 * 15;
- $powerUsage = $a[1];
- $powerUsageLevel = $drLevel >= $threshold ? 1 : 0;
- $setpoint = $a[1] * 2.55;
- shift(@a);
- } elsif ($cmd eq "setpoint") {
- return "Usage: $cmd 0...255 [max|rel [yes|no [yes|no [timeout/min]]]]"
- if(@a < 2 || $a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 255 );
- $drLevel = $a[1] / 255 * 15 ;
- $powerUsage = $a[1] / 255 * 100;
- $powerUsageLevel = $drLevel >= $threshold ? 1 : 0;
- $setpoint = $a[1];
- shift(@a);
- } else {
- 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"
- }
- if ($cmd ne "teach") {
- if (@a > 1) {
- return "Usage: $cmd [<cmdValue>] [max|rel [yes|no [yes|no [timeout/min]]]]" if($a[1] !~ m/^max|rel$/);
- $powerUsageScale = $a[1] eq "rel" ? 0x80 : 0;
- shift(@a);
- }
- if (@a > 1) {
- return "Usage: $cmd [<cmdValue>] [max|rel [yes|no [yes|no [timeout/min]]]]" if($a[1] !~ m/^yes|no$/);
- $randomStart = $a[1] eq "yes" ? 4 : 0;
- shift(@a);
- }
- if (@a > 1) {
- return "Usage: $cmd [<cmdValue>] [max|rel [yes|no [yes|no [timeout/min]]]]" if($a[1] !~ m/^yes|no$/);
- $randomEnd = $a[1] eq "yes" ? 2 : 0;
- shift(@a);
- }
- if (@a > 1) {
- return "Usage: $cmd [<cmdValue>] [max|rel [yes|no [yes|no [timeout/min]]]]"
- if($a[1] !~ m/^\d+$/ || $a[1] < 0 || $a[1] > 3825);
- $timeout = int($a[1] / 15);
- shift(@a);
- }
- $data = sprintf "%02X%02X%02X%02X", $setpoint, $powerUsageScale | $powerUsage, $timeout,
- $drLevel << 4 | $randomStart | $randomEnd | $powerUsageLevel | 8;
- my @db = ($drLevel << 4 | $randomStart | $randomEnd | $powerUsageLevel | 8,
- $timeout, $powerUsageScale | $powerUsage, $setpoint);
- EnOcean_energyManagement_01Parse($hash, @db);
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "lightCtrl.01") {
- # Central Command, Extended Lighting-Control (EEP A5-38-09)
- $rorg = "A5";
- my %ctrlFunc = (
- "off" => 1,
- "on" => 2,
- "dimup" => 3,
- "dimdown" => 4,
- "stop" => 5,
- "dim" => 6,
- "rgb" => 7,
- "scene" => 8,
- "dimMinMax" => 9,
- "lampOpHours" => 10,
- "block" => 11,
- "meteringValue" => 12,
- "teach" => 255,
- );
- my $ctrlFuncID;
- if (exists $ctrlFunc{$cmd}) {
- $ctrlFuncID = $ctrlFunc{$cmd};
- } elsif ($cmd =~ m/^\d+$/) {
- # interpretive numeric value as dimming
- unshift(@a, 'dim');
- $cmd = 'dim';
- $ctrlFuncID = 6;
- } else {
- $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";
- return SetExtensions ($hash, $cmdList, $name, @a);
- }
- my ($ctrlParam1, $ctrlParam2, $ctrlParam3) = (0, 0, 0);
- my $setCmd = $ctrlFuncID << 4 | 8;
- if($ctrlFuncID == 255) {
- # teach-in EEP A5-38-09, Manufacturer "Multi user Manufacturer ID"
- $ctrlParam1 = 0xE1;
- $ctrlParam2 = 0xC7;
- $ctrlParam3 = 0xFF;
- $setCmd = 0x80;
- $attr{$name}{eep} = "A5-38-09";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- $updateState = 0;
- } elsif ($ctrlFuncID == 1) {
- # off
- CommandDeleteReading(undef, "$name scene");
- #####
- SetExtensionsCancel($hash);
- $updateState = 0;
- } elsif ($ctrlFuncID == 2) {
- # on
- CommandDeleteReading(undef, "$name scene");
- #####
- SetExtensionsCancel($hash);
- $updateState = 0;
- } elsif ($ctrlFuncID == 3 || $ctrlFuncID == 4) {
- # dimup / dimdown
- my $rampTime = $a[1];
- if (defined $a[1]) {
- if ($a[1] =~ m/^\d+?$/ && $a[1] >= 0 && $a[1] <= 65535) {
- shift(@a);
- } else {
- return "Usage: $cmd ramping time value is not numeric or out of range.";
- }
- } else {
- $rampTime = AttrVal($name, "rampTime", 1);
- }
- $ctrlParam3 = $rampTime & 0xFF;
- $ctrlParam2 = ($rampTime & 0xFF00) >> 8;
- readingsSingleUpdate($hash, "rampTime", $rampTime, 1);
- CommandDeleteReading(undef, "$name scene");
- #####
- SetExtensionsCancel($hash);
- $updateState = 0;
- } elsif ($ctrlFuncID == 5) {
- # stop
- CommandDeleteReading(undef, "$name scene");
- #####
- SetExtensionsCancel($hash);
- $updateState = 0;
- } elsif ($ctrlFuncID == 6) {
- # dim
- if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
- $ctrlParam1 = $a[1];
- shift(@a);
- } else {
- return "Usage: $cmd dimming value is not numeric or out of range.";
- }
- my $rampTime = $a[1];
- if (defined $a[1]) {
- if ($a[1] =~ m/^\d+?$/ && $a[1] >= 0 && $a[1] <= 65535) {
- shift(@a);
- } else {
- return "Usage: $cmd ramping time value is not numeric or out of range.";
- }
- } else {
- $rampTime = AttrVal($name, "rampTime", 1);
- }
- $ctrlParam3 = $rampTime & 0xFF;
- $ctrlParam2 = ($rampTime & 0xFF00) >> 8;
- CommandDeleteReading(undef, "$name scene");
- readingsSingleUpdate($hash, "rampTime", $rampTime, 1);
- #####
- SetExtensionsCancel($hash);
- $updateState = 0;
- } elsif ($ctrlFuncID == 7) {
- # RGB
- if (@a > 1) {
- if ($a[1] =~ m/^[\dA-Fa-f]{6}$/) {
- # red
- $ctrlParam1 = hex substr($a[1], 0, 2);
- # green
- $ctrlParam2 = hex substr($a[1], 2, 2);
- # blue
- $ctrlParam3 = hex substr($a[1], 4, 2);
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "red", $ctrlParam1);
- readingsBulkUpdate($hash, "green", $ctrlParam2);
- readingsBulkUpdate($hash, "blue", $ctrlParam3);
- readingsBulkUpdate($hash, "rgb", uc($a[1]));
- readingsEndUpdate($hash, 0);
- shift(@a);
- } else {
- return "Usage: $cmd value is not hexadecimal or out of range.";
- }
- } else {
- return "Usage: $cmd values are missing";
- }
- $updateState = 0;
- } elsif ($ctrlFuncID == 8) {
- # scene
- if (@a > 2) {
- if ($a[2] =~ m/^\d+?$/ && $a[2] >= 0 && $a[2] <= 15) {
- $ctrlParam3 = $a[2];
- } else {
- return "Usage: $cmd number is not numeric or out of range.";
- }
- if ($a[1] eq "drive") {
- readingsSingleUpdate($hash, "scene", $ctrlParam3, 1);
- splice(@a, 0, 2);
- } elsif ($a[1] eq "store") {
- $ctrlParam3 |= 0x80;
- splice(@a, 0, 2);
- } else {
- return "Usage: $cmd parameter is wrong.";
- }
- } else {
- return "Usage: $cmd values are missing";
- }
- $updateState = 0;
- } elsif ($ctrlFuncID == 9) {
- # dimMinMax
- if (@a > 2) {
- if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
- # dimming limit min
- $ctrlParam1 = $a[1];
- shift(@a);
- } else {
- return "Usage: $cmd value is not numeric or out of range.";
- }
- if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
- # dimming limit max
- $ctrlParam2 = $a[1];
- shift(@a);
- } else {
- return "Usage: $cmd value is not numeric or out of range.";
- }
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "dimMin", $ctrlParam1);
- readingsBulkUpdate($hash, "dimMax", $ctrlParam2);
- readingsEndUpdate($hash, 1);
- } else {
- return "Usage: $cmd values are missing";
- }
- $updateState = 0;
- } elsif ($ctrlFuncID == 10) {
- # set operating hours of the lamp
- if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 65535) {
- $ctrlParam2 = $a[1] & 0xFF;
- $ctrlParam1 = ($a[1] & 0xFF00) >> 8;
- shift(@a);
- } else {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- $updateState = 0;
- } elsif ($ctrlFuncID == 11) {
- # block
- if (!defined $a[1]) {
- return "Usage: $cmd values are missing";
- } elsif ($a[1] eq "unlock") {
- $ctrlParam3 = 0;
- } elsif ($a[1] eq "on") {
- $ctrlParam3 = 1;
- } elsif ($a[1] eq "off") {
- $ctrlParam3 = 2;
- } elsif ($a[1] eq "local") {
- $ctrlParam3 = 3;
- } else {
- return "Usage: $cmd variable is unknown.";
- }
- readingsSingleUpdate($hash, "block", $a[1], 1);
- shift(@a);
- $updateState = 0;
- } elsif ($ctrlFuncID == 12) {
- # meteringValues
- if (@a > 2) {
- my %unitEnum = (
- "mW" => 0,
- "W" => 1,
- "kW" => 2,
- "MW" => 3,
- "Wh" => 4,
- "kWh" => 5,
- "MWh" => 6,
- "GWh" => 7,
- "mA" => 8,
- "A" => 9,
- "mV" => 10,
- "V" => 11,
- );
- if (exists $unitEnum{$a[2]}) {
- $ctrlParam3 = $unitEnum{$a[2]};
- } else {
- return "Unknown metering value choose one of " . join(" ", sort keys %unitEnum);
- }
- if ($ctrlParam3 == 9 || $ctrlParam3 == 11) {
- if ($a[1] =~ m/^\d+(\.\d+)?$/ && $a[1] >= 0 && $a[1] <= 6553.5) {
- $ctrlParam2 = int($a[1] * 10) & 0xFF;
- $ctrlParam1 = (int($a[1] * 10) & 0xFF00) >> 8;
- } else {
- return "Usage: $cmd value is not numeric or out of range.";
- }
- } else {
- if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 65535) {
- $ctrlParam2 = $a[1] & 0xFF;
- $ctrlParam1 = ($a[1] & 0xFF00) >> 8;
- } else {
- return "Usage: $cmd value is not numeric or out of range.";
- }
- }
- splice(@a, 0, 2);
- } else {
- return "Usage: $cmd values are missing";
- }
- $updateState = 0;
- }
- $setCmd |= 4 if (AttrVal($name, "sendDevStatus", "yes") eq "no");
- $setCmd |= 1 if (AttrVal($name, "serviceOn", "no") eq "yes");
- $data = sprintf "%02X%02X%02X%02X", $ctrlParam1, $ctrlParam2, $ctrlParam3, $setCmd;
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "radioLinkTest") {
- # Radio Link Test (A5-3F-00)
- $rorg = "A5";
- $updateState = 3;
- if($cmd =~ m/^standby|stop$/) {
- @{$hash->{helper}{rlt}{param}} = ($cmd, $hash, undef, $subDef, 'master', 0);
- EnOcean_RLT($hash->{helper}{rlt}{param});
- } else {
- return "Unknown argument " . $cmd . ", choose one of " . $cmdList . "standby:noArg stop:noArg"
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "manufProfile") {
- if ($manufID eq "00D") {
- # Eltako Shutter
- my $angleMax = AttrVal($name, "angleMax", 90);
- my $angleMin = AttrVal($name, "angleMin", -90);
- my $anglePos = ReadingsVal($name, "anglePos", undef);
- my $anglePosStart;
- my $angleTime = AttrVal($name, "angleTime", 0);
- my $position = ReadingsVal($name, "position", undef);
- my $positionStart;
- my $setCmd = 8;
- my $settingAccuracy = 1;
- if (AttrVal($name, 'settingAccuracy', 'low') eq 'high') {
- $setCmd = 0x0A;
- $settingAccuracy = 10;
- }
- if ($cmd eq "?" || $cmd eq "stop") {
- } else {
- # check actual shutter position
- my $actualState = ReadingsVal($name, "state", undef);
- if (defined $actualState) {
- if ($actualState eq "open") {
- $position = 0;
- $anglePos = 0;
- } elsif ($actualState eq "closed") {
- $position = 100;
- $anglePos = $angleMax;
- }
- }
- $anglePosStart = $anglePos;
- $positionStart = $position;
- readingsSingleUpdate($hash, ".anglePosStart", $anglePosStart, 0);
- readingsSingleUpdate($hash, ".positionStart", $positionStart, 0);
- }
- $rorg = "A5";
- my $shutTime = AttrVal($name, "shutTime", 255);
- my $shutTimeCloses = AttrVal($name, "shutTimeCloses", $shutTime);
- $shutTimeCloses = $shutTime if ($shutTimeCloses < $shutTime);
- my $shutCmd = 0;
- $angleMax = 90 if ($angleMax !~ m/^[+-]?\d+$/);
- $angleMax = 180 if ($angleMax > 180);
- $angleMax = -180 if ($angleMax < -180);
- $angleMin = -90 if ($angleMin !~ m/^[+-]?\d+$/);
- $angleMin = 180 if ($angleMin > 180);
- $angleMin = -180 if ($angleMin < -180);
- ($angleMax, $angleMin) = ($angleMin, $angleMax) if ($angleMin > $angleMax);
- $angleMax ++ if ($angleMin == $angleMax);
- $angleTime = 6 if ($angleTime !~ m/^[+-]?\d+$/);
- $angleTime = 6 if ($angleTime > 6);
- $angleTime = 0 if ($angleTime < 0);
- $shutTime = 255 if ($shutTime !~ m/^[+-]?\d+$/);
- $shutTime = 255 if ($shutTime > 255);
- $shutTime = 1 if ($shutTime < 1);
- if ($cmd =~ m/^\d+$/) {
- # interpretive numeric value as position
- unshift(@a, 'position');
- $cmd = 'position';
- }
- if ($cmd eq "teach") {
- # teach-in EEP A5-3F-7F, Manufacturer "Eltako"
- $data = "FFF80D80";
- $attr{$name}{eep} = "A5-3F-7F";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
- $updateState = 0;
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
- EnOcean_setTeachConfirmWaitHash(undef, $hash);
- } elsif ($cmd eq "stop") {
- # stop
- # delete readings, as they are undefined
- CommandDeleteReading(undef, "$name anglePos");
- CommandDeleteReading(undef, "$name position");
- readingsSingleUpdate($hash, "endPosition", "not_reached", 1);
- readingsSingleUpdate($hash, "state", "stop", 1);
- $shutCmd = 0;
- } elsif ($cmd eq "opens") {
- # opens >> B0
- $anglePos = 0;
- $position = 0;
- readingsSingleUpdate($hash, "anglePos", $anglePos, 1);
- readingsSingleUpdate($hash, "position", $position, 1);
- readingsSingleUpdate($hash, "endPosition", "open", 1);
- $cmd = "open";
- $shutTime = $shutTimeCloses;
- $shutCmd = 1;
- #$updateState = 0;
- } elsif ($cmd eq "closes") {
- # closes >> BI
- $anglePos = $angleMax;
- $position = 100;
- readingsSingleUpdate($hash, "anglePos", $anglePos, 1);
- readingsSingleUpdate($hash, "position", $position, 1);
- readingsSingleUpdate($hash, "endPosition", "closed", 1);
- $cmd = "closed";
- $shutTime = $shutTimeCloses;
- $shutCmd = 2;
- #$updateState = 0;
- } elsif ($cmd eq "up") {
- # up
- if (defined $a[1]) {
- if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
- $position = $positionStart - $a[1] / $shutTime * 100;
- if ($angleTime) {
- $anglePos = $anglePosStart - ($angleMax - $angleMin) * $a[1] / $angleTime;
- if ($anglePos < $angleMin) {
- $anglePos = $angleMin;
- }
- } else {
- $anglePos = $angleMin;
- }
- if ($position <= 0) {
- $anglePos = 0;
- $position = 0;
- readingsSingleUpdate($hash, "endPosition", "open", 1);
- $cmd = "open";
- } else {
- readingsSingleUpdate($hash, "endPosition", "not_reached", 1);
- $cmd = "not_reached";
- }
- $shutTime = $a[1];
- shift(@a);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- } else {
- $anglePos = 0;
- $position = 0;
- readingsSingleUpdate($hash, "endPosition", "open", 1);
- $cmd = "open";
- }
- readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1);
- readingsSingleUpdate($hash, "position", sprintf("%d", $position), 1);
- $shutCmd = 1;
- } elsif ($cmd eq "down") {
- # down
- if (defined $a[1]) {
- if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) {
- $position = $positionStart + $a[1] / $shutTime * 100;
- if ($angleTime) {
- $anglePos = $anglePosStart + ($angleMax - $angleMin) * $a[1] / $angleTime;
- if ($anglePos > $angleMax) {
- $anglePos = $angleMax;
- }
- } else {
- $anglePos = $angleMax;
- }
- if($position >= 100) {
- $anglePos = $angleMax;
- $position = 100;
- readingsSingleUpdate($hash, "endPosition", "closed", 1);
- $cmd = "closed";
- } else {
- readingsSingleUpdate($hash, "endPosition", "not_reached", 1);
- $cmd = "not_reached";
- }
- $shutTime = $a[1];
- shift(@a);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- } else {
- $anglePos = $angleMax;
- $position = 100;
- readingsSingleUpdate($hash, "endPosition", "closed", 1);
- $cmd = "closed";
- }
- readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1);
- readingsSingleUpdate($hash, "position", sprintf("%d", $position), 1);
- $shutCmd = 2;
- } elsif ($cmd eq "position") {
- if (!defined $positionStart) {
- return "Position unknown, please first opens the blinds completely."
- } elsif ($angleTime > 0 && !defined $anglePosStart){
- return "Slats angle position unknown, please first opens the blinds completely."
- } else {
- my $shutTimeSet = $shutTime;
- if (defined $a[2]) {
- if ($a[2] =~ m/^[+-]?\d+$/ && $a[2] >= $angleMin && $a[2] <= $angleMax) {
- $anglePos = $a[2];
- } else {
- return "Usage: $a[1] $a[2] is not numeric or out of range";
- }
- splice(@a,2,1);
- } else {
- $anglePos = $angleMax;
- }
- if ($positionStart <= $angleTime * $angleMax / ($angleMax - $angleMin) / $shutTimeSet * 100) {
- $anglePosStart = $angleMax;
- }
- if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
- if ($positionStart < $a[1]) {
- # down
- $angleTime = $angleTime * ($angleMax - $anglePos) / ($angleMax - $angleMin);
- $shutTime = $shutTime * ($a[1] - $positionStart) / 100 + $angleTime;
- # round up
- $angleTime = int($angleTime) + 1 if ($settingAccuracy == 1 && $angleTime > int($angleTime));
- $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
- $position = $a[1] + $angleTime / $shutTimeSet * 100;
- if ($position >= 100) {
- $position = 100;
- }
- $shutCmd = 2;
- if ($angleTime) {
- my @timerCmd = ($name, "up", $angleTime);
- my %par = (hash => $hash, timerCmd => \@timerCmd);
- InternalTimer(gettimeofday() + $shutTime + 1, "EnOcean_TimerSet", \%par, 0);
- }
- } elsif ($positionStart > $a[1]) {
- # up
- $angleTime = $angleTime * ($anglePos - $angleMin) /($angleMax - $angleMin);
- $shutTime = $shutTime * ($positionStart - $a[1]) / 100 + $angleTime;
- # round up
- $angleTime = int($angleTime) + 1 if ($settingAccuracy == 1 && $angleTime > int($angleTime));
- $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
- $position = $a[1] - $angleTime / $shutTimeSet * 100;
- if ($position <= 0) {
- $position = 0;
- $anglePos = 0;
- }
- $shutCmd = 1;
- if ($angleTime && $a[1] > 0) {
- my @timerCmd = ($name, "down", $angleTime);
- my %par = (hash => $hash, timerCmd => \@timerCmd);
- InternalTimer(gettimeofday() + $shutTime + 1, "EnOcean_TimerSet", \%par, 0);
- }
- } else {
- if ($anglePosStart > $anglePos) {
- # up >> reduce slats angle
- $shutTime = $angleTime * ($anglePosStart - $anglePos)/($angleMax - $angleMin);
- # round up
- $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
- $shutCmd = 1;
- } elsif ($anglePosStart < $anglePos) {
- # down >> enlarge slats angle
- $shutTime = $angleTime * ($anglePos - $anglePosStart) /($angleMax - $angleMin);
- # round up
- $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
- $shutCmd = 2;
- } else {
- # position and slats angle ok
- $data = '00000008';
- $shutCmd = 0;
- $updateState = 3;
- }
- }
- if ($position == 0) {
- readingsSingleUpdate($hash, "endPosition", "open", 1);
- $cmd = "open";
- } elsif ($position == 100) {
- readingsSingleUpdate($hash, "endPosition", "closed", 1);
- $cmd = "closed";
- } else {
- readingsSingleUpdate($hash, "endPosition", "not_reached", 1);
- $cmd = "not_reached";
- }
- readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1);
- readingsSingleUpdate($hash, "position", sprintf("%d", $position), 1);
- shift(@a);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- } elsif ($cmd eq "anglePos") {
- if (!defined $positionStart) {
- return "Position unknown, please first opens the blinds completely."
- } elsif ($angleTime > 0 && !defined $anglePosStart){
- return "Slats angle position unknown, please first opens the blinds completely."
- } else {
- if (defined $a[1]) {
- if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= $angleMin && $a[1] <= $angleMax) {
- $anglePos = $a[1];
- shift(@a);
- if ($anglePosStart > $anglePos) {
- # up >> reduce slats angle
- $shutTime = $angleTime * ($anglePosStart - $anglePos)/($angleMax - $angleMin);
- # round up
- $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
- $shutCmd = 1;
- } elsif ($anglePosStart < $anglePos) {
- # down >> enlarge slats angle
- $shutTime = $angleTime * ($anglePos - $anglePosStart) /($angleMax - $angleMin);
- # round up
- $shutTime = int($shutTime) + 1 if ($settingAccuracy == 1 && $shutTime > int($shutTime));
- $shutCmd = 2;
- } else {
- # slats angle ok
- $data = '00000008';
- $shutCmd = 0;
- $updateState = 3;
- }
- readingsSingleUpdate($hash, "anglePos", sprintf("%d", $anglePos), 1);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- } else {
- return "Usage: $cmd values are missing";
- }
- }
- } elsif ($cmd eq "local") {
- if ($a[1]) {
- return "Usage: $cmd [learn]" if ($a[1] ne "learn");
- if ($a[1] eq "learn") {
- $setCmd = $setCmd | 0x20;
- }
- shift(@a);
- }
- $updateState = 0;
- $data = sprintf "%04X%02X%02X", int($shutTime * $settingAccuracy), $shutCmd, $setCmd;
- } else {
- 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"
- }
- if ($shutCmd || $cmd eq "stop") {
- #$updateState = 0;
- $data = sprintf "%04X%02X%02X", int($shutTime * $settingAccuracy), $shutCmd, $setCmd;
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- }
- } elsif ($st eq "actuator.01") {
- # Electronic switches and dimmers with Energy Measurement and Local Control
- # (D2-01-00 - D2-01-12)
- $rorg = "D2";
- #$updateState = 0;
- my $cmdID;
- my $channel;
- my $dimValTimer = 0;
- my $outputVal;
- if ($cmd =~ m/^\d+$/) {
- # interpretive numeric value as dimming
- unshift(@a, 'dim');
- $cmd = 'dim';
- }
- if ($cmd eq "on") {
- shift(@a);
- $cmdID = 1;
- my $dimValueOn = AttrVal($name, "dimValueOn", 100);
- if ($dimValueOn eq "stored") {
- $outputVal = ReadingsVal($name, "dimValueStored", 100);
- if ($outputVal < 1) {
- $outputVal = 100;
- readingsSingleUpdate ($hash, "dimValueStored", $outputVal, 1);
- }
- } elsif ($dimValueOn eq "last") {
- $outputVal = ReadingsVal ($name, "dimValueLast", 100);
- if ($outputVal < 1) { $outputVal = 100; }
- } else {
- if ($dimValueOn !~ m/^[+-]?\d+$/) {
- $outputVal = 100;
- } elsif ($dimValueOn > 100) {
- $outputVal = 100;
- } elsif ($dimValueOn < 1) {
- $outputVal = 1;
- } else {
- $outputVal = $dimValueOn;
- }
- }
- $channel = shift(@a);
- $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
- if (!defined $channel || $channel eq "all") {
- CommandDeleteReading(undef, "$name channel.*");
- CommandDeleteReading(undef, "$name dim.*");
- readingsSingleUpdate($hash, "channelAll", "on", 1);
- readingsSingleUpdate($hash, "dim", $outputVal, 1);
- $channel = 30;
- } elsif ($channel eq "input" || $channel + 0 == 31) {
- readingsSingleUpdate($hash, "channelInput", "on", 1);
- readingsSingleUpdate($hash, "dimInput", $outputVal, 1);
- $channel = 31;
- } elsif ($channel + 0 >= 30) {
- CommandDeleteReading(undef, "$name channel.*");
- CommandDeleteReading(undef, "$name dim.*");
- readingsSingleUpdate($hash, "channelAll", "on", 1);
- readingsSingleUpdate($hash, "dim", $outputVal, 1);
- $channel = 30;
- } elsif ($channel + 0 >= 0 && $channel + 0 <= 29) {
- readingsSingleUpdate($hash, "channel" . $channel, "on", 1);
- readingsSingleUpdate($hash, "dim" . $channel, $outputVal, 1);
- } else {
- return "$cmd $channel wrong, choose 0...29|all|input.";
- }
- #readingsSingleUpdate($hash, "state", "on", 1);
- $data = sprintf "%02X%02X%02X", $cmdID, $dimValTimer << 5 | $channel, $outputVal;
- } elsif ($cmd eq "off") {
- shift(@a);
- $cmdID = 1;
- $outputVal = 0;
- $channel = shift(@a);
- $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
- if (!defined $channel || $channel eq "all") {
- CommandDeleteReading(undef, "$name channel.*");
- CommandDeleteReading(undef, "$name dim.*");
- readingsSingleUpdate($hash, "channelAll", "off", 1);
- readingsSingleUpdate($hash, "dim", $outputVal, 1);
- $channel = 30;
- } elsif ($channel eq "input" || $channel + 0 == 31) {
- readingsSingleUpdate($hash, "channelInput", "off", 1);
- readingsSingleUpdate($hash, "dimInput", $outputVal, 1);
- $channel = 31;
- } elsif ($channel + 0 >= 30) {
- CommandDeleteReading(undef, "$name channel.*");
- CommandDeleteReading(undef, "$name dim.*");
- readingsSingleUpdate($hash, "channelAll", "off", 1);
- readingsSingleUpdate($hash, "dim", $outputVal, 1);
- $channel = 30;
- } elsif ($channel >= 0 && $channel <= 29) {
- readingsSingleUpdate($hash, "channel" . $channel, "off", 1);
- readingsSingleUpdate($hash, "dim" . $channel, $outputVal, 1);
- } else {
- return "$cmd $channel wrong, choose 0...39|all|input.";
- }
- #readingsSingleUpdate($hash, "state", "off", 1);
- $data = sprintf "%02X%02X%02X", $cmdID, $dimValTimer << 5 | $channel, $outputVal;
- } elsif ($cmd eq "dim") {
- shift(@a);
- $cmdID = 1;
- $outputVal = shift(@a);
- if (!defined $outputVal || $outputVal !~ m/^[+-]?\d+$/ || $outputVal < 0 || $outputVal > 100) {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- $channel = shift(@a);
- $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
- if (!defined $channel) {
- CommandDeleteReading(undef, "$name channel.*");
- CommandDeleteReading(undef, "$name dim.*");
- if ($outputVal == 0) {
- readingsSingleUpdate($hash, "channelAll", "off", 1);
- } else {
- readingsSingleUpdate($hash, "channelAll", "on", 1);
- }
- readingsSingleUpdate($hash, "dim", $outputVal, 1);
- $channel = 30;
- } else {
- if ($channel eq "all") {
- CommandDeleteReading(undef, "$name channel.*");
- CommandDeleteReading(undef, "$name dim.*");
- if ($outputVal == 0) {
- readingsSingleUpdate($hash, "channelAll", "off", 1);
- } else {
- readingsSingleUpdate($hash, "channelAll", "on", 1);
- }
- readingsSingleUpdate($hash, "dim", $outputVal, 1);
- $channel = 30;
- } elsif ($channel eq "input" || $channel + 0 == 31) {
- if ($outputVal == 0) {
- readingsSingleUpdate($hash, "channelInput", "off", 1);
- } else {
- readingsSingleUpdate($hash, "channelInput", "on", 1);
- }
- readingsSingleUpdate($hash, "dimInput", $outputVal, 1);
- $channel = 31;
- } elsif ($channel + 0 >= 30) {
- CommandDeleteReading(undef, "$name channel.*");
- CommandDeleteReading(undef, "$name dim.*");
- if ($outputVal == 0) {
- readingsSingleUpdate($hash, "channelAll", "off", 1);
- } else {
- readingsSingleUpdate($hash, "channelAll", "on", 1);
- }
- readingsSingleUpdate($hash, "dim", $outputVal, 1);
- $channel = 30;
- } elsif ($channel >= 0 && $channel <= 29) {
- if ($outputVal == 0) {
- readingsSingleUpdate($hash, "channel" . $channel, "off", 1);
- } else {
- readingsSingleUpdate($hash, "channel" . $channel, "on", 1);
- }
- readingsSingleUpdate($hash, "dim" . $channel, $outputVal, 1);
- } else {
- return "Usage: $cmd $channel wrong, choose 0...39|all|input.";
- }
- $dimValTimer = shift(@a);
- if (defined $dimValTimer) {
- if ($dimValTimer eq "switch") {
- $dimValTimer = 0;
- } elsif ($dimValTimer eq "stop") {
- $dimValTimer = 4;
- } elsif ($dimValTimer =~ m/^[1-3]$/) {
- } else {
- return "Usage: $cmd <channel> $dimValTimer wrong, choose 1..3|switch|stop.";
- }
- } else {
- $dimValTimer = 0;
- }
- }
- if ($outputVal == 0) {
- $cmd = "off";
- #readingsSingleUpdate($hash, "state", "off", 1);
- } else {
- $cmd = "on";
- #readingsSingleUpdate($hash, "state", "on", 1);
- }
- $data = sprintf "%02X%02X%02X", $cmdID, $dimValTimer << 5 | $channel, $outputVal;
- } elsif ($cmd eq "local") {
- shift(@a);
- $updateState = 0;
- $cmdID = 2;
- # same configuration for all channels
- $channel = 30;
- my $dayNight = ReadingsVal($name, "dayNight", "day");
- my $dayNightCmd = ($dayNight eq "night")? 1:0;
- my $defaultState = ReadingsVal($name, "defaultState", "off");
- my $defaultStateCmd;
- if ($defaultState eq "off") {
- $defaultStateCmd = 0;
- } elsif ($defaultState eq "on") {
- $defaultStateCmd = 1;
- } elsif ($defaultState eq "last") {
- $defaultStateCmd = 2;
- } else {
- $defaultStateCmd = 0;
- }
- my $localControl = ReadingsVal($name, "localControl", "disabled");
- my $localControlCmd = ($localControl eq "enabled")? 1:0;
- my $overCurrentShutdown = ReadingsVal($name, "overCurrentShutdown", "off");
- my $overCurrentShutdownCmd = ($overCurrentShutdown eq "restart")? 1:0;
- my $overCurrentShutdownReset = "not_active";
- my $overCurrentShutdownResetCmd = 0;
- my $rampTime1 = ReadingsVal($name, "rampTime1", 0);
- my $rampTime1Cmd = $rampTime1 * 2;
- if ($rampTime1Cmd <= 0) {
- $rampTime1Cmd = 0;
- } elsif ($rampTime1Cmd >= 15) {
- $rampTime1Cmd = 15;
- }
- my $rampTime2 = ReadingsVal($name, "rampTime2", 0);
- my $rampTime2Cmd = $rampTime2 * 2;
- if ($rampTime2Cmd <= 0) {
- $rampTime2Cmd = 0;
- } elsif ($rampTime2Cmd >= 15) {
- $rampTime2Cmd = 15;
- }
- my $rampTime3 = ReadingsVal($name, "rampTime3", 0);
- my $rampTime3Cmd = $rampTime3 * 2;
- if ($rampTime3Cmd <= 0) {
- $rampTime3Cmd = 0;
- } elsif ($rampTime3Cmd >= 15) {
- $rampTime3Cmd = 15;
- }
- my $teachInDev = ReadingsVal($name, "teachInDev", "disabled");
- my $teachInDevCmd = ($teachInDev eq "enabled")? 1:0;
- my $localCmd = shift(@a);
- my $localCmdVal = shift(@a);
- if ($localCmd eq "dayNight") {
- if ($localCmdVal eq "day") {
- $dayNight = "day";
- $dayNightCmd = 0;
- } elsif ($localCmdVal eq "night") {
- $dayNight = "night";
- $dayNightCmd = 1;
- } else {
- return "Usage: $cmd $localCmd <value> wrong, choose day night.";
- }
- } elsif ($localCmd eq "defaultState"){
- if ($localCmdVal eq "off") {
- $defaultState = "off";
- $defaultStateCmd = 0;
- } elsif ($localCmdVal eq "on") {
- $defaultState = "on";
- $defaultStateCmd = 1;
- } elsif ($localCmdVal eq "last") {
- $defaultState = "last";
- $defaultStateCmd = 2;
- } else {
- return "Usage: $cmd $localCmd <value> wrong, choose on off last.";
- }
- } elsif ($localCmd eq "localControl"){
- if ($localCmdVal eq "disabled") {
- $localControl = "disabled";
- $localControlCmd = 0;
- } elsif ($localCmdVal eq "enabled") {
- $localControl = "enabled";
- $localControlCmd = 1;
- } else {
- return "Usage: $cmd $localCmd <value> wrong, choose disabled enabled.";
- }
- } elsif ($localCmd eq "overCurrentShutdown"){
- if ($localCmdVal eq "off") {
- $overCurrentShutdown = "off";
- $overCurrentShutdownCmd = 0;
- } elsif ($localCmdVal eq "restart") {
- $overCurrentShutdown = "restart";
- $overCurrentShutdownCmd = 1;
- } else {
- return "Usage: $cmd $localCmd <value> wrong, choose off restart.";
- }
- } elsif ($localCmd eq "overCurrentShutdownReset"){
- if ($localCmdVal eq "not_active") {
- $overCurrentShutdownReset = "not_active";
- $overCurrentShutdownResetCmd = 0;
- } elsif ($localCmdVal eq "trigger") {
- $overCurrentShutdownReset = "trigger";
- $overCurrentShutdownResetCmd = 1;
- } else {
- return "Usage: $cmd $localCmd <value> wrong, choose not_active trigger.";
- }
- } elsif ($localCmd eq "rampTime1"){
- if ($localCmdVal >= 0 || $localCmdVal <= 7.5) {
- $rampTime1 = $localCmdVal;
- $rampTime1Cmd = $localCmdVal * 2;
- } else {
- return "Usage: $cmd $localCmd <value> wrong, choose 0, 0.5, ..., 7, 7.5";
- }
- } elsif ($localCmd eq "rampTime2"){
- if ($localCmdVal >= 0 || $localCmdVal <= 7.5) {
- $rampTime2 = $localCmdVal;
- $rampTime2Cmd = $localCmdVal * 2;
- } else {
- return "Usage: $cmd $localCmd <value> wrong, choose 0, 0.5, ..., 7, 7.5";
- }
- } elsif ($localCmd eq "rampTime3"){
- if ($localCmdVal >= 0 || $localCmdVal <= 7.5) {
- $rampTime3 = $localCmdVal;
- $rampTime3Cmd = $localCmdVal * 2;
- } else {
- return "Usage: $cmd $localCmd <value> wrong, choose 0, 0.5, ..., 7, 7.5";
- }
- } elsif ($localCmd eq "teachInDev"){
- if ($localCmdVal eq "disabled") {
- $teachInDev = "disabled";
- $teachInDevCmd = 0;
- } elsif ($localCmdVal eq "enabled") {
- $teachInDev = "enabled";
- $teachInDevCmd = 1;
- } else {
- return "Usage: $cmd $localCmd <value> wrong, choose disabled enabled.";
- }
- } else {
- return "Usage: $cmd <localCmd> wrong, choose dayNight|defaultState|localControl|" .
- "overCurrentShutdown|overCurrentShutdownReset|rampTime1|rampTime2|rampTime3|teachInDev.";
- }
- readingsSingleUpdate($hash, "dayNight", $dayNight, 1);
- readingsSingleUpdate($hash, "defaultState", $defaultState, 1);
- readingsSingleUpdate($hash, "localControl", $localControl, 1);
- readingsSingleUpdate($hash, "overCurrentShutdown", $overCurrentShutdown, 1);
- readingsSingleUpdate($hash, "overCurrentShutdownReset", $overCurrentShutdownReset, 1);
- readingsSingleUpdate($hash, "rampTime1", $rampTime1, 1);
- readingsSingleUpdate($hash, "rampTime2", $rampTime2, 1);
- readingsSingleUpdate($hash, "rampTime3", $rampTime3, 1);
- readingsSingleUpdate($hash, "teachInDev", $teachInDev, 1);
- $data = sprintf "%02X%02X%02X%02X", $teachInDevCmd << 7 | $cmdID,
- $overCurrentShutdownCmd << 7 | $overCurrentShutdownResetCmd << 6 | $localControlCmd << 5 | $channel,
- int($rampTime2Cmd) << 4 | int($rampTime3Cmd),
- $dayNightCmd << 7 | $defaultStateCmd << 4 | int($rampTime1Cmd);
- } elsif ($cmd eq "measurement") {
- shift(@a);
- $updateState = 0;
- $cmdID = 5;
- # same configuration for all channels
- $channel = 30;
- my $measurementMode = ReadingsVal($name, "measurementMode", "energy");
- my $measurementModeCmd = ($measurementMode eq "power")? 0:1;
- my $measurementReport = ReadingsVal($name, "measurementReport", "query");
- my $measurementReportCmd = ($measurementReport eq "auto")? 0:1;
- my $measurementReset = "not_active";
- my $measurementResetCmd = 0;
- my $measurementDelta = int(ReadingsVal($name, "measurementDelta", 0));
- if ($measurementDelta <= 0) {
- $measurementDelta = 0;
- } elsif ($measurementDelta >= 4095) {
- $measurementDelta = 4095;
- }
- my $unit = ReadingsVal($name, "measurementUnit", "Ws");
- my $unitCmd;
- if ($unit eq "Ws") {
- $unitCmd = 0;
- } elsif ($unit eq "Wh") {
- $unitCmd = 1;
- } elsif ($unit eq "KWh") {
- $unitCmd = 2;
- } elsif ($unit eq "W") {
- $unitCmd = 3;
- } elsif ($unit eq "KW") {
- $unitCmd = 4;
- } else {
- $unitCmd = 0;
- }
- my $responseTimeMax = ReadingsVal($name, "responseTimeMax", 10);
- my $responseTimeMaxCmd = $responseTimeMax / 10;
- if ($responseTimeMaxCmd <= 1) {
- $responseTimeMaxCmd = 1;
- } elsif ($responseTimeMaxCmd >= 255) {
- $responseTimeMaxCmd = 255;
- }
- my $responseTimeMin = ReadingsVal($name, "responseTimeMin", 1);
- if ($responseTimeMin <= 1) {
- $responseTimeMin = 1;
- } elsif ($responseTimeMin >= 255) {
- $responseTimeMin = 255;
- }
- my $measurementCmd = shift(@a);
- my $measurementCmdVal = shift(@a);
- if (!defined $measurementCmdVal) {
- return "Usage: $cmd $measurementCmd <value> needed.";
- }
- if (!defined $measurementCmd) {
- return "Usage: $cmd <measurementCmd> wrong, choose mode|report|" .
- "reset|delta|unit|responseTimeMax|responseTimeMin.";
- } elsif ($measurementCmd eq "mode") {
- if ($measurementCmdVal eq "energy") {
- $measurementMode = "energy";
- $measurementModeCmd = 0;
- } elsif ($measurementCmdVal eq "power") {
- $measurementMode = "power";
- $measurementModeCmd = 1;
- } else {
- return "Usage: $cmd $measurementCmd <value> wrong, choose energy power.";
- }
- } elsif ($measurementCmd eq "report"){
- if ($measurementCmdVal eq "query") {
- $measurementReport = "query";
- $measurementReportCmd = 0;
- } elsif ($measurementCmdVal eq "auto") {
- $measurementReport = "auto";
- $measurementReportCmd = 1;
- } else {
- return "Usage: $cmd $measurementCmd <value> wrong, choose query auto.";
- }
- } elsif ($measurementCmd eq "reset"){
- if ($measurementCmdVal eq "not_active") {
- $measurementReset = "not_active";
- $measurementResetCmd = 0;
- } elsif ($measurementCmdVal eq "trigger") {
- $measurementReset = "trigger";
- $measurementResetCmd = 1;
- } else {
- return "Usage: $cmd $measurementCmd <value> wrong, choose not_active trigger.";
- }
- } elsif ($measurementCmd eq "unit"){
- if ($measurementCmdVal eq "Ws") {
- $unit = "Ws";
- $unitCmd = 0;
- } elsif ($measurementCmdVal eq "Wh") {
- $unit = "Wh";
- $unitCmd = 1;
- } elsif ($measurementCmdVal eq "KWh") {
- $unit = "KWh";
- $unitCmd = 2;
- } elsif ($measurementCmdVal eq "W") {
- $unit = "W";
- $unitCmd = 3;
- } elsif ($measurementCmdVal eq "KW") {
- $unit = "KW";
- $unitCmd = 4;
- } else {
- return "Usage: $cmd $measurementCmd <value> wrong, choose Ws Wh KWh W KW.";
- }
- } elsif ($measurementCmd eq "delta"){
- if ($measurementCmdVal >= 0 || $measurementCmdVal <= 4095) {
- $measurementDelta = int($measurementCmdVal);
- } else {
- return "Usage: $cmd $measurementCmd <value> wrong, choose 0 ... 4095";
- }
- } elsif ($measurementCmd eq "responseTimeMax"){
- if ($measurementCmdVal >= 10 || $measurementCmdVal <= 2550) {
- $responseTimeMax = int($measurementCmdVal);
- $responseTimeMaxCmd = int($measurementCmdVal) / 10;
- } else {
- return "Usage: $cmd $measurementCmd <value> wrong, choose 10 ... 2550";
- }
- } elsif ($measurementCmd eq "responseTimeMin"){
- if ($measurementCmdVal >= 1 || $measurementCmdVal <= 255) {
- $responseTimeMin = int($measurementCmdVal);
- } else {
- return "Usage: $cmd $measurementCmd <value> wrong, choose 1 ... 255";
- }
- } else {
- return "Usage: $cmd <measurementCmd> wrong, choose mode|report|" .
- "reset|delta|unit|responseTimeMax|responseTimeMin.";
- }
- readingsSingleUpdate($hash, "measurementMode", $measurementMode, 1);
- readingsSingleUpdate($hash, "measurementReport", $measurementReport, 1);
- readingsSingleUpdate($hash, "measurementReset", $measurementReset, 1);
- readingsSingleUpdate($hash, "measurementDelta", $measurementDelta, 1);
- readingsSingleUpdate($hash, "measurementUnit", $unit, 1);
- readingsSingleUpdate($hash, "responseTimeMax", $responseTimeMax, 1);
- readingsSingleUpdate($hash, "responseTimeMin", $responseTimeMin, 1);
- $data = sprintf "%02X%02X%02X%02X%02X%02X", $cmdID,
- $measurementReportCmd << 7 | $measurementResetCmd << 6 | $measurementModeCmd << 5 | $channel,
- ($measurementDelta | 0x0F) << 4 | $unitCmd, ($measurementDelta | 0xFF00) >> 8,
- $responseTimeMax, $responseTimeMin;
- } elsif ($cmd eq "roomCtrlMode") {
- shift(@a);
- $updateState = 0;
- $cmdID = 8;
- my $roomCtrlModeCmd = shift(@a);
- return "$cmd <channel> <roomCtrlMode> is missing, choose off|comfort|comfort-1|comfort-2|economy|buildingProtection." if (!defined $roomCtrlModeCmd);
- if ($roomCtrlModeCmd eq "off") {
- $roomCtrlModeCmd = 0;
- } elsif ($roomCtrlModeCmd eq "comfort") {
- $roomCtrlModeCmd = 1;
- } elsif ($roomCtrlModeCmd eq "economy") {
- $roomCtrlModeCmd = 2;
- } elsif ($roomCtrlModeCmd eq "buildingProtection") {
- $roomCtrlModeCmd = 3;
- } elsif ($roomCtrlModeCmd eq "comfort-1") {
- $roomCtrlModeCmd = 4;
- } elsif ($roomCtrlModeCmd eq "comfort-2") {
- $roomCtrlModeCmd = 5;
- } else {
- return "$cmd <channel> <roomCtrlMode> wrong, choose off|comfort|comfort-1|comfort-2|economy|buildingProtection.";
- }
- $data = sprintf "%02X%02X", $cmdID, $roomCtrlModeCmd;
- } elsif ($cmd eq "autoOffTime") {
- shift(@a);
- $updateState = 0;
- $cmdID = 0x0B;
- $outputVal = int(shift(@a) * 10);
- if (!defined $outputVal || $outputVal !~ m/^[+-]?\d+$/ || $outputVal < 0 || $outputVal > 65534) {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- $channel = shift(@a);
- $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
- if (!defined $channel || $channel eq "all") {
- CommandDeleteReading(undef, "$name autoOffTime.*");
- $channel = 30;
- } elsif ($channel eq "input" || $channel + 0 == 31) {
- $channel = 31;
- } elsif ($channel + 0 >= 30) {
- CommandDeleteReading(undef, "$name autoOffTime.*");
- $channel = 30;
- } elsif ($channel >= 0 && $channel <= 29) {
- } else {
- return "$cmd $channel wrong, choose 0...31|all|input.";
- }
- my $extSwitchMode = ReadingsVal($name, "extSwitchMode", 'unavailable');
- my %extSwitchMode = (
- "unavailable" => 0,
- "switch" => 1,
- "pushbutton" => 2,
- "auto" => 3
- );
- if (exists $extSwitchMode{$extSwitchMode}) {
- $extSwitchMode = $extSwitchMode{$extSwitchMode};
- } else {
- $extSwitchMode = 0;
- }
- my $extSwitchType = ReadingsVal($name, "extSwitchType", 'toggle') eq 'direction' ? 1 : 0;
- $data = sprintf "%02X%02X%04XFFFF%02X", $cmdID, $channel, $outputVal, $extSwitchMode << 6 | $extSwitchType << 5;
- } elsif ($cmd eq "delayOffTime") {
- shift(@a);
- $updateState = 0;
- $cmdID = 0x0B;
- $outputVal = int(shift(@a) * 10);
- if (!defined $outputVal || $outputVal !~ m/^[+-]?\d+$/ || $outputVal < 0 || $outputVal > 65534) {
- return "Usage: $cmd variable is not numeric or out of range.";
- }
- $channel = shift(@a);
- $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
- if (!defined $channel || $channel eq "all") {
- CommandDeleteReading(undef, "$name delayOffTime.*");
- $channel = 30;
- } elsif ($channel eq "input" || $channel + 0 == 31) {
- $channel = 31;
- } elsif ($channel + 0 >= 30) {
- CommandDeleteReading(undef, "$name delayOffTime.*");
- $channel = 30;
- } elsif ($channel >= 0 && $channel <= 29) {
- } else {
- return "$cmd $channel wrong, choose 0...31|all|input.";
- }
- my $extSwitchMode = ReadingsVal($name, "extSwitchMode", 'unavailable');
- my %extSwitchMode = (
- "unavailable" => 0,
- "switch" => 1,
- "pushbutton" => 2,
- "auto" => 3
- );
- if (exists $extSwitchMode{$extSwitchMode}) {
- $extSwitchMode = $extSwitchMode{$extSwitchMode};
- } else {
- $extSwitchMode = 0;
- }
- my $extSwitchType = ReadingsVal($name, "extSwitchType", 'toggle') eq 'direction' ? 1 : 0;
- $data = sprintf "%02X%02XFFFF%04X%02X", $cmdID, $channel, $outputVal, $extSwitchMode << 6 | $extSwitchType << 5;
- } elsif ($cmd eq "extSwitchMode") {
- shift(@a);
- $updateState = 0;
- $cmdID = 0x0B;
- my $extSwitchMode = shift(@a);
- return "Usage: $cmd variable is missing, choose unavailable|switch|pushbutton|auto." if (!defined $extSwitchMode);
- my %extSwitchMode = (
- "unavailable" => 0,
- "switch" => 1,
- "pushbutton" => 2,
- "auto" => 3
- );
- if (exists $extSwitchMode{$extSwitchMode}) {
- $extSwitchMode = $extSwitchMode{$extSwitchMode};
- } else {
- return "Usage: $cmd variable wrong, choose unavailable|switch|pushbutton|auto.";
- }
- $channel = shift(@a);
- $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
- if (!defined $channel || $channel eq "all") {
- CommandDeleteReading(undef, "$name extSwitchMode.*");
- $channel = 30;
- } elsif ($channel eq "input" || $channel + 0 == 31) {
- $channel = 31;
- } elsif ($channel + 0 >= 30) {
- CommandDeleteReading(undef, "$name extSwitchMode.*");
- $channel = 30;
- } elsif ($channel >= 0 && $channel <= 29) {
- } else {
- return "$cmd $channel wrong, choose 0...31|all|input.";
- }
- my $extSwitchType = ReadingsVal($name, "extSwitchType", 'toggle') eq 'direction' ? 1 : 0;
- $data = sprintf "%02X%02XFFFFFFFF%02X", $cmdID, $channel, $extSwitchMode << 6 | $extSwitchType << 5;
- } elsif ($cmd eq "extSwitchType") {
- shift(@a);
- $updateState = 0;
- $cmdID = 0x0B;
- my $extSwitchType = shift(@a);
- return "Usage: $cmd variable is missing, choose toggle|direction." if (!defined $extSwitchType);
- my %extSwitchType = (
- "toggle" => 0,
- "direction" => 1,
- );
- if (exists $extSwitchType{$extSwitchType}) {
- $extSwitchType = $extSwitchType{$extSwitchType};
- } else {
- return "Usage: $cmd variable wrong, choose toggle|direction.";
- }
- $channel = shift(@a);
- $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel);
- if (!defined $channel || $channel eq "all") {
- CommandDeleteReading(undef, "$name extSwitchMode.*");
- $channel = 30;
- } elsif ($channel eq "input" || $channel + 0 == 31) {
- $channel = 31;
- } elsif ($channel + 0 >= 30) {
- CommandDeleteReading(undef, "$name extSwitchMode.*");
- $channel = 30;
- } elsif ($channel >= 0 && $channel <= 29) {
- } else {
- return "$cmd $channel wrong, choose 0...31|all|input.";
- }
- my $extSwitchMode = ReadingsVal($name, "extSwitchMode", 'unavailable');
- my %extSwitchMode = (
- "unavailable" => 0,
- "switch" => 1,
- "pushbutton" => 2,
- "auto" => 3
- );
- if (exists $extSwitchMode{$extSwitchMode}) {
- $extSwitchMode = $extSwitchMode{$extSwitchMode};
- } else {
- $extSwitchMode = 0;
- }
- $data = sprintf "%02X%02XFFFFFFFF%02X", $cmdID, $channel, $extSwitchMode << 6 | $extSwitchType << 5;
- } elsif ($cmd eq "special") {
- $rorg = "D1";
- shift(@a);
- $updateState = 0;
- my $repeaterActive = 0;
- my $repeaterLevel = 0;
- my $specialCmd = shift(@a);
- if ($manufID eq "046") {
- if (!defined $specialCmd) {
- return "$cmd <command> wrong, choose repeaterLevel.";
- } elsif ($specialCmd eq "repeaterLevel") {
- $cmdID = 8;
- my $repeaterLevel = shift(@a);
- if (defined $repeaterLevel && $repeaterLevel =~ m/^off|1|2$/) {
- if ($repeaterLevel eq "off") {
- $repeaterLevel = 0;
- } else {
- $repeaterActive = 1;
- }
- } else {
- return "Usage: repeaterLevel is wrong";
- }
- } else {
- return "$cmd $specialCmd <arg> wrong, choose repeaterLevel off|1|2.";
- }
- }
- $data = sprintf "0046%02X%02X%02X", $cmdID, $repeaterActive, $repeaterLevel;
- } else {
- if ($manufID =~ m/^046$/) {
- $cmdList .= "dim:slider,0,1,100 on off autoOffTime delayOffTime extSwitchMode extSwitchType local measurement roomCtrlMode:off,buildingProtection,economy,comfort-2,comfort-1,comfort special";
- } else {
- $cmdList .= "dim:slider,0,1,100 on off autoOffTime delayOffTime extSwitchMode extSwitchType local measurement roomCtrlMode:off,buildingProtection,economy,comfort-2,comfort-1,comfort";
- }
- return SetExtensions ($hash, $cmdList, $name, @a);
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st =~ m/^blindsCtrl\.0[01]$/) {
- # Blinds Control for Position and Angle
- # (D2-05-xx)
- $rorg = "D2";
- $updateState = 0;
- my $cmdID;
- my $channel = AttrVal($name, "defaultChannel", 'all');
- my $position = 127;
- my $angle = 127;
- my $repo = AttrVal($name, "reposition", "directly");
- if ($repo eq "directly") {
- $repo = 0;
- } elsif ($repo eq "opens") {
- $repo = 1;
- } elsif ($repo eq "closes") {
- $repo = 2;
- } else {
- $repo = 0;
- }
- my $lock = 0;
- if ($cmd =~ m/^\d+$/) {
- # interpretive numeric value as position
- unshift(@a, 'position');
- $cmd = 'position';
- }
- if ($cmd eq "position") {
- $cmdID = 1;
- shift(@a);
- if (ReadingsVal($name, "block", "unlock") ne "unlock") {
- return "Attention: Device locked";
- }
- if (defined $a[0]) {
- # position value
- if (($a[0] =~ m/^\d+$/) && ($a[0] >= 0) && ($a[0] <= 100)) {
- $position = shift(@a);
- if (defined $a[0]) {
- # angle value
- if (($a[0] =~ m/^\d+$/) && ($a[0] >= 0) && ($a[0] <= 100)) {
- $angle = shift(@a);
- if (defined $a[0]) {
- # channel
- $channel = shift(@a);
- if ($channel =~ m/^all$/) {
- $channel = 15;
- } elsif ($channel =~ m/^[1234]$/) {
- $channel -= 1;
- } else {
- return "Usage: $position $angle $channel argument unknown, choose one of 1|2|3|4|all";
- }
- } else {
- $channel = 15;
- }
- if (defined $a[0]) {
- # reposition value
- $repo = shift(@a);
- if ($repo eq "directly") {
- $repo = 0;
- } elsif ($repo eq "opens") {
- $repo = 1;
- } elsif ($repo eq "closes") {
- $repo = 2;
- } else {
- return "Usage: $position $angle $channel $repo argument unknown, choose one of directly opens closes";
- }
- }
- readingsSingleUpdate($hash, "anglePos", $angle, 1);
- } else {
- return "Usage: $position $a[0] is not numeric or out of range";
- }
- } else {
- $channel = 15;
- }
- readingsSingleUpdate($hash, "state", "in_motion", 1);
- } else {
- return "Usage: $a[0] is not numeric or out of range";
- }
- } else {
- return "Usage: set <name> position <position> [<angle> [<repo>]]";
- }
- $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
- } elsif ($cmd eq "anglePos") {
- $cmdID = 1;
- shift(@a);
- if (ReadingsVal($name, "block", "unlock") ne "unlock") {
- return "Attention: Device locked";
- }
- if (defined $a[0]) {
- if (($a[0] =~ m/^\d+$/) && ($a[0] >= 0) && ($a[0] <= 100)) {
- $angle = shift(@a);
- if (defined $a[0]) {
- # channel
- $channel = shift(@a);
- if ($channel =~ m/^all$/) {
- $channel = 15;
- } elsif ($channel =~ m/^[1234]$/) {
- $channel -= 1;
- } else {
- return "Usage: $angle $channel argument unknown, choose one of 1|2|3|4|all";
- }
- } else {
- $channel = 15;
- }
- readingsSingleUpdate($hash, "state", "in_motion", 1);
- } else {
- return "Usage: $a[0] is not numeric or out of range";
- }
- } else {
- return "Usage: set <name> anglePos <angle>";
- }
- $repo = 0;
- $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
- } elsif ($cmd eq "stop") {
- $cmdID = 2;
- shift(@a);
- if (defined $a[0]) {
- # channel
- $channel = shift(@a);
- if ($channel =~ m/^all$/) {
- $channel = 15;
- } elsif ($channel =~ m/^[1234]$/) {
- $channel -= 1;
- } else {
- return "Usage: stop $channel argument unknown, choose one of 1|2|3|4|all";
- }
- } else {
- $channel = 15;
- }
- readingsSingleUpdate($hash, "state", "stopped", 1);
- $data = sprintf "%02X", $channel << 4 | $cmdID;
- } elsif ($cmd eq "opens") {
- $cmdID = 1;
- shift(@a);
- if (ReadingsVal($name, "block", "unlock") ne "unlock") {
- return "Attention: Device locked";
- }
- if (defined $a[0]) {
- # channel
- $channel = shift(@a);
- if ($channel =~ m/^all$/) {
- $channel = 15;
- } elsif ($channel =~ m/^[1234]$/) {
- $channel -= 1;
- } else {
- return "Usage: opens $channel argument unknown, choose one of 1|2|3|4|all";
- }
- } else {
- $channel = 15;
- }
- $position = 0;
- $repo = 0;
- $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
- } elsif ($cmd eq "closes") {
- $cmdID = 1;
- shift(@a);
- if (ReadingsVal($name, "block", "unlock") ne "unlock") {
- return "Attention: Device locked";
- }
- if (defined $a[0]) {
- # channel
- $channel = shift(@a);
- if ($channel =~ m/^all$/) {
- $channel = 15;
- } elsif ($channel =~ m/^[1234]$/) {
- $channel -= 1;
- } else {
- return "Usage: closes $channel argument unknown, choose one of 1|2|3|4|all";
- }
- } else {
- $channel = 15;
- }
- $position = 100;
- $repo = 0;
- $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
- } elsif ($cmd eq "unlock") {
- $cmdID = 1;
- shift(@a);
- if (defined $a[0]) {
- # channel
- $channel = shift(@a);
- if ($channel =~ m/^all$/) {
- $channel = 15;
- } elsif ($channel =~ m/^[1234]$/) {
- $channel -= 1;
- } else {
- return "Usage: unlock $channel argument unknown, choose one of 1|2|3|4|all";
- }
- } else {
- $channel = 15;
- }
- $repo = 0;
- $lock = 7;
- $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
- } elsif ($cmd eq "lock") {
- $cmdID = 1;
- shift(@a);
- if (defined $a[0]) {
- # channel
- $channel = shift(@a);
- if ($channel =~ m/^all$/) {
- $channel = 15;
- } elsif ($channel =~ m/^[1234]$/) {
- $channel -= 1;
- } else {
- return "Usage: lock $channel argument unknown, choose one of 1|2|3|4|all";
- }
- } else {
- $channel = 15;
- }
- $repo = 0;
- $lock = 1;
- $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
- } elsif ($cmd eq "alarm") {
- $cmdID = 1;
- shift(@a);
- if (defined $a[0]) {
- # channel
- $channel = shift(@a);
- if ($channel =~ m/^all$/) {
- $channel = 15;
- } elsif ($channel =~ m/^[1234]$/) {
- $channel -= 1;
- } else {
- return "Usage: alarm $channel argument unknown, choose one of 1|2|3|4|all";
- }
- } else {
- $channel = 15;
- }
- $repo = 0;
- $lock = 2;
- $data = sprintf "%02X%02X%02X%02X", $position, $angle, $repo << 4 | $lock, $channel << 4 | $cmdID;
- } else {
- $cmdList .= "position:slider,0,1,100 anglePos:slider,0,1,100 stop opens closes lock unlock alarm";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "multisensor.01") {
- #####
- # Multisensor Windows Handle
- # (D2-06-01)
- $rorg = "D2";
- $updateState = 2;
- my $waitingCmds = ReadingsVal($name, "waitingCmds", undef);
- if (defined $waitingCmds) {
- # check presence state
- $waitingCmds = ReadingsVal($name, "presence", "present") eq "absent" ? $waitingCmds & 0xDF | 32 : $waitingCmds & 0xDF;
- } else {
- $waitingCmds = ReadingsVal($name, "presence", "present") eq "absent" ? 32 : 0;
- }
- if ($cmd eq "presence") {
- # set presence
- if (defined $a[1]) {
- if ($a[1] =~ m/^absent$/) {
- readingsSingleUpdate($hash, "presence", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xDF | 32, 0);
- } elsif ($a[1] =~ m/^present$/) {
- readingsSingleUpdate($hash, "presence", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xDF, 0);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- } elsif ($cmd eq "handleClosedClick") {
- # set battery closed click
- if (defined $a[1]) {
- if ($a[1] =~ m/^disable$/) {
- readingsSingleUpdate($hash, "handleClosedClick", $a[1] . 'd', 1);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xE7 | 8, 0);
- } elsif ($a[1] =~ m/^enable$/) {
- readingsSingleUpdate($hash, "handleClosedClick", $a[1] . 'd', 1);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xE7 | 16, 0);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- } elsif ($cmd eq "batteryLowClick") {
- # set battery click low
- if (defined $a[1]) {
- if ($a[1] =~ m/^disable$/) {
- readingsSingleUpdate($hash, "batteryLowClick", $a[1] . 'd', 1);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xF9 | 2, 0);
- } elsif ($a[1] =~ m/^enable$/) {
- readingsSingleUpdate($hash, "batteryLowClick", $a[1] . 'd', 1);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds & 0xF9 | 4, 0);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- } elsif ($cmd eq "updateInterval") {
- # set update interval
- if (defined $a[1]) {
- if ($a[1] =~ m/^\d+$/ && $a[1] >= 5 && $a[1] <= 65535) {
- readingsSingleUpdate($hash, "updateInterval", $a[1], 1);
- readingsSingleUpdate($hash, "updateIntervalSet", $a[1], 0);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds | 1, 0);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- } elsif ($cmd eq "blinkInterval") {
- # set blick interval
- if (defined $a[1]) {
- if ($a[1] =~ m/^\d+$/ && $a[1] >= 3 && $a[1] <= 255) {
- readingsSingleUpdate($hash, "blinkInterval", $a[1], 1);
- readingsSingleUpdate($hash, "blinkIntervalSet", $a[1], 0);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds | 1, 0);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- } elsif ($cmd eq "teachSlave") {
- # teach slave
- $updateState = 0;
- $destinationID = "FFFFFFFF";
- if (defined $a[1]) {
- if ($a[1] =~ m/^contact$/) {
- $rorg = "D5";
- $data = '00';
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDefW", "biDir");
- readingsSingleUpdate($hash, "teachSlave", '1BS teach-in sent', 1);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- } elsif ($a[1] =~ m/^windowHandleOpen$/) {
- $rorg = "F6";
- $data = 'E0';
- $status = '20';
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDefH", "biDir");
- readingsSingleUpdate($hash, "teachSlave", 'RPS teach-in sent', 1);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- } elsif ($a[1] =~ m/^windowHandleClosed$/) {
- $rorg = "F6";
- $data = 'F0';
- $status = '20';
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDefH", "biDir");
- readingsSingleUpdate($hash, "teachSlave", 'RPS teach-in sent', 1);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- } elsif ($a[1] =~ m/^windowHandleTilted$/) {
- $rorg = "F6";
- $data = 'D0';
- $status = '20';
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDefH", "biDir");
- readingsSingleUpdate($hash, "teachSlave", 'RPS teach-in sent', 1);
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is wrong";
- }
- }
- } else {
- $cmdList .= "presence:absent,present handleClosedClick:enable,disable batteryLowClick:enable,disable " .
- "updateInterval blinkInterval teachSlave:contact,windowHandleClosed,windowHandleOpen,windowHandleTilted";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- } elsif ($st eq "roomCtrlPanel.00") {
- # Room Control Panel
- # (D2-10-00 - D2-10-02)
- $rorg = "D2";
- $updateState = 2;
- if ($cmd eq "desired-temp"|| $cmd eq "setpointTemp") {
- if (defined $a[1]) {
- if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 40)) {
- $a[1] = sprintf "%0.1f", $a[1];
- readingsSingleUpdate($hash, "setpointTemp", $a[1], 1);
- readingsSingleUpdate($hash, "setpointTempSet", $a[1], 0);
- Log3 $name, 3, "EnOcean set $name setpointTemp $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- } elsif ($cmd eq "economyTemp" || $cmd eq "preComfortTemp" || $cmd eq "buildingProtectionTemp" || $cmd eq "comfortTemp") {
- if (defined $a[1]) {
- if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 40)) {
- Log3 $name, 3, "EnOcean set $name $cmd $a[1]";
- $cmd =~ s/(\b)([a-z])/$1\u$2/g;
- $a[1] = sprintf "%0.1f", $a[1];
- readingsSingleUpdate($hash, "setpoint" . $cmd, $a[1], 1);
- readingsSingleUpdate($hash, "setpoint" . $cmd . "Set", $a[1], 0);
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 8, 0);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- } elsif ($cmd eq "fanSpeed") {
- if (defined $a[1]) {
- if ($a[1] >= 0 && $a[1] <= 100) {
- readingsSingleUpdate($hash, "fanSpeed", $a[1], 1);
- readingsSingleUpdate($hash, "fanSpeedSet", $a[1], 0);
- Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- }
- } elsif ($cmd eq "fanSpeedMode") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^(central|local)$/) {
- readingsSingleUpdate($hash, "fanSpeedMode", $a[1], 1);
- readingsSingleUpdate($hash, "fanSpeedModeSet", $a[1], 0);
- Log3 $name, 3, "EnOcean set $name fanSpeedMode $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- }
- } elsif ($cmd eq "cooling") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^(on|off|auto|no_change)$/) {
- readingsSingleUpdate($hash, "cooling", $a[1], 1);
- readingsSingleUpdate($hash, "coolingSet", $a[1], 0);
- Log3 $name, 3, "EnOcean set $name cooling $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- }
- } elsif ($cmd eq "heating") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^(on|off|auto|no_change)$/) {
- readingsSingleUpdate($hash, "heating", $a[1], 1);
- readingsSingleUpdate($hash, "heatingSet", $a[1], 0);
- Log3 $name, 3, "EnOcean set $name heating $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- }
- } elsif ($cmd eq "roomCtrlMode") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^(comfort|preComfort|economy|buildingProtection)$/) {
- readingsSingleUpdate($hash, "roomCtrlMode", $a[1], 1);
- readingsSingleUpdate($hash, "roomCtrlModeSet", $a[1], 0);
- Log3 $name, 3, "EnOcean set $name roomCtrlMode $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- }
- } elsif ($cmd eq "config") {
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 64, 0);
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($cmd eq "timeProgram") {
- # delete remote and send new time program
- delete $hash->{helper}{4}{telegramWait};
- for (my $messagePartCntr = 1; $messagePartCntr <= 4; $messagePartCntr ++) {
- if (defined AttrVal($name, "timeProgram" . $messagePartCntr, undef)) {
- $hash->{helper}{4}{telegramWait}{$messagePartCntr} = 1;
- Log3 $name, 3, "EnOcean $name EnOcean_Set timeProgram" . $messagePartCntr . " set";
- }
- }
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 528, 0);
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($cmd eq "deleteTimeProgram") {
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 512, 0);
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($cmd eq "time") {
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 4, 0);
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($cmd eq "window") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^closed|open$/) {
- readingsSingleUpdate($hash, "window", $a[1], 1);
- readingsSingleUpdate($hash, "windowSet", $a[1], 0);
- Log3 $name, 3, "EnOcean set $name window $a[1]";
- shift(@a);
- readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- }
- } elsif ($cmd eq "clearCmds") {
- CommandDeleteReading(undef, "$name waitingCmds");
- Log3 $name, 3, "EnOcean set $name $cmd";
- } else {
- $cmdList .= "cooling:auto,off,on,no_change desired-temp setpointTemp:slider,0,1,40 " .
- "comfortTemp deleteTimeProgram:noArg " .
- "economyTemp preComfortTemp buildingProtectionTemp config:noArg " .
- "clearCmds:noArg fanSpeed:slider,0,1,100 heating:auto,off,on,no_change " .
- "fanSpeedMode:central,local time:noArg " .
- "roomCtrlMode:comfort,economy,preComfort,buildingProtection timeProgram:noArg window:closed,open";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- } elsif ($st eq "roomCtrlPanel.01") {
- # Room Control Panel
- # (D2-11-01 - D2-11-08)
- $rorg = "D2";
- $updateState = 0;
- my $cooling = ReadingsVal($name, "colling", 'off');
- my $fanSpeed = ReadingsVal($name, "fanSpeed", 'auto');
- my $heating = ReadingsVal($name, "heating", 'off');
- my $humidity = ReadingsVal($name, "humidity", 0);
- my $occupancy = ReadingsVal($name, "occupancy", 'unoccupied');
- my $setpointBase = ReadingsVal($name, "setpointBase", 20);
- my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
- my $setpointShift = ReadingsVal($name, "setpointShift", 0);
- my $setpointShiftMax = ReadingsVal($name, "setpointShiftMax", 10);
- my $setpointType = ReadingsVal($name, "setpointType", 'setpointShift');
- my $temperature = ReadingsVal($name, "temperature", 20);
- my $waitingCmds = ReadingsVal($name, "waitingCmds", 0);
- my $window = ReadingsVal($name, "window", 'closed');
- if ($cmd eq "desired-temp"|| $cmd eq "setpointTemp") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^\d+$/ && $a[1] >= 5 && $a[1] <= 40) {
- readingsBeginUpdate($hash);
- if ($a[1] < 15) {
- $setpointBase = 15;
- $setpointShift = $a[1] - $setpointBase;
- $setpointShiftMax = $setpointBase - $a[1] if ($setpointShiftMax < -$setpointShift);
- readingsBulkUpdate($hash, "setpointShiftMax", $setpointShiftMax);
- } elsif ($a[1] > 30) {
- $setpointBase = 30;
- $setpointShift = $a[1] - $setpointBase;
- $setpointShiftMax = $a[1] - $setpointBase if ($setpointShiftMax < $setpointShift);
- readingsBulkUpdate($hash, "setpointShiftMax", $setpointShiftMax);
- } else {
- $setpointBase = $a[1];
- $setpointShift = 0;
- }
- readingsBulkUpdate($hash, "setpointShift", $setpointShift);
- readingsBulkUpdate($hash, "setpointBase", $setpointBase);
- $setpointTemp = $a[1];
- readingsBulkUpdate($hash, "setpointTemp", $a[1]);
- readingsBulkUpdate($hash, "state", "T: $temperature H: $humidity SPT: $setpointTemp F: $fanSpeed");
- readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 1);
- readingsEndUpdate($hash, 1);
- CommandDeleteReading(undef, "$name smartAckMailbox");
- Log3 $name, 3, "EnOcean set $name setpointTemp $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- } else {
- return "Usage: set <name> setpointTemp 5...40";
- }
- } elsif ($cmd eq "setpointShiftMax") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^\d+$/ && $a[1] >= 1 && $a[1] <= 10) {
- readingsBeginUpdate($hash);
- if ($setpointTemp < 15 - $a[1]) {
- $setpointShiftMax = 15 - $setpointTemp;
- } elsif ($setpointTemp > 30 + $a[1]) {
- $setpointShiftMax = $setpointTemp - 30;
- } else {
- $setpointShiftMax = $a[1];
- }
- readingsBulkUpdate($hash, "setpointShiftMax", $setpointShiftMax);
- readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 2);
- readingsEndUpdate($hash, 1);
- CommandDeleteReading(undef, "$name smartAckMailbox");
- Log3 $name, 3, "EnOcean set $name setpointShiftMax $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- } else {
- return "Usage: set <name> setpointShiftMax 1...10";
- }
- } elsif ($cmd eq "setpointType") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^setpointTemp|setpointShift$/) {
- readingsBeginUpdate($hash);
- $setpointType = $a[1];
- readingsBulkUpdate($hash, "setpointType", $setpointType);
- readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 0x80);
- readingsEndUpdate($hash, 1);
- CommandDeleteReading(undef, "$name smartAckMailbox");
- Log3 $name, 3, "EnOcean set $name setpointType $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- } else {
- return "Usage: set <name> setpointType setpointTemp|setpointShift";
- }
- } elsif ($cmd eq 'fanSpeed') {
- if (defined $a[1]) {
- if ($a[1] =~ m/^auto|off|1|2|3$/) {
- $fanSpeed = $a[1];
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "fanSpeed", $a[1]);
- readingsBulkUpdate($hash, "state", "T: $temperature H: $humidity SPT: $setpointTemp F: $fanSpeed");
- readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 4);
- readingsEndUpdate($hash, 1);
- CommandDeleteReading(undef, "$name smartAckMailbox");
- Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- } else {
- return "Usage: set <name> fanspeed 1...3|auto|off";
- }
- } elsif ($cmd eq "cooling") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^on|off$/) {
- $cooling = $a[1];
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "cooling", $a[1]);
- if ($a[1] eq 'on') {
- $heating = 'off';
- readingsBulkUpdate($hash, "heating", 'off');
- }
- readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 0x20);
- readingsEndUpdate($hash, 1);
- CommandDeleteReading(undef, "$name smartAckMailbox");
- Log3 $name, 3, "EnOcean set $name cooling $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- } else {
- return "Usage: set <name> cooling on|off";
- }
- } elsif ($cmd eq "heating") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^on|off$/) {
- $heating = $a[1];
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "heating", $a[1]);
- if ($a[1] eq 'on') {
- $cooling = 'off';
- readingsBulkUpdate($hash, "cooling", 'off');
- }
- readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 0x40);
- readingsEndUpdate($hash, 1);
- CommandDeleteReading(undef, "$name smartAckMailbox");
- Log3 $name, 3, "EnOcean set $name heating $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- } else {
- return "Usage: set <name> heating on|off";
- }
- } elsif ($cmd eq "occupancy") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^occupied|unoccupied$/) {
- $occupancy = $a[1];
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "occupancy", $a[1]);
- readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 8);
- readingsEndUpdate($hash, 1);
- CommandDeleteReading(undef, "$name smartAckMailbox");
- Log3 $name, 3, "EnOcean set $name occupancy $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- } else {
- return "Usage: set <name> occupancy occupied|unoccupied";
- }
- } elsif ($cmd eq "window") {
- if (defined $a[1]) {
- if ($a[1] =~ m/^closed|open$/) {
- $window = $a[1];
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "window", $a[1]);
- readingsBulkUpdate($hash, "waitingCmds", $waitingCmds |= 0x10);
- readingsEndUpdate($hash, 1);
- CommandDeleteReading(undef, "$name smartAckMailbox");
- Log3 $name, 3, "EnOcean set $name window $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- } else {
- return "Usage: set <name> window open|closed";
- }
- } else {
- $cmdList .= "setpointTemp:slider,5,1,40 cooling:off,on desired-temp " .
- "fanSpeed:auto,off,1,2,3 heating:off,on occupancy:occupied,unoccupied " .
- "setpointShiftMax:slider,1,1,10 setpointType:setpointShift,setpointTemp window:closed,open";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- $setpointType = $setpointType eq 'setpointTemp' ? 0x80 : 0;
- $heating = $heating eq 'on' ? 0x40 : 0;
- $cooling = $cooling eq 'on' ? 0x20 : 0;
- $window = $window eq 'open' ? 0x10 : 0;
- $setpointShift = int(($setpointShift + $setpointShiftMax) * 255 / ($setpointShiftMax * 2));
- #$setpointShift = unpack('C', pack('c', $setpointShift));
- my %fanSpeed = ('auto' => 0, 'off' =>1, 1 => 2, 2 => 3, 3 => 4);
- $occupancy = $occupancy eq 'occupied' ? 1 : 0;
- $data = sprintf "%02X%02X%02X%02X", $setpointType |$heating | $cooling | $window | 1,
- $setpointShift, $setpointBase,
- $setpointShiftMax << 4 | $fanSpeed{$fanSpeed} << 1 | $occupancy;
- } elsif ($st eq "fanCtrl.00") {
- # Fan Control
- # (D2-20-00 - D2-20-02)
- $rorg = "D2";
- #$updateState = 0;
- my ($fanSpeed, $humiThreshold, $roomSize, $roomSizeRef, $humidityCtrl, $tempLevel, $opMode) =
- (255, 255, 15, 3, 3, 3, 15);
- my $messageType = 0;
- my @roomSizeTbl = (25, 50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300, 325, 350);
- if ($cmd eq "fanSpeed") {
- $updateState = 0;
- if (defined $a[1]) {
- if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
- $fanSpeed = $a[1];
- readingsSingleUpdate($hash, "fanSpeed", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]";
- shift(@a);
- } elsif ($a[1] eq "auto") {
- $fanSpeed = 253;
- readingsSingleUpdate($hash, "fanSpeed", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]";
- shift(@a);
- } elsif ($a[1] eq "default") {
- $fanSpeed = 254;
- readingsSingleUpdate($hash, "fanSpeed", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- } else {
- return "Usage: set <name> fanspeed 0...100|auto|default";
- }
- shift(@a);
- } elsif ($cmd eq "on") {
- $opMode = 1;
- Log3 $name, 3, "EnOcean set $name $a[0]";
- shift(@a);
- } elsif ($cmd eq "off") {
- $opMode = 0;
- Log3 $name, 3, "EnOcean set $name $a[0]";
- shift(@a);
- } elsif ($cmd eq "desired-temp" || $cmd eq "setpointTemp") {
- $updateState = 0;
- my $temperatureRefDev = AttrVal($name, "temperatureRefDev", undef);
- return "attribute missing: attr $name temperatureRefDev <refDev> must be defined" if (!defined $temperatureRefDev);
- my $temperature = ReadingsVal($temperatureRefDev, "temperature", 20);
- my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
- my $switchHysteresis = AttrVal($name, "switchHysteresis", 1);
- if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 40) {
- $setpointTemp = $a[1];
- shift(@a);
- }
- if ($setpointTemp > $temperature + $switchHysteresis / 2) {
- $tempLevel = 2;
- } elsif ($setpointTemp < $temperature - $switchHysteresis / 2) {
- $tempLevel = 0;
- } else {
- $tempLevel = 1;
- }
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "setpointTemp", sprintf("%.1f", $setpointTemp));
- readingsBulkUpdate($hash, "temperature", sprintf("%.1f", $temperature));
- readingsEndUpdate($hash, 1);
- Log3 $name, 3, "EnOcean set $name $cmd $setpointTemp";
- shift(@a);
- } elsif ($cmd eq "humidityThreshold") {
- $updateState = 0;
- if (defined $a[1]) {
- if ($a[1] =~ m/^\d+$/ && $a[1] >= 0 && $a[1] <= 100) {
- $humidityCtrl = 1;
- $humiThreshold = $a[1];
- readingsSingleUpdate($hash, "humidityThreshold", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name humidityThreshold $a[1]";
- shift(@a);
- } elsif ($a[1] eq "auto") {
- $humidityCtrl = 1;
- $humiThreshold = 253;
- readingsSingleUpdate($hash, "humidityThreshold", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name humidityThreshold $a[1]";
- shift(@a);
- } elsif ($a[1] eq "default") {
- $humidityCtrl = 2;
- $humiThreshold = 254;
- readingsSingleUpdate($hash, "humidityThreshold", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name humidityThreshold $a[1]";
- shift(@a);
- } elsif ($a[1] eq "disabled") {
- $humidityCtrl = 0;
- $humiThreshold = 255;
- readingsSingleUpdate($hash, "humidityThreshold", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name humidityThreshold $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- } else {
- return "Usage: set <name> humidityThreshold 0...100|auto|default|disabled";
- }
- shift(@a);
- } elsif ($cmd eq "roomSize") {
- $updateState = 0;
- if (defined $a[1]) {
- if ($a[1] =~ m/^\d+$/) {
- readingsSingleUpdate($hash, "roomSize", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name roomSize $a[1]";
- $roomSize = 14;
- for (my $i = 0; $i < @roomSizeTbl; $i++) {
- if ($a[1] <= $roomSizeTbl[$i]) {
- $roomSize = $i;
- last;
- }
- }
- $roomSizeRef = 0;
- shift(@a);
- } elsif ($a[1] eq "not_used") {
- $roomSizeRef = 1;
- $roomSize = 15;
- readingsSingleUpdate($hash, "roomSize", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name roomSize $a[1]";
- shift(@a);
- } elsif ($a[1] eq "default") {
- $roomSizeRef = 2;
- $roomSize = 15;
- readingsSingleUpdate($hash, "roomSize", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name roomSize $a[1]";
- shift(@a);
- } elsif ($a[1] eq "max") {
- $roomSizeRef = 0;
- $roomSize = 14;
- readingsSingleUpdate($hash, "roomSize", $a[1], 1);
- Log3 $name, 3, "EnOcean set $name roomSize $a[1]";
- shift(@a);
- } else {
- return "Usage: $a[1] is wrong.";
- }
- } else {
- return "Usage: set <name> roomSize 0...350|max|not_used|default";
- }
- shift(@a);
- } else {
- $cmdList .= "off:noArg on:noArg desired-temp fanSpeed setpointTemp:slider,0,1,40 " .
- "humidityThreshold roomSize";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- $data = sprintf "%02X%02X%02X%02X", ($opMode << 4) | ($tempLevel << 1),
- ($humidityCtrl << 6) | ($roomSizeRef << 4) | $roomSize,
- $humiThreshold, $fanSpeed;
- } elsif ($st eq "heatRecovery.00") {
- # heat recovery ventilation
- # (D2-50-00)
- $rorg = "D2";
- $updateState = 0;
- my $airQuatityThreshold = ReadingsVal($name, "airQuatityThreshold", 'default');
- $airQuatityThreshold = $airQuatityThreshold eq 'default' ? 127 : $airQuatityThreshold;
- my $CO2Threshold = ReadingsVal($name, "CO2Threshold", 'default');
- $CO2Threshold = $CO2Threshold eq 'default' ? 127 : $CO2Threshold;
- my $heatExchangerBypass = 0;
- my $humidityThreshold = ReadingsVal($name, "humidityThreshold", 'default');
- $humidityThreshold = $humidityThreshold eq 'default' ? 127 : $humidityThreshold;
- my $startTimerMode = 0;
- my $tempThreshold = ReadingsVal($name, "roomTempSet", 'default');;
- $tempThreshold = $tempThreshold eq 'default' ? 127 : $tempThreshold;
- my $ventilation = 15;
- if ($cmd eq "ventilation") {
- $ventilation = $a[1];
- return "Usage: $cmd variable is missing, choose off|1...4|auto|demand|supplyAir|exhaustAir." if (!defined $ventilation);
- shift(@a);
- my %ventilation = (
- "off" => 0,
- 1 => 1,
- 2 => 2,
- 3 => 3,
- 4 => 4,
- "auto" => 11,
- "demand" => 12,
- "supplyAir" => 13,
- "exhaustAir" => 14
- );
- if (exists $ventilation{$ventilation}) {
- $ventilation = $ventilation{$ventilation};
- } else {
- return "Usage: $cmd variable wrong, choose off|1...4|auto|demand|supplyAir|exhaustAir.";
- }
- } elsif ($cmd eq "heatExchangerBypass") {
- $heatExchangerBypass = $a[1];
- return "Usage: $cmd variable is missing, choose opens|closes." if (!defined $heatExchangerBypass);
- shift(@a);
- my %heatExchangerBypass = (
- "closes" => 1,
- "opens" => 2
- );
- if (exists $heatExchangerBypass{$heatExchangerBypass}) {
- $heatExchangerBypass = $heatExchangerBypass{$heatExchangerBypass};
- } else {
- return "Usage: $cmd variable wrong, choose opens|closes.";
- }
- } elsif ($cmd eq "startTimerMode") {
- $startTimerMode = 1;
- } elsif ($cmd eq "CO2Threshold") {
- $CO2Threshold = $a[1];
- if (defined $CO2Threshold) {
- if ($CO2Threshold eq 'default') {
- $CO2Threshold = 127;
- readingsSingleUpdate($hash, "CO2Threshold", 'default', 1);
- } elsif ($CO2Threshold =~ m/^\d+$/ && $CO2Threshold >= 0 && $CO2Threshold <= 100) {
- readingsSingleUpdate($hash, "CO2Threshold", $CO2Threshold, 1);
- } else {
- return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
- }
- } else {
- return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
- }
- shift(@a);
- } elsif ($cmd eq "humidityThreshold") {
- $humidityThreshold = $a[1];
- if (defined $humidityThreshold) {
- if ($humidityThreshold eq 'default') {
- $humidityThreshold = 127;
- readingsSingleUpdate($hash, "humidityThreshold", 'default', 1);
- } elsif ($humidityThreshold =~ m/^\d+$/ && $humidityThreshold >= 0 && $humidityThreshold <= 100) {
- readingsSingleUpdate($hash, "humidityThreshold", $humidityThreshold, 1);
- } else {
- return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
- }
- } else {
- return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
- }
- shift(@a);
- } elsif ($cmd eq "airQuatityThreshold") {
- $airQuatityThreshold = $a[1];
- if (defined $airQuatityThreshold) {
- if ($airQuatityThreshold eq 'default') {
- $airQuatityThreshold = 127;
- readingsSingleUpdate($hash, "airQuatityThreshold", 'default', 1);
- } elsif ($airQuatityThreshold =~ m/^\d+$/ && $airQuatityThreshold >= 0 && $airQuatityThreshold <= 100) {
- readingsSingleUpdate($hash, "airQuatityThreshold", $airQuatityThreshold, 1);
- } else {
- return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
- }
- } else {
- return "Usage: $cmd variable is wrong, choose default|0 ... 100." ;
- }
- shift(@a);
- } elsif ($cmd eq "roomTemp") {
- $tempThreshold = $a[1];
- if (defined $tempThreshold) {
- if ($tempThreshold eq 'default') {
- $tempThreshold = 127;
- readingsSingleUpdate($hash, "roomTempSet", 'default', 1);
- } elsif ($tempThreshold =~ m/^[+-]?\d+$/ && $tempThreshold >= - 63 && $tempThreshold <= 63) {
- readingsSingleUpdate($hash, "roomTempSet", $tempThreshold, 1);
- $tempThreshold = abs($tempThreshold) | 0x40 if ($tempThreshold < 0);
- } else {
- return "Usage: $cmd variable is wrong, choose default|-63 ... 63." ;
- }
- } else {
- return "Usage: $cmd variable is wrong, choose default|-63 ... 63." ;
- }
- shift(@a);
- } else {
- $cmdList .= "ventilation:off,1,2,3,4,auto,demand,supplyAir,exhaustAir roomTemp heatExchangerBypass:closes,opens " .
- "startTimerMode:noArg CO2Threshold humidityThreshold airQuatityThreshold";
- return "Unknown argument $cmd, choose one of $cmdList";
- }
- $data = sprintf "%02X%02X%02X%02X%02X%02X", 32 | $ventilation, $heatExchangerBypass << 4, $startTimerMode << 7 | $CO2Threshold,
- $humidityThreshold, $airQuatityThreshold, $tempThreshold;
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "valveCtrl.00") {
- # Valve Control
- # (D2-A0-01)
- if (AttrVal($name, "devMode", "master") eq "slave") {
- # devNode slave
- if ($cmd eq "closed") {
- $rorg = "D2";
- $data = "01";
- } elsif ($cmd eq "open") {
- $rorg = "D2";
- $data = "02";
- } elsif ($cmd eq "teachIn") {
- $updateState = 0;
- ($err, $rorg, $data) = EnOcean_sndUTE(undef, $hash, "biDir",
- AttrVal($name, "uteResponseRequest", "yes"), "in", 0, "D2-A0-01");
- } elsif ($cmd eq "teachOut") {
- $updateState = 0;
- ($err, $rorg, $data) = EnOcean_sndUTE(undef, $hash, "biDir",
- AttrVal($name, "uteResponseRequest", "yes"), "out", 0, "D2-A0-01");
- } else {
- return "Unknown argument $cmd, choose one of " . $cmdList . "open:noArg closed:noArg teachIn:noArg teachOut:noArg";
- }
- } else {
- # devMode master
- if ($cmd eq "closes") {
- $rorg = "D2";
- $data = "01";
- } elsif ($cmd eq "opens") {
- $rorg = "D2";
- $data = "02";
- } else {
- return "Unknown argument $cmd, choose one of " . $cmdList . "opens:noArg closes:noArg";
- }
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "contact") {
- # 1BS Telegram
- # Single Input Contact (EEP D5-00-01)
- $rorg = "D5";
- my $setCmd;
- if ($cmd eq "teach") {
- $attr{$name}{eep} = "D5-00-01";
- CommandDeleteReading(undef, "$name .*");
- readingsSingleUpdate($hash, "teach", "1BS teach-in sent", 1);
- $setCmd = 0;
- $updateState = 0;
- } elsif ($cmd eq "closed") {
- $setCmd = 9;
- } elsif ($cmd eq "open") {
- $setCmd = 8;
- } else {
- return "Unknown argument $cmd, choose one of " . $cmdList . "open:noArg closed:noArg teach:noArg";
- }
- $data = sprintf "%02X", $setCmd;
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "genericProfile") {
- # Generic Profile
- my $channel = 0;
- my $devMode = AttrVal($name, "devMode", "master");
- my $header = 1;
- my ($setChannel, $setChannelName) = split(/-|:/, $cmd, 2);
- my ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax);
- my ($readingFormat, $readingName, $readingType, $readingUnit, $readingValue);
- my $gpDef = AttrVal($name, "gpDef", undef);
- return "Usage: Channel definition is missing" if (!defined $gpDef);
- my @gpDef = split("[ \t][ \t]*", $gpDef);
- if ($cmd eq "channelName") {
- # rename channel name
- ($setChannel, $setChannelName) = split(/-|:/, $a[1], 2);
- if (defined $gpDef[$setChannel]) {
- ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
- split(':', $gpDef[$setChannel]);
- # remove spaces und tabs
- #$channelName =~ tr/ \t//d;
- if ($setChannelName eq "?") {
- $channelName = "none";
- if ($channelType == 1 && defined $EnO_gpValueData{$signalType}{name}) {
- $channelName = $EnO_gpValueData{$signalType}{name};
- } elsif ($channelType == 2 && defined $EnO_gpValueFlag{$signalType}{name}) {
- $channelName = $EnO_gpValueFlag{$signalType}{name};
- } elsif ($channelType == 3 && defined $EnO_gpValueEnum{$signalType}{name}) {
- $channelName = $EnO_gpValueEnum{$signalType}{name};
- }
- } else {
- $channelName = $setChannelName;
- }
- $resolution = '' if (!defined $resolution);
- $engMin = '' if (!defined $engMin);
- $scalingMin = '' if (!defined $scalingMin);
- $engMax = '' if (!defined $engMax);
- $scalingMax = '' if (!defined $scalingMax);
- $gpDef[$setChannel] = $channelName . ':' . $channelDir . ':' . $channelType . ':' . $signalType . ':' . $valueType .
- ':' . $resolution . ':' . $engMin . ':' . $scalingMin . ':' . $engMax . ':' . $scalingMax;
- $attr{$name}{gpDef} = join(' ', @gpDef);
- shift @a;
- $updateState = 3;
- $channelType = 255;
- EnOcean_CommandSave(undef, undef);
- } else {
- return "Wrong parameter, channel $setChannel not defined.";
- }
- } elsif ($cmd eq "teachIn" && $devMode eq "slave") {
- # teach-in generic profile
- $rorg = "B0";
- my ($gpDefO, $gpDefI, $formatPattern, $teachInInfo);
- my $channelDirSeq = "--";
- my $comMode = 0;
- $attr{$name}{comMode} = "uniDir";
- $attr{$name}{eep} = "B0-00-00";
- # multicast teach-in
- $destinationID = "FFFFFFFF";
- my $productID = AttrVal($name, "productID", undef);
- if (defined $productID) {
- $productID = '0000000010' . EnOcean_convHexToBit($productID);
- } else {
- $productID = '';
- }
- while ($gpDef[$channel]) {
- ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
- split(':', $gpDef[$channel]);
- if ($channelDir eq "O") {
- #Log3 $name, 3, "EnOcean set $name channel: $channel channelDir: $channelDir seq: $channelDirSeq";
- #Log3 $name, 3, "EnOcean set $name channel: $channel channelType: $channelType signalType: $signalType valueType: $valueType";
- return "Usage: attr $name gpDef: O/I sequence error" if $channelDirSeq =~ m/.I$/;
- $channelDirSeq = "O-";
- # add channel-, signal- and valuetype
- $gpDefO .= substr(unpack('B8', pack('C', $channelType)), 6) .
- unpack('B8', pack('C', $signalType)) .
- substr(unpack('B8', pack('C', $valueType)), 6);
- if ($channelType == 1 || $channelType == 3) {
- # data, enumeration: add resolution
- $gpDefO .= substr(unpack('B8', pack('C', $resolution)), 4);
- }
- if ($channelType == 1) {
- # data: add engineering and scaling
- $gpDefO .= unpack('B8', pack('c', $engMin)) .
- substr(unpack('B8', pack('C', $scalingMin)), 4) .
- unpack('B8', pack('c', $engMax)) .
- substr(unpack('B8', pack('C', $scalingMax)), 4);
- }
- } elsif ($channelDir eq "I") {
- #Log3 $name, 3, "EnOcean set $name channel: $channel channelDir: $channelDir seq: $channelDirSeq";
- return "Usage: attr $name gpDef: O/I sequence error" if $channelDirSeq !~ m/^O./;
- $channelDirSeq = "OI";
- $gpDefI .= substr(unpack('B8', pack('C', $channelType)), 6) .
- unpack('B8', pack('C', $signalType)) .
- substr(unpack('B8', pack('C', $valueType)), 6);
- if ($channelType == 1 || $channelType == 3) {
- # data, enumeration: add resolution
- $gpDefI .= substr(unpack('B8', pack('C', $resolution)), 4);
- }
- if ($channelType == 1) {
- # data: add engineering and scaling
- $gpDefI .= unpack('B8', pack('c', $engMin)) .
- substr(unpack('B8', pack('C', $scalingMin)), 4) .
- unpack('B8', pack('c', $engMax)) .
- substr(unpack('B8', pack('C', $scalingMax)), 4);
- }
- }
- $channel ++;
- }
- if ($channelDirSeq eq "OI") {
- # await teach-in response
- $comMode = 1;
- $attr{$name}{comMode} = "biDir";
- # set flag for response request
- $hash->{IODev}{helper}{gpRespWait}{AttrVal($name, "subDef", $hash->{DEF})}{teachInReq} = "in";
- $hash->{IODev}{helper}{gpRespWait}{AttrVal($name, "subDef", $hash->{DEF})}{hash} = $hash;
- if (!exists($hash->{IODev}{Teach})) {
- # enable teach-in receiving for 3 sec
- $hash->{IODev}{Teach} = 1;
- my %timeoutHash = (hash => $IOHash, function => "gpRespTimeout", helper => "gpRespWait");
- RemoveInternalTimer(\%timeoutHash);
- InternalTimer(gettimeofday() + 3, "EnOcean_RespTimeout", \%timeoutHash, 0);
- }
- }
- $header = (0x7FF << 1 | $comMode) << 4;
- if ($channelDirSeq =~ m/.I$/) {
- # create teach-in information
- if (length($gpDefI) % 8) {
- # fill with trailing zeroes to x bytes
- $gpDefI .= 0 x (8 - length($gpDefI) % 8);
- }
- $teachInInfo = '0000000001' . unpack('B8', pack('C', length($gpDefI) / 4));
- }
- #Log3 $name, 3, "EnOcean set $name header: $header O: $gpDefO Info: $teachInInfo I: $gpDefI";
- # DophinView GP profile error if Product ID sent
- $data = $productID . $gpDefO . $teachInInfo . $gpDefI;
- #$data = $gpDefO . $teachInInfo . $gpDefI;
- if (length($data) % 8) {
- # fill with trailing zeroes to x bytes
- $data .= 0 x (8 - length($data) % 8);
- }
- $channelType = 0;
- EnOcean_CommandSave(undef, undef);
- $data = sprintf '%04X%s', $header, EnOcean_convBitToHex($data);
- my $teachInState = $comMode == 1 ? "teach-in sent, response requested" : "teach-in sent";
- readingsSingleUpdate($hash, "teach", "GP $teachInState", 1);
- } elsif ($cmd eq "teachOut" && $devMode eq "slave") {
- # teach out generic profile
- $rorg = "B0";
- $channelType = 0;
- my $comMode = 0;
- if (AttrVal($name, "comMode", "uniDir") eq "biDir") {
- $comMode = 1;
- $hash->{IODev}{helper}{gpRespWait}{AttrVal($name, "subDef", $hash->{DEF})}{teachInReq} = "out";
- $hash->{IODev}{helper}{gpRespWait}{AttrVal($name, "subDef", $hash->{DEF})}{hash} = $hash;
- if (!exists($hash->{IODev}{Teach})) {
- # enable teach-in receiving for 3 sec
- $hash->{IODev}{Teach} = 1;
- my %timeoutHash = (hash => $IOHash, function => "gpRespTimeout", helper => "gpRespWait");
- RemoveInternalTimer(\%timeoutHash);
- InternalTimer(gettimeofday() + 3, "EnOcean_RespTimeout", \%timeoutHash, 0);
- }
- }
- $data = sprintf '%04X', (0x7FF << 1 | $comMode) << 4 | 4;
- my $teachInState = $comMode == 1 ? "teach-in deletion sent, response requested" : "teach-in deletion sent";
- readingsSingleUpdate($hash, "teach", "GP $teachInState", 1);
- } elsif ($setChannel =~ m/^\d+$/ && defined $gpDef[$setChannel]) {
- # send selective data (GPSD)
- $rorg = "B3";
- # select channel
- ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
- split(':', $gpDef[$setChannel]);
- if ($channelName eq $setChannelName && $channelDir eq "O") {
- $channel = $setChannel;
- } else {
- return "Channel name wrong or no output channel";
- }
- } else {
- # command error
- my $channelCntr = 0;
- my @cmdList;
- while (defined $gpDef[$channelCntr]) {
- ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
- split(':', $gpDef[$channelCntr]);
- if ($channelDir ne "O") {
- $channelCntr ++;
- next;
- }
- if ($channelType == 1) {
- # data
- push @cmdList, sprintf('%02d', $channelCntr) . "-" . $channelName;
- } elsif ($channelType == 2) {
- # flag
- push @cmdList, sprintf('%02d', $channelCntr) . "-" . $channelName . ':' .
- $EnO_gpValueFlag{$signalType}{flag}{0} . "," .
- $EnO_gpValueFlag{$signalType}{flag}{1};
- } elsif ($channelType == 3) {
- # enumeration
- my $cmdListEnum = "";
- my $enum = 0;
- while (defined $EnO_gpValueEnum{$signalType}{enum}{$enum}) {
- $cmdListEnum .= $EnO_gpValueEnum{$signalType}{enum}{$enum} . ",";
- $enum ++;
- }
- push @cmdList, sprintf('%02d', $channelCntr) . "-" . $channelName . ':' . substr($cmdListEnum, 0, -1);
- }
- $channelCntr ++;
- }
- if (defined $cmdList[0] && $devMode eq "slave") {
- return "Unknown argument $cmd, choose one of " . $cmdList . 'channelName teachIn:noArg teachOut:noArg ' . join(" ", @cmdList);
- } elsif (defined $cmdList[0] && $devMode eq "master") {
- return "Unknown argument $cmd, choose one of " . $cmdList . 'channelName ' . join(" ", @cmdList);
- } else {
- return "Unknown argument $cmd, choose one of " . $cmdList . 'channelName';
- }
- }
- #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel";
- if ($channelType == 1) {
- # data
- if ($engMin * $EnO_scaling[$scalingMin] > $engMax * $EnO_scaling[$scalingMax]) {
- return "Usage: numerical value is missing" if (!defined $a[1]);
- if ($a[1] =~ m/^[-+]?\d*\.?\d+([eE][-+]?\d+)?$/ &&
- $a[1] >= $engMax * $EnO_scaling[$scalingMax] &&
- $a[1] <= $engMin * $EnO_scaling[$scalingMin]) {
- $data = int(2**$EnO_resolution[$resolution] * ($a[1] - $engMin * $EnO_scaling[$scalingMin]) /
- ($engMax * $EnO_scaling[$scalingMax] - $engMin * $EnO_scaling[$scalingMin]));
- #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel resolution: " . $EnO_resolution[$resolution] . " data: $data";
- if ($data >= 2**$EnO_resolution[$resolution]) {
- $data = 2**$EnO_resolution[$resolution] - 1;
- #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel resolution: " . $EnO_resolution[$resolution] . " data: $data";
- }
- shift @a;
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- } else {
- return "Usage: numerical value is missing" if (!defined $a[1]);
- if ($a[1] =~ m/^[-+]?\d*\.?\d+([eE][-+]?\d+)?$/ &&
- $a[1] >= $engMin * $EnO_scaling[$scalingMin] &&
- $a[1] <= $engMax * $EnO_scaling[$scalingMax]) {
- $data = int(2**$EnO_resolution[$resolution] * ($a[1] - $engMin * $EnO_scaling[$scalingMin]) /
- ($engMax * $EnO_scaling[$scalingMax] - $engMin * $EnO_scaling[$scalingMin]));
- #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel resolution: " . $EnO_resolution[$resolution] . " data: $data";
- if ($data >= 2**$EnO_resolution[$resolution]) {
- $data = 2**$EnO_resolution[$resolution] - 1;
- #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel resolution: " . $EnO_resolution[$resolution] . " data: $data";
- }
- shift @a;
- } else {
- return "Usage: $a[1] is not numeric or out of range";
- }
- }
- ($err, $logLevel, $response, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType) =
- EnOcean_gpConvDataToValue (undef, $hash, $channel, $data, $gpDef[$channel]);
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, $readingName, sprintf("$readingFormat", $readingValue));
- readingsBulkUpdate($hash, $readingName . "Unit", $readingUnit);
- readingsBulkUpdate($hash, $readingName . "ValueType", $valueType);
- readingsBulkUpdate($hash, $readingName . "ChannelType", $readingType);
- readingsEndUpdate($hash, 1);
- $data = EnOcean_gpConvSelDataToSndData($header, $channel, $EnO_resolution[$resolution], $data);
- } elsif ($channelType == 2) {
- # flag
- if (defined $EnO_gpValueFlag{$signalType}{flagInv}{$a[1]}) {
- $data = $EnO_gpValueFlag{$signalType}{flagInv}{$a[1]};
- #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel data: " . $EnO_gpValueFlag{$signalType}{flagInv}{$a[1]};
- shift @a;
- } else {
- return "Usage: $a[1] is unknown";
- }
- #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel data: $data";
- ($err, $logLevel, $response, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType)=
- EnOcean_gpConvDataToValue (undef, $hash, $channel, $data, $gpDef[$channel]);
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, $readingName, sprintf("$readingFormat", $readingValue));
- readingsBulkUpdate($hash, $readingName . "Unit", $readingUnit);
- readingsBulkUpdate($hash, $readingName . "ValueType", $valueType);
- readingsBulkUpdate($hash, $readingName . "ChannelType", $readingType);
- readingsEndUpdate($hash, 1);
- $data = sprintf '%04X', (($header << 6 | $channel) << 1 | $data) << 5;
- } elsif ($channelType == 3) {
- # enumeration
- if (defined $EnO_gpValueEnum{$signalType}{enumInv}{$a[1]}) {
- $data = $EnO_gpValueEnum{$signalType}{enumInv}{$a[1]};
- #Log3 $name, 3, "EnOcean set $name header: $header channel: $channel data: $data";
- shift @a;
- } else {
- return "Usage: $a[1] is unknown";
- }
- ($err, $logLevel, $response, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType)=
- EnOcean_gpConvDataToValue (undef, $hash, $channel, $data, $gpDef[$channel]);
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, $readingName, sprintf("$readingFormat", $readingValue));
- readingsBulkUpdate($hash, $readingName . "Unit", $readingUnit);
- readingsBulkUpdate($hash, $readingName . "ValueType", $valueType);
- readingsBulkUpdate($hash, $readingName . "ChannelType", $readingType);
- readingsEndUpdate($hash, 1);
- $data = EnOcean_gpConvSelDataToSndData($header, $channel, $EnO_resolution[$resolution], $data);
- }
- Log3 $name, 3, "EnOcean set $name $cmd";
- } elsif ($st eq "raw") {
- # sent raw data
- if ($cmd eq "4BS"){
- # 4BS Telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{8}$/) {
- $data = uc($a[1]);
- $rorg = "A5";
- } else {
- return "Wrong parameter, choose 4BS <data 4 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "1BS") {
- # 1BS Telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2}$/) {
- $data = uc($a[1]);
- $rorg = "D5";
- } else {
- return "Wrong parameter, choose 1BS <data 1 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "RPS") {
- # RPS Telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2}$/) {
- $data = uc($a[1]);
- $rorg = "F6";
- } else {
- return "Wrong parameter, choose RPS <data 1 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "MSC") {
- # MSC Telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) {
- $data = uc($a[1]);
- $rorg = "D1";
- } else {
- return "Wrong parameter, choose MSC <data 1 ... 14 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "UTE") {
- # UTE Telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{14}$/) {
- $data = uc($a[1]);
- $rorg = "D4";
- } else {
- return "Wrong parameter, choose UTE <data 7 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "VLD") {
- # VLD Telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) {
- $data = uc($a[1]);
- $rorg = "D2";
- } else {
- return "Wrong parameter, choose VLD <data 1 ... 14 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "GPTI") {
- # GP teach-in request telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,1024}$/ && !(length($a[1]) % 2)) {
- $data = uc($a[1]);
- $rorg = "B0";
- } else {
- return "Wrong parameter, choose GPTI <data 1 ... 512 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "GPTR") {
- # GP teach-in response telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,1024}$/ && !(length($a[1]) % 2)) {
- $data = uc($a[1]);
- $rorg = "B1";
- } else {
- return "Wrong parameter, choose GPTR <data 1 ... 512 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "GPCD") {
- # GP complete date telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,1024}$/ && !(length($a[1]) % 2)) {
- $data = uc($a[1]);
- $rorg = "B2";
- } else {
- return "Wrong parameter, choose GPCD <data 1 ... 512 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "GPSD") {
- # GP selective data telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,1024}$/ && !(length($a[1]) % 2)) {
- $data = uc($a[1]);
- $rorg = "B3";
- } else {
- return "Wrong parameter, choose GPSD <data 1 ... 512 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "SEC") {
- # secure telegram
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) {
- $data = uc($a[1]);
- $rorg = "30";
- } else {
- return "Wrong parameter, choose SEC <data 1 ... 14 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "ENC") {
- # secure telegram with encapsulation
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) {
- $data = uc($a[1]);
- $rorg = "31";
- } else {
- return "Wrong parameter, choose ENC <data 1 ... 14 Byte hex> [status 1 Byte hex]";
- }
- } elsif ($cmd eq "STE") {
- # secure Teach-In
- if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) {
- $data = uc($a[1]);
- $rorg = "35";
- } else {
- return "Wrong parameter, choose STE <data 1 ... 14 Byte hex> [status 1 Byte hex]";
- }
- } else {
- return "Unknown argument $cmd, choose one of 1BS 4BS ENC GPCD GPSD GPTI GPTR MSC RPS SEC STE UTE VLD";
- }
- if ($a[2]) {
- if ($a[2] !~ m/^[\dA-Fa-f]{2}$/) {
- return "Wrong status parameter, choose $cmd $a[1] [status 1 Byte hex]";
- }
- $status = uc($a[2]);
- splice(@a,2,1);
- }
- $updateState = 0;
- readingsSingleUpdate($hash, "RORG", $cmd, 1);
- readingsSingleUpdate($hash, "dataSent", $data, 1);
- readingsSingleUpdate($hash, "statusSent", $status, 1);
- Log3 $name, 3, "EnOcean set $name $cmd $data $status";
- shift(@a);
- } elsif ($st eq "remote") {
- return "Unknown argument $cmd, choose one of $cmdList";
- } else {
- # subtype does not support set commands
- $updateState = -1;
- if (AttrVal($name, "remoteManagement", "off") eq "manager") {
- return "Unknown argument $cmd, choose one of $cmdList";
- } else {
- return;
- }
- }
- # set reading state if confirmation telegram is not expected
- if($updateState == 1 && $updateStateAttr =~ m/^default|yes$/) {
- readingsSingleUpdate($hash, "state", $cmd, 1);
- } elsif ($updateState == 0 && $updateStateAttr eq "yes") {
- readingsSingleUpdate($hash, "state", $cmd, 1);
- #} elsif ($updateState == 3) {
- #internal command
- #} else {
- # readingsSingleUpdate($hash, ".info", "await_confirm", 0);
- }
- # send commands
- if($updateState >= -1 && $updateState <= 1) {
- EnOcean_SndCdm(undef, $hash, $packetType, $rorg, $data, $subDef, $status, $destinationID);
- EnOcean_observeInit(1, $hash, @cmdObserve);
- if ($switchMode eq "pushbutton" && $cmd1 ne "released") {
- my @timerCmd = ($name, "released");
- my %par = (hash => $hash, timerCmd => \@timerCmd);
- InternalTimer(gettimeofday() + 0.1, "EnOcean_TimerSet", \%par, 0);
- }
- }
- #Log3 $name, 3, "EnOcean set $name remainder of CmdArg: " . join(' ' ,@a) if (@a > 0);
- }
- return undef;
- }
- # parse and display the incoming telegrams
- sub EnOcean_Parse($$)
- {
- my ($iohash, $msg) = @_;
- my $IODev = $iohash->{NAME};
- my ($hash, $name, $filelogName, $rorgname);
- my ($ctrl, $err, $logLevel, $response);
- Log3 $IODev, 4, "EnOcean received via $IODev: $msg";
- my @msg = split(':', $msg);
- my ($rorg, $data, $senderID, $status, $odata, $subDef, $destinationID, $funcNumber, $manufID, $RSSI, $delay, $subTelNum);
- my $packetType = hex($msg[1]);
- my @event;
- if ($packetType == 1) {
- # packet type RADIO
- #EnOcean:PacketType:RORG:MessageData:SourceID:Status:OptionalData
- (undef, undef, $rorg, $data, $senderID, $status, $odata) = @msg;
- $odata =~ m/^(..)(........)(..)(..)$/;
- ($subTelNum, $destinationID, $RSSI) = (hex($1), $2, hex($3));
- if (exists $modules{EnOcean}{defptr}{$senderID}) {
- $hash = $modules{EnOcean}{defptr}{$senderID};
- } elsif ($destinationID ne 'FFFFFFFF') {
- $hash = $modules{EnOcean}{defptr}{$destinationID};
- }
- $rorgname = $EnO_rorgname{$rorg};
- if (!$rorgname) {
- if($hash) {
- Log3 $hash->{NAME}, 2, "EnOcean $hash->{NAME} RORG $rorg unknown.";
- } else {
- Log3 undef, 2, "EnOcean RORG $rorg received from $senderID unknown.";
- }
- return "";
- }
- if ($rorg eq "40") {
- # chained data message (CDM)
- $data =~ m/^(..)(.*)$/;
- # SEQ evaluation?
- my ($seq, $idx) = (hex($1) & 0xC0, hex($1) & 0x3F);
- $data = $2;
- if ($idx == 0) {
- # first message part
- delete $iohash->{helper}{cdm};
- $data =~ m/^(....)(..)(.*)$/;
- $iohash->{helper}{cdm}{len} = hex($1);
- $iohash->{helper}{cdm}{rorg} = $2;
- $iohash->{helper}{cdm}{data}{$idx} = $3;
- $iohash->{helper}{cdm}{lenCounter} = length($3) / 2;
- my %functionHash = (hash => $iohash, function => "cdm");
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 1, "EnOcean_helperClear", \%functionHash, 0);
- #Log3 $IODev, 3, "EnOcean $IODev CDM timer started";
- } else {
- $iohash->{helper}{cdm}{data}{$idx} = $data;
- $iohash->{helper}{cdm}{lenCounter} += length($data) / 2;
- }
- if ($iohash->{helper}{cdm}{lenCounter} >= $iohash->{helper}{cdm}{len}) {
- # data message complete
- # reconstruct RORG, DATA
- my ($idx, $dataPart, @data);
- while (($idx, $dataPart) = each(%{$iohash->{helper}{cdm}{data}})) {
- $data[$idx] = $iohash->{helper}{cdm}{data}{$idx};
- }
- $data = join('', @data);
- $msg[3] = $data;
- $rorg = $iohash->{helper}{cdm}{rorg};
- $msg[2] = $rorg;
- $msg = join(':', @msg);
- $rorgname = $EnO_rorgname{$rorg};
- if (!$rorgname) {
- Log3 undef, 2, "EnOcean RORG $rorg received from $senderID unknown.";
- return "";
- }
- delete $iohash->{helper}{cdm};
- my %functionHash = (hash => $iohash, function => "cdm");
- RemoveInternalTimer(\%functionHash);
- #Log3 $IODev, 3, "EnOcean $IODev CDM concatenated DATA $data";
- } else {
- # wait for next data message part
- return $IODev;
- }
- }
- if($hash) {
- $name = $hash->{NAME};
- if ($rorg eq 'A5' && !(hex(substr($data, 6, 2)) & 8) &&
- hex(substr($data, 0, 2)) >> 2 == 0x3F &&
- (hex(substr($data, 0, 4)) >> 3 & 0x7F) == 0) {
- # find Radio Link Test device
- my $rltHash;
- foreach my $dev (keys %defs) {
- next if ($defs{$dev}{TYPE} ne 'EnOcean');
- next if (!exists($attr{$dev}{subType}));
- next if ($attr{$dev}{subType} ne 'radioLinkTest');
- $rltHash = $defs{$dev};
- last;
- }
- if (defined $rltHash) {
- if ($rltHash != $hash) {
- if (ReadingsVal($rltHash->{NAME}, 'state', '') =~ m/^standby$/) {
- # device is temporarily subType radioLinkTest
- @{$rltHash->{helper}{rlt}{oldDev}} = ($hash, $name, $hash->{DEF});
- CommandModify(undef, "$name 00000000");
- delete $modules{EnOcean}{defptr}{$hash->{DEF}};
- $hash = $rltHash;
- $name = $hash->{NAME};
- CommandModify(undef, "$name $senderID");
- $modules{EnOcean}{defptr}{$senderID} = $hash;
- } else {
- # Radio Link Test Devices not ready at the moment
- return '';
- }
- }
- Log3 $name, 4, "EnOcean received RLT Query messsage DATA: $data from DeviceID: $senderID";
- } else {
- Log3 $name, 4, "EnOcean received RLT Query messsage DATA: $data from DeviceID: $senderID";
- }
- } else {
- Log3 $name, 4, "EnOcean $name received PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status";
- }
- $manufID = uc(AttrVal($name, "manufID", ""));
- $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
- $filelogName = "FileLog_$name";
- #if ($IODev ne $hash->{IODev}{NAME}) {
- # transceiver wrong
- # Log3 $name, 4, "EnOcean $name locked telegram via $IODev PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status";
- # return "";
- #}
- } else {
- # SenderID unknown, created new device
- Log3 undef, 5, "EnOcean received PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status";
- my $learningMode = AttrVal($IODev, "learningMode", "demand");
- my $ret = "UNDEFINED EnO_$senderID EnOcean $senderID $msg";
- if ($rorgname =~ m/^GPCD|GPSD|SMLRNANS|SMREC|SMMBOX$/) {
- Log3 undef, 4, "EnOcean Received $rorgname telegram to the unknown device with SenderID $senderID.";
- return '';
- } elsif ($rorg eq 'A5' &&
- hex(substr($data, 0, 2)) >> 2 == 0x3F &&
- (hex(substr($data, 0, 4)) >> 3 & 0x7F) == 0 &&
- !(hex(substr($data, 6, 2)) & 8)) {
- # find Radio Link Test device
- my $rltHash;
- foreach my $dev (keys %defs) {
- next if ($defs{$dev}{TYPE} ne 'EnOcean');
- next if (!exists($attr{$dev}{subType}));
- next if ($attr{$dev}{subType} ne 'radioLinkTest');
- $rltHash = $defs{$dev};
- last;
- }
- if ($rltHash) {
- return '' if (ReadingsVal($rltHash->{NAME}, 'state', '') !~ m/^standby$/);
- $hash = $rltHash;
- $name = $hash->{NAME};
- CommandModify(undef, "$name $senderID");
- $modules{EnOcean}{defptr}{$senderID} = $hash;
- $filelogName = "FileLog_$name";
- Log3 $name, 4, "EnOcean RLT Query messsage DATA: $data from SenderID: $senderID received";
- $manufID = uc(AttrVal($name, "manufID", ""));
- $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
- } else {
- Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and RLT Query message, please define it.";
- return $ret;
- }
- } elsif (exists $iohash->{helper}{teachConfirmWaitHash}) {
- # teach-in response with confirm telegram, assign remote device
- $hash = $iohash->{helper}{teachConfirmWaitHash};
- $name = $hash->{NAME};
- # substitute subDef with DEF
- delete $modules{EnOcean}{defptr}{$hash->{DEF}};
- $modules{EnOcean}{defptr}{$senderID} = $hash;
- $attr{$name}{subDef} = $hash->{DEF};
- $subDef = $attr{$name}{subDef};
- $hash->{DEF} = $senderID;
- $attr{$name}{comMode} = "confirm";
- $manufID = uc(AttrVal($name, "manufID", ""));
- $filelogName = "FileLog_$name";
- # clear teach-in request
- delete $iohash->{helper}{teachConfirmWaitHash};
- # store changes
- EnOcean_CommandSave(undef, undef);
- push @event, "3:teach:4BS teach-in accepted";
- Log3 $name, 2, "EnOcean $name remote device with SenderID $senderID assigned";
- } elsif ($learningMode eq "demand" && $iohash->{Teach}) {
- Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
- return $ret;
- } elsif ($learningMode eq "nearfield" && $iohash->{Teach} && $RSSI <= 60) {
- Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
- return $ret;
- } elsif ($learningMode eq "always") {
- if ($rorgname =~ m/^UTE|GPTI|GPTR$/) {
- if ($iohash->{Teach}) {
- Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
- return $ret;
- } else {
- Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, activate learning mode.";
- return "";
- }
- } elsif ($rorgname =~ m/^SMLRNREQ$/) {
- if ($iohash->{SmartAckLearn}) {
- Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
- return $ret;
- } else {
- Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, activate learning mode.";
- return "";
- }
- } else {
- Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
- return $ret;
- }
- } else {
- Log3 undef, 4, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, activate learning mode.";
- return "";
- }
- }
- } elsif ($packetType == 2) {
- # packet type RESPONSE
- #EnOcean:PacketType:ResposeCode:MessageData:OptionalData
- (undef, undef, $funcNumber, $data, $odata) = @msg;
- $data = defined($data) ? $data : '';
- $odata = defined($odata) ? $odata : '';
- my %codes = (
- "00" => "OK",
- "01" => "ERROR",
- "02" => "NOT_SUPPORTED",
- "03" => "WRONG_PARAM",
- "04" => "OPERATION_DENIED",
- "82" => "FLASH_HW_ERROR",
- "90" => "BASEID_OUT_OF_RANGE",
- "91" => "BASEID_MAX_REACHED",
- );
- my $rcTxt = $codes{$funcNumber} if($codes{$funcNumber});
- if($hash) {
- $name = $hash->{NAME};
- $funcNumber = hex($funcNumber);
- Log3 $name, $funcNumber == 0 ? 5 : 2, "EnOcean $name RESPONSE: $rcTxt DATA: $data ODATA: $odata";
- return $name;
- } else {
- Log3 undef, $funcNumber == 0 ? 5 : 2, "EnOcean RESPONSE: $rcTxt DATA: $data ODATA: $odata";
- return "";
- }
- } elsif ($packetType == 4) {
- # packet type EVENT
- #EnOcean:PacketType:EventCode:MessageData
- (undef, undef, $funcNumber, $data) = @msg;
- Log3 undef, 5, "EnOcean EventCode: $funcNumber DATA: $data";
- $funcNumber = hex($funcNumber);
- if ($funcNumber == 2) {
- # smart Ack confirm learn
- my ($priority, $rorg, $func, $type, $postmasterID, $hopCount);
- my $responseTime = 150;
- my $sendData = '';
- my $sendHash = defined($hash) ? $hash : $iohash;
- my $sendName = $sendHash->{NAME};
- $data =~ m/^(..)(....)(..)(..)(..)(..)(........)(........)(..)$/;
- ($priority, $manufID, $rorg, $func, $type, $RSSI, $postmasterID, $senderID, $hopCount) = (hex($1), $2, $3, $4, $5, hex($6), $7, $8, $9);
- #Log3 undef, 2, "EnOcean IOHASH: $iohash PRIORITY: $priority SmartAckLearn: " . (exists($iohash->{SmartAckLearn}) ? 1 : 0) .
- # " SmartAckLearnWait: " . (exists($iohash->{helper}{smartAckLearnWait}) ? $iohash->{helper}{smartAckLearnWait} : '');
- if ($iohash->{SmartAckLearn} ||
- exists($iohash->{helper}{smartAckLearnWait}) && $iohash->{helper}{smartAckLearnWait} eq $sendName) {
- my $subType = "$rorg.$func.$type";
- if (exists $EnO_eepConfig{$subType}) {
- # EEP supported
- if (exists $modules{EnOcean}{defptr}{$senderID}) {
- $hash = $modules{EnOcean}{defptr}{$senderID};
- }
- $rorgname = $EnO_rorgname{$rorg};
- if($hash) {
- delete $iohash->{helper}{smartAckLearnWait};
- $name = $hash->{NAME};
- $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
- if (($priority & 15) == 15) {
- # device exists, learn OUT
- # send response, to delete mailbox
- $rorg = substr(AttrVal($name, "eep", ' '), 0, 2);
- $sendData = sprintf "00%04X20", $responseTime;
- EnOcean_SndRadio(undef, $hash, 2, $rorg, $sendData, $subDef, '00', $hash->{DEF});
- Log3 $name, 2, "EnOcean $name Smart Ack teach-out send";
- Log3 $name, 2, "EnOcean $name device $name deleted";
- CommandDelete(undef, $name);
- EnOcean_CommandSave(undef, undef);
- return '';
- } else {
- # config device
- Log3 $name, 5, "EnOcean $name received PacketType: $packetType EventCode: $funcNumber DATA: $data";
- $attr{$name}{subType} = $EnO_eepConfig{$subType}{attr}{subType};
- $attr{$name}{eep} = "$rorg-$func-$type";
- $manufID = sprintf "%03X", hex($manufID) & 0x7FF;
- $attr{$name}{manufID} = $manufID;
- $manufID = $EnO_manuf{$manufID} if(exists $EnO_manuf{$manufID});
- $attr{$name}{postmasterID} = $postmasterID;
- $hash->{SmartAckRSSI} = $RSSI;
- $attr{$name}{teachMethod} = 'smartAck';
- foreach my $attrCntr (keys %{$EnO_eepConfig{$subType}{attr}}) {
- if ($attrCntr ne "subDef") {
- $attr{$name}{$attrCntr} = $EnO_eepConfig{$subType}{attr}{$attrCntr};
- }
- }
- #if (defined AttrVal($name, 'subDef', undef)) {
- # $subDef = $attr{$name}{subDef};
- #} else {
- #$subDef = $postmasterID;
- #$attr{$name}{subDef} = $postmasterID;
- #}
- $subDef = '0' x 8;
- $attr{$name}{subDef} = '0' x 8;
- # sent response to create mailbox
- $sendData = sprintf "00%04X00", $responseTime;
- EnOcean_SndRadio(undef, $hash, 2, $rorg, $sendData, $subDef, '00', $hash->{DEF});
- readingsSingleUpdate($hash, "teach", "Smart Ack teach-in accepted EEP $rorg-$func-$type Manufacturer: $manufID", 1);
- Log3 $name, 2, "EnOcean $name Smart Ack teach-in accepted EEP $rorg-$func-$type Manufacturer: $manufID";
- EnOcean_CommandSave(undef, undef);
- return $name;
- }
- } else {
- # device unknown
- if (($priority & 5) == 5) {
- # Smart Ack priority ok (place for mailbox, good RSSI, Local)
- Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and Smart Ack learn In message, please define it.";
- return "UNDEFINED EnO_$senderID EnOcean $senderID $msg";
- } elsif (($priority & 5) == 1) {
- # Smart Ack no place for mailbox
- $sendData = sprintf "00%04X12", $responseTime;
- EnOcean_SndRadio(undef, $sendHash, 2, $rorg, $sendData, $postmasterID, '00', $senderID);
- Log3 $sendName, 2, "EnOcean $sendName Smart Ack learn in from SenderID $senderID Discard learn in, postmaster has no place for further mailbox";
- return '';
- } else {
- # Smart Ack priority to low
- $sendData = sprintf "00%04X13", $responseTime;
- EnOcean_SndRadio(undef, $sendHash, 2, $rorg, $sendData, $postmasterID, '00', $senderID);
- Log3 $sendName, 2, "EnOcean $sendName Smart Ack learn in from SenderID $senderID Discard learn in, priority to low";
- return '';
- }
- }
- } else {
- # EEP not supported
- # sent response
- $sendData = sprintf "00%04X11", $responseTime;
- EnOcean_SndRadio(undef, $sendHash, 2, $rorg, $sendData, $postmasterID, '00', $senderID);
- Log3 $sendName, 2, "EnOcean $sendName Smart Ack learn in from SenderID $senderID with EEP $rorg-$func-$type not supported";
- return '';
- }
- } else {
- # smart ack learn not activated
- $sendData = sprintf "00%04XFF", $responseTime;
- EnOcean_SndRadio(undef, $sendHash, 2, $rorg, $sendData, $postmasterID, '00', $senderID);
- Log3 $sendName, 2, "EnOcean $sendName Smart Ack learn in from SenderID $senderID received, activate learning";
- return '';
- }
- }
- } elsif ($packetType == 6) {
- # packet type SMART ACK
- #EnOcean:PacketType:SmartAckCode:MessageData
- (undef, undef, $funcNumber, $data) = @msg;
- if($hash) {
- $name = $hash->{NAME};
- $subDef = uc(AttrVal($name, "subDef", $hash->{DEF}));
- Log3 $name, 2, "EnOcean $name received PacketType: $packetType Function Number: $funcNumber DATA: $data";
- } else {
- Log3 undef, 2, "EnOcean received PacketType: $packetType Function Number: $funcNumber DATA: $data";
- return "";
- }
- $funcNumber = hex($funcNumber);
- } elsif ($packetType == 7) {
- # packet type REMOTE_MAN_COMMAND
- #EnOcean:PacketType:RORG:MessageData:SourceID:DestinationID:FunctionNumber:ManufacturerID:RSSI:Delay
- (undef, undef, $rorg, $data, $senderID, $destinationID, $funcNumber, $manufID, $RSSI, $delay) = @msg;
- if (exists $modules{EnOcean}{defptr}{$senderID}) {
- $hash = $modules{EnOcean}{defptr}{$senderID};
- $name = $hash->{NAME};
- if (!exists $attr{$name}{remoteID}) {
- $attr{$name}{remoteID} = $senderID;
- Log3 $name, 2, "EnOcean $name remoteID $senderID assigned";
- if (exists($iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash}) &&
- $iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash} == $hash) {
- delete $iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash};
- }
- }
- } elsif (exists($iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash})) {
- # the remoteID is assigned to the requesting device
- $hash = $iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash};
- $name = $hash->{NAME};
- $subDef = '0' x 8;
- $attr{$name}{remoteID} = $senderID;
- $modules{EnOcean}{defptr}{$senderID} = $hash;
- delete $iohash->{helper}{remoteAnswerWait}{hex($funcNumber)}{hash};
- Log3 $name, 2, "EnOcean $name remoteID $senderID assigned";
- #EnOcean_CommandSave(undef, undef);
- } elsif ($destinationID ne 'FFFFFFFF') {
- $hash = $modules{EnOcean}{defptr}{$destinationID};
- }
- #$funcNumber = substr($funcNumber, 1);
- if($hash) {
- $name = $hash->{NAME};
- $manufID = substr($manufID, 1);
- $rorgname = $EnO_rorgname{$rorg};
- $subDef = '0' x 8;
- Log3 $name, 4, "EnOcean $name received PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID
- DestinationID $destinationID Function Number: " . substr($funcNumber, 1) . " ManufacturerID: $manufID";
- $delay = hex($delay);
- $funcNumber = hex($funcNumber);
- $RSSI = hex($RSSI);
- } else {
- if (hex($funcNumber) == 4) {
- #Log3 undef, 1, "EnOcean received remote management query ID from unknown SenderID $senderID, please define device.";
- #return "UNDEFINED EnO_$senderID EnOcean $senderID $msg";
- return '';
- } else {
- return '';
- }
- }
- }
- my $eep = AttrVal($name, "eep", undef);
- my $smartAckLearn = $hash->{IODev}{SmartAckLearn};
- my $teach = $hash->{IODev}{Teach};
- my ($deleteDevice, $oldDevice);
- if (AttrVal($name, "secLevel", "off") =~ m/^encapsulation|encryption$/ &&
- AttrVal($name, "secMode", "") =~ m/^rcv|biDir$/) {
- if ($rorg eq "30" || $rorg eq "31") {
- Log3 $name, 5, "EnOcean $name secure data RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status";
- ($err, $rorg, $data) = EnOcean_sec_convertToNonsecure($hash, $rorg, $data);
- if (defined $err) {
- Log3 $name, 2, "EnOcean $name security ERROR: $err";
- return "";
- }
- } elsif ($rorg eq "35") {
- # pass second teach-in telegram
- } else {
- Log3 $name, 2, "EnOcean $name unsecure telegram locked";
- return "";
- }
- if ($rorg eq "32") {
- if (defined $eep) {
- # reconstruct RORG
- $rorg = substr($eep, 0, 2);
- Log3 $name, 5, "EnOcean $name decrypted data RORG: 32 >> $rorg DATA: $data SenderID: $senderID STATUS: $status";
- } else {
- # Teach-In telegram expected
- #####
- # telegram analyse needed >> 1BS, 4BS, UTE
- if (length($data) == 14) {
- $rorg = "D4";
- } else {
- Log3 $name, 2, "EnOcean $name security teach-in failed, UTE message is missing";
- return "";
- }
- }
- }
- }
- if ($rorg eq "A6") {
- # addressing destination telegram (ADT)
- # reconstruct RORG, DATA
- $data =~ m/^(..)(.*)(........)$/;
- ($rorg, $data) = ($1, $2);
- $rorgname = $EnO_rorgname{$rorg};
- if (!$rorgname) {
- Log3 undef, 1, "EnOcean RORG $rorg received from $senderID unknown.";
- return "";
- }
- if ($destinationID ne $3) {
- Log3 $name, 1, "EnOcean $name ADT DestinationID wrong.";
- return "";
- }
- Log3 $name, 1, "EnOcean $name ADT decapsulation RORG: $rorg DATA: $data DestinationID: $3";
- }
- # compute data
- # extract data bytes $db[x] ... $db[0]
- my @db;
- my $dbCntr = 0;
- for (my $strCntr = length($data) / 2 - 1; $strCntr >= 0; $strCntr --) {
- $db[$dbCntr] = hex substr($data, $strCntr * 2, 2);
- $dbCntr ++;
- }
- my $model = AttrVal($name, "model", "");
- my $st = AttrVal($name, "subType", "");
- my $subtypeReading = AttrVal($name, "subTypeReading", undef);
- $st = $subtypeReading if (defined $subtypeReading);
- if ($rorg eq "F6") {
- # RPS Telegram
- my $event = "state";
- my $nu = (hex($status) & 0x10) >> 4;
- # unused flags (AFAIK)
- #push @event, "1:T21:".((hex($status) & 0x20) >> 5);
- #push @event, "1:NU:$nu";
- if ($st eq "FRW" || $st eq "smokeDetector.02") {
- # smoke detector
- if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} != $db[0] || ReadingsVal($name, 'alarm', '') eq 'dead_sensor') {
- if ($db[0] == 0x30) {
- push @event, "3:alarm:off";
- push @event, "3:battery:low";
- $msg = ReadingsVal($name, 'state', 'off');
- } elsif ($db[0] == 0x10) {
- push @event, "3:battery:ok";
- push @event, "3:alarm:smoke-alarm";
- $msg = "smoke-alarm";
- } elsif ($db[0] == 0) {
- push @event, "3:alarm:off";
- push @event, "3:battery:ok";
- $msg = "off";
- }
- push @event, "3:$event:$msg";
- $hash->{helper}{lastEvent} = $db[0];
- }
- @{$hash->{helper}{alarmTimer}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
- RemoveInternalTimer($hash->{helper}{alarmTimer});
- InternalTimer(gettimeofday() + 1320, 'EnOcean_readingsSingleUpdate', $hash->{helper}{alarmTimer}, 0);
- } elsif ($st eq "windSpeed.00") {
- # wind speed threshold detector
- if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} != $db[0] || ReadingsVal($name, 'alarm', '') eq 'dead_sensor') {
- push @event, "3:alarm:off";
- if ($db[0] == 0x30) {
- push @event, "3:battery:low";
- $msg = ReadingsVal($name, 'state', 'off');
- } elsif ($db[0] == 0x10) {
- push @event, "3:windSpeed:on";
- push @event, "3:battery:ok";
- $msg = "on";
- } elsif ($db[0] == 0) {
- push @event, "3:windSpeed:off";
- push @event, "3:battery:ok";
- $msg = "off";
- }
- push @event, "3:$event:$msg";
- $hash->{helper}{lastEvent} = $db[0];
- }
- @{$hash->{helper}{alarmTimer}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
- RemoveInternalTimer($hash->{helper}{alarmTimer});
- InternalTimer(gettimeofday() + 1320, 'EnOcean_readingsSingleUpdate', $hash->{helper}{alarmTimer}, 0);
- } elsif ($model eq "FAE14" || $model eq "FHK14" || $model eq "FHK61") {
- # heating/cooling relay FAE14, FHK14, untested
- $event = "controllerMode";
- if ($db[0] == 0x30) {
- # night reduction 2 K
- push @event, "3:energyHoldOff:holdoff";
- $msg = "auto";
- } elsif ($db[0] == 0x10) {
- # off
- push @event, "3:energyHoldOff:normal";
- $msg = "off";
- } elsif ($db[0] == 0x70) {
- # on
- push @event, "3:energyHoldOff:normal";
- $msg = "auto";
- } elsif ($db[0] == 0x50) {
- # night reduction 4 K
- push @event, "3:energyHoldOff:holdoff";
- $msg = "auto";
- }
- push @event, "3:$event:$msg";
- } elsif ($st eq "gateway") {
- # Eltako switching, dimming
- if ($db[0] == 0x70) {
- $msg = "on";
- } elsif ($db[0] == 0x50) {
- $msg = "off";
- } elsif ($db[0] == 0x30) {
- $event = 'alert';
- $msg = "on";
- } elsif ($db[0] == 0x10) {
- $event = 'alert';
- $msg = "off";
- }
- push @event, "3:$event:$msg";
- } elsif ($st eq "manufProfile" && $manufID eq "00D") {
- # Eltako shutter
- if ($db[0] == 0x70) {
- # open
- push @event, "3:endPosition:open_ack";
- $msg = "open_ack";
- } elsif ($db[0] == 0x50) {
- # closed
- push @event, "3:position:100";
- push @event, "3:anglePos:" . AttrVal($name, "angleMax", 90);
- push @event, "3:endPosition:closed";
- $msg = "closed";
- } elsif ($db[0] == 0) {
- # not reached or not available
- push @event, "3:endPosition:not_reached";
- $msg = "not_reached";
- } elsif ($db[0] == 1) {
- # up
- push @event, "3:endPosition:not_reached";
- $msg = "up";
- } elsif ($db[0] == 2) {
- # down
- push @event, "3:endPosition:not_reached";
- $msg = "down";
- }
- push @event, "3:$event:$msg";
- } elsif ($st eq "occupSensor.01" && $model eq "tracker") {
- # tracker
- push @event, "3:button:" . ($db[0] & 0x70 ? "pressed" : "released");
- } elsif ($st eq "switch.7F" && $manufID eq "00D") {
- $msg = $EnO_ptm200btn[($db[0] & 0xE0) >> 5];
- $msg .= "," . $EnO_ptm200btn[($db[0] & 0x0E) >> 1] if ($db[0] & 1);
- $msg .= " released" if (!($db[0] & 0x10));
- push @event, "3:buttons:" . ($db[0] & 0x10 ? "pressed" : "released");
- if ($msg =~ m/A0/) {push @event, "3:channelA:A0";}
- if ($msg =~ m/AI/) {push @event, "3:channelA:AI";}
- if ($msg =~ m/B0/) {push @event, "3:channelB:B0";}
- if ($msg =~ m/BI/) {push @event, "3:channelB:BI";}
- if ($msg =~ m/C0/) {push @event, "3:channelC:C0";}
- if ($msg =~ m/CI/) {push @event, "3:channelC:CI";}
- if ($msg =~ m/D0/) {push @event, "3:channelD:D0";}
- if ($msg =~ m/DI/) {push @event, "3:channelD:DI";}
- push @event, "3:$event:$msg";
- } else {
- if ($nu) {
- if ($st eq "keycard") {
- # Key Card, not tested
- $msg = "keycard_inserted" if ($db[0] == 112);
- } elsif ($st eq "liquidLeakage") {
- # liquid leakage sensor, not tested
- $msg = "wet" if ($db[0] == 0x11);
- } else {
- # Theoretically there can be a released event with some of the A0, BI
- # pins set, but with the plastic cover on this wont happen.
- $msg = $EnO_ptm200btn[($db[0] & 0xE0) >> 5];
- $msg .= "," . $EnO_ptm200btn[($db[0] & 0x0E) >> 1] if ($db[0] & 1);
- $msg .= " released" if (!($db[0] & 0x10));
- push @event, "3:buttons:" . ($db[0] & 0x10 ? "pressed" : "released");
- if ($msg =~ m/A0/) {push @event, "3:channelA:A0";}
- if ($msg =~ m/AI/) {push @event, "3:channelA:AI";}
- if ($msg =~ m/B0/) {push @event, "3:channelB:B0";}
- if ($msg =~ m/BI/) {push @event, "3:channelB:BI";}
- if ($msg =~ m/C0/) {push @event, "3:channelC:C0";}
- if ($msg =~ m/CI/) {push @event, "3:channelC:CI";}
- if ($msg =~ m/D0/) {push @event, "3:channelD:D0";}
- if ($msg =~ m/DI/) {push @event, "3:channelD:DI";}
- }
- } else {
- if ($db[0] & 0xC0) {
- # Only a Mechanical Handle is setting these bits when NU = 0
- $msg = "closed" if ($db[0] == 0xF0);
- $msg = "open" if ($db[0] == 0xE0);
- $msg = "tilted" if ($db[0] == 0xD0);
- $msg = "open_from_tilted" if ($db[0] == 0xC0);
- } elsif ($st eq "keycard") {
- $msg = "keycard_removed";
- } elsif ($st eq "liquidLeakage") {
- $msg = "dry";
- } else {
- $msg = (($db[0] & 0x10) ? "pressed" : "released");
- push @event, "3:buttons:" . ($db[0] & 0x10 ? "pressed" : "released");
- }
- }
- # released events are disturbing when using a remote, since it overwrites
- # the "real" state immediately. In the case of an Eltako FSB14, FSB61 ...
- # the state should remain released. (by Thomas)
- if ($msg =~ m/released$/ &&
- AttrVal($name, "sensorMode", "switch") ne "pushbutton" &&
- $model ne "FT55" && $model ne "FSB14" &&
- $model ne "FSB61" && $model ne "FSB70" &&
- $model ne "FSM12" && $model ne "FSM61" &&
- $model ne "FTS12") {
- $event = "buttons";
- $msg = "released";
- } else {
- push @event, "3:$event:$msg";
- }
- }
- } elsif ($rorg eq "D5") {
- # 1BS telegram
- if ($st eq "radioLinkTest") {
- # Radio Link Test (EEP A5-3F-00)
- @{$hash->{helper}{rlt}{param}} = ('parse', $hash, $data, $subDef, 'master', $RSSI);
- EnOcean_RLT($hash->{helper}{rlt}{param});
- } else {
- # Single Input Contact (EEP D5-00-01)
- # [Eltako FTK, STM-250]
- push @event, "3:state:" . ($db[0] & 1 ? "closed" : "open");
- if (!($db[0] & 8)) {
- # teach-in
- $attr{$name}{eep} = "D5-00-01";
- $attr{$name}{manufID} = "7FF";
- $attr{$name}{subType} = "contact";
- push @event, "3:teach:1BS teach-in accepted EEP D5-00-01 Manufacturer: no ID";
- Log3 $name, 2, "EnOcean $name teach-in EEP D5-00-01 Manufacturer: no ID";
- # store attr subType, manufID ...
- EnOcean_CommandSave(undef, undef);
- }
- }
- } elsif ($rorg eq "A5") {
- # 4BS telegram
- if (($db[0] & 8) == 0) {
- # Teach-In telegram
- if ($teach || AttrVal($hash->{IODev}{NAME}, "learningMode", "demand") eq "always" || $st eq "radioLinkTest") {
- if ($db[0] & 0x80) {
- # 4BS Teach-In telegram with EEP and manufacturer ID
- my $func = sprintf "%02X", ($db[3] >> 2);
- my $type = sprintf "%02X", ((($db[3] & 3) << 5) | ($db[2] >> 3));
- my $mid = sprintf "%03X", ((($db[2] & 7) << 8) | $db[1]);
- # manufID to account for vendor-specific features
- $attr{$name}{manufID} = $mid;
- $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
- my $st = "A5.$func.$type";
- $attr{$name}{eep} = "A5-$func-$type";
- if ($db[0] & 0x10) {
- # 4BS teach-in bidirectional response received
- Log3 $name, 5, "EnOcean $name 4BS teach-in response message from $senderID received";
- if ($teach && exists($hash->{IODev}{helper}{"4BSRespWait"}{$destinationID})) {
- if ($db[0] & 0x40) {
- # EEP supported
- if ($db[0] & 0x20) {
- # SenderID stored
- Log3 $name, 2, "EnOcean $name 4BS teach-in accepted by $senderID";
- push @event, "3:teach:4BS teach-in accepted EEP $rorg-$func-$type Manufacturer: $mid";
- $attr{$name}{comMode} = "biDir";
- $attr{$name}{destinationID} = "unicast";
- # substitute subDef with DEF
- $attr{$name}{subDef} = $hash->{DEF};
- $hash->{DEF} = $senderID;
- $modules{EnOcean}{defptr}{$senderID} = $hash;
- delete $modules{EnOcean}{defptr}{$destinationID};
- # clear teach-in request
- delete $hash->{IODev}{helper}{"4BSRespWait"}{$destinationID};
- # store attr subType, manufID ...
- EnOcean_CommandSave(undef, undef);
- } else {
- # SenderID not stored / deleted
- Log3 $name, 2, "EnOcean $name 4BS request not accepted by $senderID";
- push @event, "3:teach:4BS request not accepted EEP $rorg-$func-$type Manufacturer: $mid";
- }
- } else {
- # EEP not suppported
- Log3 $name, 2, "EnOcean $name 4BS EEP not supported by $senderID";
- push @event, "3:teach:4BS EEP not supported EEP $rorg-$func-$type Manufacturer: $mid";
- }
- } else {
- Log3 $name, 2, "EnOcean $name 4BS teach-in response message from $senderID ignored";
- }
- } else {
- # 4BS teach-in query
- $attr{$name}{teachMethod} = '4BS';
- if(exists $EnO_eepConfig{$st}{attr}) {
- push @event, "3:teach:4BS teach-in accepted EEP A5-$func-$type Manufacturer: $mid";
- Log3 $name, 2, "EnOcean $name 4BS teach-in accepted EEP A5-$func-$type Manufacturer: $mid";
- foreach my $attrCntr (keys %{$EnO_eepConfig{$st}{attr}}) {
- if ($attrCntr eq "subDef") {
- if (!defined AttrVal($name, $attrCntr, undef)) {
- $attr{$name}{$attrCntr} = EnOcean_CheckSenderID($EnO_eepConfig{$st}{attr}{$attrCntr}, $hash->{IODev}{NAME}, "00000000");
- }
- } else {
- $attr{$name}{$attrCntr} = $EnO_eepConfig{$st}{attr}{$attrCntr};
- }
- }
- if (exists($hash->{helper}{teachInWait}) && $hash->{helper}{teachInWait} =~ m/^4BS|STE$/) {
- $attr{$filelogName}{logtype} = $EnO_eepConfig{$st}{GPLOT} . 'text'
- if (exists $attr{$filelogName}{logtype});
- EnOcean_CreateSVG(undef, $hash, undef);
- delete $hash->{helper}{teachInWait};
- }
- $st = $EnO_eepConfig{$st}{attr}{subType};
- } else {
- push @event, "3:teach:4BS EEP not supported EEP A5-$func-$type Manufacturer: $mid";
- Log3 $name, 2, "EnOcean $name 4BS EEP not supported EEP A5-$func-$type Manufacturer: $mid";
- $attr{$name}{subType} = "raw";
- $st = "raw";
- }
- if ($teach || $st eq "radioLinkTest") {
- # bidirectional 4BS teach-in
- if ($st eq "hvac.01" || $st eq "MD15") {
- # EEP A5-20-01
- $attr{$name}{comMode} = "biDir";
- $attr{$name}{destinationID} = "unicast";
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
- # teach-in response
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, "800FFFF0", $subDef, "00", $hash->{DEF});
- Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
- readingsSingleUpdate($hash, 'operationMode', 'setpointTemp', 0);
- #####
- #EnOcean_hvac_01Cmd($hash, $packetType, 128); # 128 == 20 degree C
- } elsif ($st eq "hvac.02") {
- # EEP A5-20-02 not supported
- # teach-in response
- $data = sprintf "%06X90", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, "00000000", "00", $hash->{DEF});
- Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
- } elsif ($st eq "hvac.03") {
- # EEP A5-20-03 not supported
- # teach-in response
- $data = sprintf "%06X90", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, "00000000", "00", $hash->{DEF});
- Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
- } elsif ($st eq "hvac.04") {
- # heating radiator valve actuating drive (EEP A5-20-04)
- $attr{$name}{comMode} = "biDir";
- $attr{$name}{destinationID} = "unicast";
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
- # teach-in response
- $data = sprintf "%06XF0", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, "00", $hash->{DEF});
- Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
- readingsSingleUpdate($hash, 'operationMode', 'setpointTemp', 0);
- } elsif ($st eq "radioLinkTest") {
- # Radio Link Test (EEP A5-3F-00)
- if (ReadingsVal($name, "state", 'standby') eq 'standby') {
- $attr{$name}{comMode} = "biDir";
- $attr{$name}{destinationID} = "unicast";
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
- # teach-in response, SenderID not stored
- $data = sprintf "%06XD0", (hex($func) << 7 | hex($type)) << 11 | hex($attr{$name}{manufID});
- #$data = sprintf "%06XD0", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
- #$data = sprintf "%06XF0", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, "00", $hash->{DEF});
- Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
- @{$hash->{helper}{rlt}{param}} = ('start', $hash, undef, $subDef, 'master', $RSSI);
- EnOcean_RLT($hash->{helper}{rlt}{param});
- }
- #} elsif ($st =~ m/^hvac\.1[0-1]$/) {
- # EEP A5-20-10, A5-20-11
- # teach-in response
- # $data = sprintf "%06XF0", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
- # EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, "00000000", "00", $hash->{DEF});
- # Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
- } elsif (AttrVal($name, "comMode", "") =~ m/^confirm|biDir$/) {
- # confirm telegram requested, teach-in response sent
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
- $data = sprintf "%06XF0", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, "00", $hash->{DEF});
- Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
- #} else {
- # EEP not supported
- # teach-in response
- # $data = sprintf "%06X90", (hex($func) << 7 | hex($type)) << 11 | 0x7FF;
- # EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, "00000000", "00", $hash->{DEF});
- # Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
- }
- }
- }
- # store attr subType, manufID ...
- EnOcean_CommandSave(undef, undef);
- # delete standard readings
- CommandDeleteReading(undef, "$name sensor[0-9]");
- CommandDeleteReading(undef, "$name D[0-9]");
- } else {
- # 4BS Teach-In without EEP and manufacturer ID
- push @event, "3:teach:4BS teach-in accepted: No EEP profile identifier and no Manufacturer ID";
- Log3 $name, 2, "EnOcean $name 4BS teach-in accepted. No EEP profile identifier and no Manufacturer ID";
- $attr{$name}{subType} = "raw";
- $st = "raw";
- }
- } else {
- Log3 $name, 4, "EnOcean $name teach-in with subType $st locked, set transceiver in teach mode.";
- return "";
- }
- } elsif ($st eq "hvac.01" || $st eq "MD15") {
- # Battery Powered Actuator (EEP A5-20-01)
- # [Kieback&Peter MD15-FTL-xx]
- push @event, "3:energyInput:" . (($db[2] & 0x40) ? "enabled" : "disabled");
- my $battery = ($db[2] & 0x10) ? "ok" : "low";
- my $energyStorage;
- if ($db[2] & 0x20) {
- $energyStorage = 'charged';
- $battery = 'ok';
- } else {
- $energyStorage = 'empty';
- }
- push @event, "3:battery:$battery";
- push @event, "3:energyStorage:$energyStorage";
- my $roomTemp = ReadingsVal($name, "roomTemp", 20);
- if ($db[2] & 4) {
- CommandDeleteReading(undef, "$name roomTemp");
- } else {
- $roomTemp = $db[1] * 40 / 255;
- push @event, "3:roomTemp:" . sprintf "%0.1f", $roomTemp;
- }
- my $setpoint = $db[3];
- push @event, "3:setpoint:$setpoint";
- my $maintenanceMode = ReadingsVal($name, "maintenanceMode", ($db[2] & 0x80) ? 'on' : 'off');
- # if ($db[2] & 0x80) {
- # $maintenanceMode = 'on' if (!defined($maintenanceMode));
- # } else {
- # $maintenanceMode = 'off';
- # }
- push @event, "3:cover:" . (($db[2] & 8) ? "open" : "closed");
- push @event, "3:window:" . (($db[2] & 2) ? "open" : "closed");
- push @event, "3:actuatorState:". (($db[2] & 1) ? "obstructed" : "ok");
- push @event, "3:selfCtrl:" . (($db[0] & 4) ? "on" : "off");
- my $functionSelect = 0;
- my $setpointSelect = 0;
- my $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
- my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
- my $setpointTempSet = ReadingsVal($name, "setpointTempSet", $setpointTemp);
- my $temperature = ReadingsVal($name, 'temperature', $roomTemp);
- if (!defined(AttrVal($name, "temperatureRefDev", undef))) {
- if ($db[2] & 4) {
- CommandDeleteReading(undef, "$name temperature");
- } else {
- $temperature = $roomTemp;
- readingsSingleUpdate($hash, 'temperature', sprintf("%0.1f", $temperature), 1);
- }
- }
- Log3 $name, 5, "EnOcean $name EnOcean_parse SPT: $setpointTemp SPTS: $setpointTempSet";
- my $operationMode = ReadingsVal($name, "operationMode", 'setpointTemp');
- my $setpointSummerMode = AttrVal($name, "setpointSummerMode", 0);
- my $summerMode = AttrVal($name, "summerMode", "off");
- my $timeDiff = EnOcean_TimeDiff(ReadingsTimestamp($name, 'wakeUpCycle', undef));
- my $waitingCmds = ReadingsVal($name, "waitingCmds", "no_change");
- my $wakeUpCycle = 600;
- # calc wakeup cycle
- if ($summerMode eq 'off') {
- $summerMode = 0;
- if ($timeDiff == 0) {
- $wakeUpCycle = 1200;
- } elsif ($timeDiff < 120) {
- $wakeUpCycle = 120;
- } elsif ($timeDiff > 1200) {
- $wakeUpCycle = 1200;
- } else {
- $wakeUpCycle = int($timeDiff);
- }
- } else {
- $summerMode = 8;
- # ignore all commands
- if ($waitingCmds ne "summerMode") {
- $waitingCmds = "no_change";
- CommandDeleteReading(undef, "$name waitingCmds");
- }
- if ($manufID eq '049') {
- $wakeUpCycle = 28800;
- } else {
- $wakeUpCycle = 3600;
- }
- }
- readingsSingleUpdate($hash, 'wakeUpCycle', $wakeUpCycle, 1);
- # set alarm timer
- CommandDeleteReading(undef, "$name alarm");
- @{$hash->{helper}{alarmTimer}} = ($hash, 'alarm', 'no_response_from_actuator', 1, 3);
- RemoveInternalTimer($hash->{helper}{alarmTimer});
- InternalTimer(gettimeofday() + $wakeUpCycle * 1.1, "EnOcean_readingsSingleUpdate", $hash->{helper}{alarmTimer}, 0);
- my $actionCmd = AttrVal($name, "rcvRespAction", undef);
- if (defined $actionCmd) {
- my %specials = ("%ACTUATORSTATE" => (($db[2] & 1) ? "obstructed" : "ok"),
- "%BATTERY" => $battery,
- "%COVER" => (($db[2] & 8) ? "open" : "closed"),
- "%ENERGYINPUT" => (($db[2] & 0x40) ? "enabled" : "disabled"),
- "%ENERGYSTORAGE" => $energyStorage,
- "%MAINTENANCEMODE" => $maintenanceMode,
- "%NAME" => $name,
- "%OPERATIONMODE" => $operationMode,
- "%ROOMTEMP" => $roomTemp,
- "%SELFCTRL" => (($db[0] & 4) ? "on" : "off"),
- "%SETPOINT" => $setpoint,
- "%SETPOINTTEMP" => $setpointTemp,
- "%SUMMERMODE" => $summerMode,
- "%TEMPERATURE" => $temperature,
- "%WINDOW" => (($db[2] & 2) ? "open" : "closed"),
- );
- # action exec
- $actionCmd = EvalSpecials($actionCmd, %specials);
- my $ret = AnalyzeCommandChain(undef, $actionCmd);
- Log3 $name, 2, "Encean $name rcvRespAction ERROR: $ret" if($ret);
- $maintenanceMode = ReadingsVal($name, "maintenanceMode", ($db[2] & 0x80) ? 'on' : 'off');
- $operationMode = ReadingsVal($name, "operationMode", 'setpointTemp');
- $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
- $setpointTempSet = ReadingsVal($name, "setpointTempSet", $setpointTemp);
- $temperature = ReadingsVal($name, 'temperature', $roomTemp);
- $waitingCmds = ReadingsVal($name, "waitingCmds", "no_change");
- }
- if ($waitingCmds eq "valveOpens") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 100;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:valveOpend:runInit";
- push @event, "3:operationMode:off";
- CommandDeleteReading(undef, "$name setpointSet");
- CommandDeleteReading(undef, "$name setpointTemp");
- CommandDeleteReading(undef, "$name setpointTempSet");
- CommandDeleteReading(undef, "$name waitingCmds");
- $functionSelect = 1;
- $waitingCmds = 0x20;
- } elsif ($waitingCmds eq "valveCloses") {
- if ($maintenanceMode eq "valveOpend:runInit") {
- $setpointSet = 100;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:runInit";
- push @event, "3:operationMode:off";
- $functionSelect = 1;
- $waitingCmds = 0x80;
- } else {
- $setpointSet = 0;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:valveClosed";
- push @event, "3:operationMode:off";
- CommandDeleteReading(undef, "$name waitingCmds");
- $functionSelect = 1;
- $waitingCmds = 0x10;
- }
- # stop PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- CommandDeleteReading(undef, "$name setpointSet");
- CommandDeleteReading(undef, "$name setpointTemp");
- CommandDeleteReading(undef, "$name setpointTempSet");
- } elsif ($waitingCmds eq "runInit") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 0;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:runInit";
- push @event, "3:operationMode:off";
- push @event, "3:waitingCmds:$operationMode";
- #CommandDeleteReading(undef, "$name setpointSet");
- #CommandDeleteReading(undef, "$name setpointTemp");
- #CommandDeleteReading(undef, "$name setpointTempSet");
- #CommandDeleteReading(undef, "$name waitingCmds");
- $functionSelect = 1;
- $waitingCmds = 0x80;
- } elsif ($waitingCmds eq "liftSet") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 0;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:listSet";
- push @event, "3:operationMode:off";
- push @event, "3:waitingCmds:$operationMode";
- #CommandDeleteReading(undef, "$name setpointSet");
- #CommandDeleteReading(undef, "$name setpointTemp");
- #CommandDeleteReading(undef, "$name setpointTempSet");
- #CommandDeleteReading(undef, "$name waitingCmds");
- $functionSelect = 1;
- $waitingCmds = 0x40;
- } elsif ($waitingCmds eq "setpoint") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- if ($maintenanceMode eq "valveOpend:runInit") {
- $setpointSet = 100;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:runInit";
- push @event, "3:operationMode:off";
- $functionSelect = 1;
- $waitingCmds = 0x80;
- } else {
- $db[2] = (40 - $temperature) * 255 / 40;
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpoint";
- CommandDeleteReading(undef, "$name setpointTemp");
- CommandDeleteReading(undef, "$name setpointTempSet");
- CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 0;
- }
- } elsif ($waitingCmds eq "setpointTemp") {
- if ($maintenanceMode eq "valveOpend:runInit") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 100;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:runInit";
- push @event, "3:operationMode:off";
- $functionSelect = 1;
- $waitingCmds = 0x80;
- } else {
- if (AttrVal($name, "pidCtrl", 'on') eq 'on') {
- # activate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'actuator', '');
- $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
- } else {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- # setpoint temperature
- $setpointSet = $setpointTempSet * 255 / 40;
- $setpointSelect = 4;
- }
- $setpointTemp = $setpointTempSet;
- $db[2] = (40 - $temperature) * 255 / 40;
- push @event, "3:setpointTemp:" . sprintf("%0.1f", $setpointTemp);
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpointTemp";
- CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 0;
- }
- } elsif ($waitingCmds eq "summerMode") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = $setpointSummerMode;
- $db[2] = (40 - $temperature) * 255 / 40;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:summerMode";
- #CommandDeleteReading(undef, "$name setpointSet");
- CommandDeleteReading(undef, "$name setpointTemp");
- CommandDeleteReading(undef, "$name setpointTempSet");
- CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 0;
- } elsif ($operationMode eq "setpoint") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- if ($maintenanceMode eq "valveOpend:runInit") {
- $setpointSet = 100;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpoint";
- $functionSelect = 1;
- $waitingCmds = 0x80;
- } else {
- $db[2] = (40 - $temperature) * 255 / 40;
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpoint";
- $waitingCmds = 0;
- }
- } elsif ($operationMode eq "setpointTemp") {
- if ($maintenanceMode eq "valveOpend:runInit") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 100;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpointTemp";
- $functionSelect = 1;
- $waitingCmds = 0x80;
- } else {
- if (AttrVal($name, "pidCtrl", 'on') eq 'on') {
- # activate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'actuator', '');
- $setpointSet = ReadingsVal($name, "setpointSet", $setpointSet);
- } else {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- # setpoint temperature
- $setpointSet = $setpointTempSet * 255 / 40;
- $setpointSelect = 4;
- }
- $db[2] = (40 - $temperature) * 255 / 40;
- $setpointTemp = $setpointTempSet;
- push @event, "3:setpointTemp:" . sprintf("%.1f", $setpointTemp);
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpointTemp";
- $waitingCmds = 0;
- }
- } elsif ($operationMode eq "summerMode") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = $setpointSummerMode;
- $db[2] = (40 - $temperature) * 255 / 40;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:summerMode";
- $waitingCmds = 0;
- } elsif ($maintenanceMode eq "valveOpend:runInit") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 100;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:valveOpend:runInit";
- push @event, "3:operationMode:off";
- #CommandDeleteReading(undef, "$name setpointSet");
- #CommandDeleteReading(undef, "$name setpointTemp");
- #CommandDeleteReading(undef, "$name setpointTempSet");
- #CommandDeleteReading(undef, "$name waitingCmds");
- $functionSelect = 1;
- $waitingCmds = 0x20;
- } elsif ($maintenanceMode eq "valveClosed") {
- # stop PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 0;
- $db[2] = 0x20;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:valveClosed";
- push @event, "3:operationMode:off";
- #CommandDeleteReading(undef, "$name setpointSet");
- #CommandDeleteReading(undef, "$name setpointTemp");
- #CommandDeleteReading(undef, "$name setpointTempSet");
- #CommandDeleteReading(undef, "$name waitingCmds");
- $functionSelect = 1;
- $waitingCmds = 0x10;
- } else {
- $db[2] = (40 - $temperature) * 255 / 40;
- $waitingCmds = 0;
- }
- push @event, "3:state:T: " . sprintf("%0.1f", $temperature) . " SPT: " . sprintf("%.1f", $setpointTemp) . " SP: $setpoint";
- # sent message to the actuator
- $data = sprintf "%02X%02X%02X08", $setpointSet, $db[2], $waitingCmds | $summerMode | $setpointSelect | $functionSelect;
- EnOcean_SndRadio(undef, $hash, $packetType, "A5", $data, $subDef, "00", $hash->{DEF});
- } elsif ($st eq "hvac.04") {
- # heating radiator valve actuating drive EEP A5-20-04)
- my %failureCode = (
- 17 => "measurement_error",
- 18 => "battery_empty",
- 20 => "frost_protection",
- 33 => "blocked_valve",
- 36 => "end_point_detection_error",
- 40 => "no_valve",
- 49 => "not_taught_in",
- 53 => "no_response_from_controller",
- 54 => "teach-in_error"
- );
- my $battery = "ok";
- my %displayOrientation = (0 => 0, 90 => 1, 180 => 2, 270 => 3);
- my $feedTemp = ReadingsVal($name, "feedTemp", 20);
- my $roomTemp = ReadingsVal($name, "roomTemp", 20);
- my $setpoint = $db[3];
- push @event, "3:setpoint:$setpoint";
- my $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
- my $setpointTemp = ReadingsVal($name, "setpointTemp", 20);
- my $setpointTempSet = ReadingsVal($name, "setpointTempSet", $setpointTemp);
- my $temperature = ReadingsVal($name, "temperature", $roomTemp);
- if ($db[0] & 2) {
- if ($setpointTemp == $setpointTempSet) {
- $setpointTemp = sprintf "%0.1f", ($db[2] * 20 / 255 + 10);
- if ($setpointTemp != $setpointTempSet) {
- # setpointTempSet has been changed by actuator
- $setpointTempSet = $setpointTemp;
- readingsSingleUpdate($hash, 'setpointTempSet', $setpointTempSet, 1);
- }
- } else {
- # setpointTempSet has been changed by Fhem
- $setpointTemp = sprintf "%0.1f", ($db[2] * 20 / 255 + 10);
- }
- push @event, "3:setpointTemp:$setpointTemp";
- } else {
- if ($db[0] & 0x80) {
- # temperature measurement inactive
- CommandDeleteReading(undef, "$name feedTemp");
- } else {
- $feedTemp = $db[2] * 60 / 255 + 20;
- push @event, "3:feedTemp:" . sprintf("%0.1f", $feedTemp);
- }
- }
- if ($db[0] & 1) {
- # failure code
- if (exists $failureCode{$db[1]}) {
- push @event, "3:alarm:" . $failureCode{$db[1]};
- $battery = "empty" if ($db[1] == 18);
- } else {
- CommandDeleteReading(undef, "$name alarm");
- }
- } else {
- if ($db[0] & 0x80) {
- # temperature measurement inactive
- CommandDeleteReading(undef, "$name roomTemp");
- } else {
- # room temperature
- $roomTemp = sprintf("%0.1f", ($db[1] * 20 / 255 + 10));
- push @event, "3:roomTemp:$roomTemp";
- CommandDeleteReading(undef, "$name alarm");
- }
- }
- if (!defined(AttrVal($name, "temperatureRefDev", undef))) {
- if ($db[0] & 0x80) {
- # temperature measurement needed, activate temperature measurement
- $attr{$name}{measurementCtrl} = 'enable';
- EnOcean_CommandSave(undef, undef);
- } else {
- $temperature = $roomTemp;
- readingsSingleUpdate($hash, 'temperature', $temperature, 1);
- #push @event, "3:temperature:$temperature";
- }
- }
- push @event, "3:measurementState:" . ($db[0] & 0x80 ? "inactive" : "active");
- push @event, "3:blockKey:" . ($db[0] & 4 ? "yes" : "no");
- push @event, "3:battery:$battery";
- #push @event, "3:state:T: $temperature SPT: $setpointTemp SP: $setpoint";
- if ($db[0] & 0x40) {
- # status request
- # action needed?
- }
- Log3 $name, 5, "EnOcean $name EnOcean_parse SPT: $setpointTemp SPTS: $setpointTempSet";
- my $activatePID = AttrVal($name, 'pidCtrl', 'on') eq 'on' ? 'actuator' : 'stop';
- my $blockKey = ((AttrVal($name, "blockKey", 'no') eq 'yes') ? 1 : 0) << 2;
- my $displayOrientation = $displayOrientation{AttrVal($name, "displayOrientation", 0)} << 4;
- my $maintenanceMode = ReadingsVal($name, "maintenanceMode", "off");
- my $measurementCtrl = (AttrVal($name, 'measurementCtrl', 'enable') eq 'enable') ? 0 : 0x40;
- #my $operationMode = ReadingsVal($name, "operationMode", "off");
- my $operationMode = ReadingsVal($name, "operationMode", ((AttrVal($name, 'pidCtrl', 'on') eq 'on') ? 'setpointTemp' : 'setpoint'));
- my $summerMode = AttrVal($name, "summerMode", "off");
- my $waitingCmds = ReadingsVal($name, "waitingCmds", "no_change");
- my $wakeUpCycle = $wakeUpCycle{AttrVal($name, "wakeUpCycle", 300)};
- if ($summerMode eq 'off' && $wakeUpCycle >= 50) {
- # set default Wake-up Cycle (300 s)
- $wakeUpCycle = 9;
- } elsif ($summerMode eq 'on') {
- if ($waitingCmds ne "summerMode") {
- $waitingCmds = "no_change";
- CommandDeleteReading(undef, "$name waitingCmds");
- }
- $setpointSet = 100;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- $wakeUpCycle = 50 if ($wakeUpCycle < 50);
- }
- my $actionCmd = AttrVal($name, "rcvRespAction", undef);
- if (defined $actionCmd) {
- my %specials = ("%BATTERY" => $battery,
- "%FEEDTEMP" => $feedTemp,
- "%MAINTENANCEMODE" => $maintenanceMode,
- "%NAME" => $name,
- "%OPERATIONMODE" => $operationMode,
- "%ROOMTEMP" => $roomTemp,
- "%SETPOINT" => $setpoint,
- "%SETPOINTTEMP" => $setpointTemp,
- "%SUMMERMODE" => $summerMode,
- "%TEMPERATURE" => $temperature,
- );
- # action exec
- $actionCmd = EvalSpecials($actionCmd, %specials);
- my $ret = AnalyzeCommandChain(undef, $actionCmd);
- Log3 $name, 2, "EnOcean $name rcvRespAction ERROR: $ret" if($ret);
- $maintenanceMode = ReadingsVal($name, "maintenanceMode", 'off');
- $operationMode = ReadingsVal($name, "operationMode", ((AttrVal($name, 'pidCtrl', 'on') eq 'on') ? 'setpointTemp' : 'setpoint'));
- $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
- $setpointTempSet = ReadingsVal($name, "setpointTempSet", $setpointTemp);
- $temperature = ReadingsVal($name, 'temperature', $roomTemp);
- $waitingCmds = ReadingsVal($name, "waitingCmds", "no_change");
- }
- if ($waitingCmds eq "valveOpens") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 100;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:valveOpend:runInit";
- push @event, "3:operationMode:off";
- CommandDeleteReading(undef, "$name setpointSet");
- CommandDeleteReading(undef, "$name setpointTemp");
- CommandDeleteReading(undef, "$name setpointTempSet");
- CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 1;
- } elsif ($waitingCmds eq "valveCloses") {
- if ($maintenanceMode eq "valveOpend:runInit") {
- $setpointSet = 100;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:runInit";
- push @event, "3:operationMode:off";
- $waitingCmds = 2;
- } else {
- $setpointSet = 0;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:valveClosed";
- push @event, "3:operationMode:off";
- CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 3;
- }
- # stop PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- CommandDeleteReading(undef, "$name setpointSet");
- CommandDeleteReading(undef, "$name setpointTemp");
- CommandDeleteReading(undef, "$name setpointTempSet");
- } elsif ($waitingCmds eq "runInit") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 100;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:runInit";
- push @event, "3:operationMode:off";
- CommandDeleteReading(undef, "$name setpointSet");
- CommandDeleteReading(undef, "$name setpointTemp");
- CommandDeleteReading(undef, "$name setpointTempSet");
- CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 2;
- } elsif ($waitingCmds eq "setpoint") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- if ($maintenanceMode eq "valveOpend:runInit") {
- $setpointSet = 100;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:runInit";
- push @event, "3:operationMode:off";
- $waitingCmds = 2;
- } else {
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpoint";
- #CommandDeleteReading(undef, "$name setpointSet");
- CommandDeleteReading(undef, "$name setpointTemp");
- CommandDeleteReading(undef, "$name setpointTempSet");
- CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 0;
- }
- } elsif ($waitingCmds eq "setpointTemp") {
- if ($maintenanceMode eq "valveOpend:runInit") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 100;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:runInit";
- push @event, "3:operationMode:off";
- $waitingCmds = 2;
- } else {
- # activate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, $activatePID, '');
- $setpointSet = ReadingsVal($name, "setpointSet", $setpoint);
- $setpointTemp = $setpointTempSet;
- push @event, "3:setpointTemp:$setpointTemp";
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpointTemp";
- #CommandDeleteReading(undef, "$name setpointSet");
- #CommandDeleteReading(undef, "$name setpointTempSet");
- CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 0;
- }
- } elsif ($waitingCmds eq "summerMode") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 100;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:summerMode";
- #CommandDeleteReading(undef, "$name setpointSet");
- CommandDeleteReading(undef, "$name setpointTemp");
- CommandDeleteReading(undef, "$name setpointTempSet");
- CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 0;
- } elsif ($operationMode eq "setpoint") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- if ($maintenanceMode eq "valveOpend:runInit") {
- $setpointSet = 100;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpoint";
- $waitingCmds = 2;
- } else {
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpoint";
- #CommandDeleteReading(undef, "$name setpointSet");
- #CommandDeleteReading(undef, "$name setpointTemp");
- #CommandDeleteReading(undef, "$name setpointTempSet");
- #CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 0;
- }
- } elsif ($operationMode eq "setpointTemp") {
- if ($maintenanceMode eq "valveOpend:runInit") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 100;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpointTemp";
- $waitingCmds = 2;
- } else {
- # activate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, $activatePID, '');
- $setpointSet = ReadingsVal($name, "setpointSet", $setpointSet);
- push @event, "3:setpointTemp:$setpointTemp";
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:setpointTemp";
- #CommandDeleteReading(undef, "$name setpointSet");
- #CommandDeleteReading(undef, "$name setpointTempSet");
- #CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 0;
- }
- } elsif ($operationMode eq "summerMode") {
- # deactivate PID regulator
- ($err, $logLevel, $response) = EnOcean_setPID(undef, $hash, 'stop', '');
- $setpointSet = 100;
- readingsSingleUpdate($hash, 'setpointSet', $setpointSet, 1);
- push @event, "3:maintenanceMode:off";
- push @event, "3:operationMode:summerMode";
- #CommandDeleteReading(undef, "$name setpointSet");
- #CommandDeleteReading(undef, "$name setpointTemp");
- #CommandDeleteReading(undef, "$name setpointTempSet");
- #CommandDeleteReading(undef, "$name waitingCmds");
- $waitingCmds = 0;
- } else {
- $waitingCmds = 0;
- }
- push @event, "3:state:T: $temperature SPT: $setpointTemp SP: $setpoint";
- # sent message to the actuator
- $data = sprintf "%02X%02X%02X%02X", $setpointSet,
- ($setpointTempSet - 10) / 20 * 255,
- (AttrVal($name, 'pidCtrl', 'on') eq 'on' ? 0 : 0x80) | $measurementCtrl | $wakeUpCycle,
- $displayOrientation | 8 | $blockKey | $waitingCmds;
- EnOcean_SndRadio(undef, $hash, $packetType, "A5", $data, $subDef, "00", $hash->{DEF});
- } elsif ($st eq "hvac.10") {
- # Generic HVAC Interface (EEP A5-20-10)
- my %mode = (
- 0 => "auto",
- 1 => "heat",
- 2 => "morning_warmup",
- 3 => "cool",
- 4 => "night_purge",
- 5 => "precool",
- 6 => "off",
- 7 => "test",
- 8 => "emergency_heat",
- 9 => "fan_only",
- 10 => "free_cool",
- 11 => "ice",
- 12 => "max_heat",
- 13 => "eco",
- 14 => "dehumidification",
- 15 => "calibration",
- 16 => "emergency_cool",
- 17 => "emergency_stream",
- 18 => "max_cool",
- 19 => "hvc_load",
- 20 => "no_load",
- 31 => "auto_heat",
- 32 => "auto_cool",
- );
- if (exists $mode{$db[3]}) {
- push @event, "3:mode:$mode{$db[3]}";
- } else {
- push @event, "3:mode:unknown";
- }
- my %vanePosition = (
- 0 => "auto",
- 1 => "horizontal",
- 2 => "position_2",
- 3 => "position_3",
- 4 => "position_4",
- 5 => "vertical",
- 6 => "swing",
- 11 => "vertical_swing",
- 12 => "horizontal_swing",
- 13 => "hor_vert_swing",
- 14 => "stop_swing",
- );
- my $vanePosition = $db[2] >> 4;
- if (exists $vanePosition{$vanePosition}) {
- push @event, "3:vanePosition:$vanePosition{$vanePosition}";
- } else {
- push @event, "3:vanePosition:unknown";
- }
- my $fanSpeed = $db[2] & 0x0F;
- if ($fanSpeed == 0) {
- push @event, "3:fanSpeed:auto";
- } elsif ($fanSpeed > 0 && $fanSpeed < 15) {
- push @event, "3:fanSpeed:" . $fanSpeed;
- } else {
- push @event, "3:fanSpeed:unknown";
- }
- if ($db[1] == 255) {
- push @event, "3:ctrl:auto";
- } elsif ($db[1] >= 0 && $db[1] <= 100) {
- push @event, "3:ctrl:" . $db[1];
- } else {
- push @event, "3:ctrl:unknown";
- }
- my $occupancy = ($db[0] & 6) >> 1;
- if ($occupancy == 0) {
- push @event, "3:occupancy:occupied";
- } elsif ($occupancy == 1) {
- push @event, "3:occupancy:standby";
- } elsif ($occupancy == 2) {
- push @event, "3:occupancy:unoccupied";
- } else {
- push @event, "3:occupancy:off";
- }
- push @event, "3:powerSwitch:" . ($db[0] & 1 ? "on" : "off");
- push @event, "3:state:" . ($db[0] & 1 ? "on" : "off");
- } elsif ($st eq "hvac.11") {
- # Generic HVAC Interface - Error Control (EEP A5-20-11)
- push @event, "3:errorCode:" . hex(substr($data, 0, 4));
- push @event, "3:otherDisable:" . ($db[1] & 8 ? "disabled" : "enabled");
- push @event, "3:windowDisable:" . ($db[1] & 4 ? "disabled" : "enabled");
- push @event, "3:keyCardDisable:" . ($db[1] & 2 ? "disabled" : "enabled");
- push @event, "3:externalDisable:" . ($db[1] & 1 ? "disabled" : "enabled");
- push @event, "3:remoteCtrl:" . ($db[0] & 4 ? "disabled" : "enabled");
- push @event, "3:window:" . ($db[0] & 2 ? "closed" : "opened");
- push @event, "3:alarm:" . ($db[0] & 1 ? "error" : "ok");
- push @event, "3:state:" . ($db[0] & 1 ? "error" : "ok");
- } elsif ($st =~ m/^tempSensor/) {
- # Temperature Sensor with with different ranges (EEP A5-02-01 ... A5-02-1B)
- # $db[1] is the temperature where 0x00 = max °C ... 0xFF = min °C
- my $temp;
- $temp = sprintf "%0.1f", 0 - $db[1] / 6.375 if ($st eq "tempSensor.01");
- $temp = sprintf "%0.1f", 10 - $db[1] / 6.375 if ($st eq "tempSensor.02");
- $temp = sprintf "%0.1f", 20 - $db[1] / 6.375 if ($st eq "tempSensor.03");
- $temp = sprintf "%0.1f", 30 - $db[1] / 6.375 if ($st eq "tempSensor.04");
- $temp = sprintf "%0.1f", 40 - $db[1] / 6.375 if ($st eq "tempSensor.05");
- $temp = sprintf "%0.1f", 50 - $db[1] / 6.375 if ($st eq "tempSensor.06");
- $temp = sprintf "%0.1f", 60 - $db[1] / 6.375 if ($st eq "tempSensor.07");
- $temp = sprintf "%0.1f", 70 - $db[1] / 6.375 if ($st eq "tempSensor.08");
- $temp = sprintf "%0.1f", 80 - $db[1] / 6.375 if ($st eq "tempSensor.09");
- $temp = sprintf "%0.1f", 90 - $db[1] / 6.375 if ($st eq "tempSensor.0A");
- $temp = sprintf "%0.1f", 100 - $db[1] / 6.375 if ($st eq "tempSensor.0B");
- $temp = sprintf "%0.1f", 20 - $db[1] / 3.1875 if ($st eq "tempSensor.10");
- $temp = sprintf "%0.1f", 30 - $db[1] / 3.1875 if ($st eq "tempSensor.11");
- $temp = sprintf "%0.1f", 40 - $db[1] / 3.1875 if ($st eq "tempSensor.12");
- $temp = sprintf "%0.1f", 50 - $db[1] / 3.1875 if ($st eq "tempSensor.13");
- $temp = sprintf "%0.1f", 60 - $db[1] / 3.1875 if ($st eq "tempSensor.14");
- $temp = sprintf "%0.1f", 70 - $db[1] / 3.1875 if ($st eq "tempSensor.15");
- $temp = sprintf "%0.1f", 80 - $db[1] / 3.1875 if ($st eq "tempSensor.16");
- $temp = sprintf "%0.1f", 90 - $db[1] / 3.1875 if ($st eq "tempSensor.17");
- $temp = sprintf "%0.1f", 100 - $db[1] / 3.1875 if ($st eq "tempSensor.18");
- $temp = sprintf "%0.1f", 110 - $db[1] / 3.1875 if ($st eq "tempSensor.19");
- $temp = sprintf "%0.1f", 120 - $db[1] / 3.1875 if ($st eq "tempSensor.1A");
- $temp = sprintf "%0.1f", 130 - $db[1] / 3.1875 if ($st eq "tempSensor.1B");
- $temp = sprintf "%0.2f", 41.2 - (($db[2] << 8) | $db[1]) / 20 if ($st eq "tempSensor.20");
- $temp = sprintf "%0.1f", 62.3 - (($db[2] << 8) | $db[1]) / 10 if ($st eq "tempSensor.30");
- push @event, "3:temperature:$temp";
- push @event, "3:state:$temp";
- } elsif ($st eq "COSensor.01") {
- # Gas Sensor, CO Sensor (EEP A5-09-01)
- # [untested]
- # $db[3] is the CO concentration where 0x00 = 0 ppm ... 0xFF = 255 ppm
- # $db[1] is the temperature where 0x00 = 0 °C ... 0xFF = 255 °C
- # $db[0] bit D1 temperature sensor available 0 = no, 1 = yes
- my $coChannel1 = $db[3];
- push @event, "3:CO:$coChannel1";
- if ($db[0] & 2) {
- my $temp = $db[1];
- push @event, "3:temperature:$temp";
- }
- push @event, "3:state:$coChannel1";
- } elsif ($st eq "COSensor.02") {
- # Gas Sensor, CO Sensor (EEP A5-09-02)
- # [untested]
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
- # $db[2] is the CO concentration where 0x00 = 0 ppm ... 0xFF = 1020 ppm
- # $db[1] is the temperature where 0x00 = 0 °C ... 0xFF = 51 °C
- # $db[0]_bit_1 temperature sensor available 0 = no, 1 = yes
- my $coChannel1 = $db[2] << 2;
- my $voltage = sprintf "%0.1f", $db[3] * 0.02;
- push @event, "3:CO:$coChannel1";
- if ($db[0] & 2) {
- my $temp = sprintf "%0.1f", $db[1] * 0.2;
- push @event, "3:temperature:$temp";
- }
- push @event, "3:voltage:$voltage";
- push @event, "3:state:$coChannel1";
- } elsif ($st eq "tempHumiCO2Sensor.01") {
- # Gas Sensor, CO2 Sensor (EEP A5-09-04)
- # [Thermokon SR04 CO2 *, Eltako FCOTF63, untested]
- # $db[3] is the humidity where 0x00 = 0 %rH ... 0xC8 = 100 %rH
- # $db[2] is the CO2 concentration where 0x00 = 0 ppm ... 0xFF = 2500 ppm
- # $db[1] is the temperature where 0x00 = 0°C ... 0xFF = +51 °C
- # $db[0] bit D2 humidity sensor available 0 = no, 1 = yes
- # $db[0] bit D1 temperature sensor available 0 = no, 1 = yes
- my $humi = "-";
- my $temp = "-";
- my $airQuality;
- if ($db[0] & 4) {
- $humi = $db[3] >> 1;
- push @event, "3:humidity:$humi";
- }
- my $co2 = sprintf "%d", $db[2] * 10;
- push @event, "3:CO2:$co2";
- if ($db[0] & 2) {
- $temp = sprintf "%0.1f", $db[1] * 51 / 255 ;
- push @event, "3:temperature:$temp";
- }
- if ($co2 <= 400) {
- $airQuality = "high";
- } elsif ($co2 <= 600) {
- $airQuality = "mean";
- } elsif ($co2 <= 1000) {
- $airQuality = "moderate";
- } else {
- $airQuality = "low";
- }
- push @event, "3:airQuality:$airQuality";
- push @event, "3:state:T: $temp H: $humi CO2: $co2 AQ: $airQuality";
- } elsif ($st eq "radonSensor.01") {
- # Gas Sensor, Radon Sensor (EEP A5-09-06)
- # [untested]
- # $db[3]_bit_7 ... $db[2]_bit_6 is the radon activity where 0 = 0 Bq/m3 ... 1023 = 1023 Bq/m3
- my $rn = $db[3] << 2 | $db[2] >> 6;
- push @event, "3:Rn:$rn";
- push @event, "3:state:$rn";
- } elsif ($st eq "vocSensor.01") {
- # Gas Sensor, VOC Sensor (EEP A5-09-05, A5-09-0C)
- # [untested]
- # $db[3]_bit_7 ... $db[2]_bit_0 is the VOC concentration where 0 = 0 ppb ... 65535 = 65535 ppb
- # $db[1] is the VOC identification
- # $db[0]_bit_1 ... $db[0]_bit_0 is the scale multiplier
- my $vocSCM = $db[0] & 3;
- if ($vocSCM == 3) {
- $vocSCM = 10;
- } elsif ($vocSCM == 2) {
- $vocSCM = 1;
- } elsif ($vocSCM == 1) {
- $vocSCM = 0.1;
- } else {
- $vocSCM = 0.01;
- }
- my $vocConc = sprintf "%f", ($db[3] << 8 | $db[2]) * $vocSCM;
- my %vocID = (
- 0 => "VOCT",
- 1 => "Formaldehyde",
- 2 => "Benzene",
- 3 => "Styrene",
- 4 => "Toluene",
- 5 => "Tetrachloroethylene",
- 6 => "Xylene",
- 7 => "n-Hexane",
- 8 => "n-Octane",
- 9 => "Cyclopentane",
- 10 => "Methanol",
- 11 => "Ethanol",
- 12 => "1-Pentanol",
- 13 => "Acetone",
- 14 => "Ethylene Oxide",
- 15 => "Acetaldehyde ue",
- 16 => "Acetic Acid",
- 17 => "Propionice Acid",
- 18 => "Valeric Acid",
- 19 => "Butyric Acid",
- 20 => "Ammoniac",
- 22 => "Hydrogen Sulfide",
- 23 => "Dimethylsulfide",
- 24 => "2-Butanol",
- 25 => "2-Methylpropanol",
- 26 => "Diethyl Ether",
- 27 => "Naphthalene",
- 28 => "4-Phenylcyclohexene",
- 29 => "Limonene",
- 30 => "Tricloroethylene",
- 31 => "Isovaleric Acid",
- 32 => "Indole",
- 33 => "Cadaverine",
- 34 => "Putrescine",
- 35 => "Caproic Acid",
- 255 => "Ozone",
- );
- if (exists $vocID{$db[1]}) {
- push @event, "3:vocName:$vocID{$db[1]}";
- } else {
- push @event, "3:vocName:unknown";
- }
- push @event, "3:concentration:$vocConc";
- push @event, "3:concentrationUnit:" . $db[0] & 4 ? 'ug/m3' : 'ppb';
- push @event, "3:state:$vocConc";
- } elsif ($st eq "particlesSensor.01") {
- # Gas Sensor, Particles Sensor (EEP A5-09-07)
- # [untested]
- # $db[3]_bit_7 ... $db[2]_bit_7 is the particle concentration < 10 µm
- # where 0 = 0 µg/m3 ... 511 = 511 µg/m3
- # $db[2]_bit_6 ... $db[1]_bit_6 is the particle concentration < 2.5 µm
- # where 0 = 0 µg/m3 ... 511 = 511 µg/m3
- # $db[1]_bit_5 ... $db[0]_bit_5 is the particle concentration < 1 µm
- # where 0 = 0 µg/m3 ... 511 = 511 µg/m3
- # $db[0]_bit_2 = 1 = Sensor PM10 active
- # $db[0]_bit_1 = 1 = Sensor PM2_5 active
- # $db[0]_bit_0 = 1 = Sensor PM1 active
- my $pm_10 = "inactive";
- my $pm_2_5 = "inactive";
- my $pm_1 = "inactive";
- if ($db[0] & 4) {$pm_10 = $db[3] << 1 | $db[2] >> 7;}
- if ($db[0] & 2) {$pm_2_5 = ($db[2] & 0x7F) << 1 | $db[1] >> 7;}
- if ($db[0] & 1) {$pm_1 = ($db[1] & 0x3F) << 3 | $db[0] >> 5;}
- push @event, "3:particles_10:$pm_10";
- push @event, "3:particles_2_5:$pm_2_5";
- push @event, "3:particles_1:$pm_1";
- push @event, "3:state:PM10: $pm_10 PM2_5: $pm_2_5 PM1: $pm_1";
- } elsif ($st eq "CO2Sensor.01") {
- # CO2 Sensor (EEP A5-09-08, A5-09-09)
- # [untested]
- # $db[1] is the CO2 concentration where 0x00 = 0 ppm ... 0xFF = 2000 ppm
- # $db[0]_bit_2 is power failure detection
- my $co2 = $db[1] / 255 * 2000;
- push @event, "3:powerFailureDetection:" . ($db[0] & 4 ? "detected" : "not_detected");
- push @event, "3:CO2:$co2";
- push @event, "3:state:$co2";
- } elsif ($st eq "HSensor.01") {
- # H Sensor (EEP A5-09-0A)
- # [untested]
- # $db[3]_$db[2] is the H concentration where 0x00 = 0 ppm ... 0xFFFF = 2000 ppm
- my $hydro = ($db[3] << 8 | $db[2]) / 65535 * 2000;
- push @event, "3:voltage:" . sprintf("%0.1f", (($db[0] & 0xF0) >> 4) / 15 * 3 + 2) if ($db[0] & 1);
- push @event, "3:temperature:" . sprintf("%0.1f", $db[1] / 255 * 80 - 20) if ($db[0] & 2);
- push @event, "3:H:" . sprintf "%0.2f", $hydro;
- push @event, "3:state:" . sprintf "%0.2f", $hydro;
- } elsif ($st eq "radiationSensor.01") {
- # Radiation Sensor (EEP A5-09-0B)
- # [untested]
- # $db[2]_$db[1] is the radioactivity where 0x00 = 0 ... 0xFFFF = 65535
- my %scaleMulti = (
- 0 => 0.001,
- 1 => 0.01,
- 2 => 0.1,
- 3 => 1,
- 4 => 10,
- 5 => 100,
- 6 => 1000,
- 7 => 10000,
- 8 => 100000
- );
- my $scaleMulti = ($db[0] & 0xF0) >> 4;
- my $scaleDecimals;
- if ($scaleMulti <= 2) {
- $scaleDecimals = "%0." . (3 - $scaleMulti) . "f";
- } else {
- $scaleDecimals = "%d"
- }
- $scaleMulti = $scaleMulti{$scaleMulti} if (exists $scaleMulti{$scaleMulti});
- my %unit = (
- 0 => "uSv/h",
- 1 => "cpm",
- 2 => "Bq/L",
- 3 => "Bq/kg"
- );
- my $unit = ($db[0] & 6) >> 1;
- $unit = $unit{$unit};
- my $radioactivity = $db[2] << 8 | $db[1];
- push @event, "3:radioactivity:" . sprintf "$scaleDecimals", $radioactivity * $scaleMulti;
- push @event, "3:radioactivityUnit:$unit";
- push @event, "3:voltage:" . sprintf("%0.1f", (($db[3] & 0xF0) >> 4) / 15 * 3 + 2) if ($db[0] & 1);
- push @event, "3:state:" . sprintf "$scaleDecimals", $radioactivity * $scaleMulti;
- } elsif ($st eq "roomSensorControl.05") {
- # Room Sensor and Control Unit (EEP A5-10-01 ... A5-10-0D)
- # [Eltako FTR55D, FTR55H, Thermokon SR04 *, Thanos SR *, untested]
- # $db[3] is the fan speed or night reduction for Eltako
- # $db[2] is the setpoint where 0x00 = min ... 0xFF = max or
- # reference temperature for Eltako where 0x00 = 0°C ... 0xFF = 40°C
- # $db[1] is the temperature where 0x00 = +40°C ... 0xFF = 0°C
- # $db[0]_bit_0 is the occupy button, pushbutton or slide switch
- my $temp = sprintf "%0.1f", 40 - $db[1] / 6.375;
- if ($manufID eq "00D") {
- my $nightReduction = 0;
- $nightReduction = 1 if ($db[3] == 0x06);
- $nightReduction = 2 if ($db[3] == 0x0C);
- $nightReduction = 3 if ($db[3] == 0x13);
- $nightReduction = 4 if ($db[3] == 0x19);
- $nightReduction = 5 if ($db[3] == 0x1F);
- my $setpointTemp = sprintf "%0.1f", $db[2] / 6.375;
- push @event, "3:state:T: $temp SPT: $setpointTemp NR: $nightReduction";
- push @event, "3:nightReduction:$nightReduction";
- push @event, "3:setpointTemp:$setpointTemp";
- } else {
- my $fspeed = 3;
- $fspeed = 2 if ($db[3] >= 145);
- $fspeed = 1 if ($db[3] >= 165);
- $fspeed = 0 if ($db[3] >= 190);
- $fspeed = "auto" if ($db[3] >= 210);
- my $switch = $db[0] & 1 ? "on" : "off";
- push @event, "3:state:T: $temp SP: $db[2] F: $fspeed SW: $switch";
- push @event, "3:fanStage:$fspeed";
- push @event, "3:switch:$switch";
- push @event, "3:setpoint:$db[2]";
- my $setpointScaled = EnOcean_ReadingScaled($hash, $db[2], 0, 255);
- if (defined $setpointScaled) {
- push @event, "3:setpointScaled:" . $setpointScaled;
- }
- }
- push @event, "3:temperature:$temp";
- } elsif ($st eq "roomSensorControl.01") {
- # Room Sensor and Control Unit (EEP A5-04-01, A5-10-10 ... A5-10-14)
- # [Thermokon SR04 * rH, Thanus SR *, untested]
- # $db[3] is the setpoint where 0x00 = min ... 0xFF = max
- # $db[2] is the humidity where 0x00 = 0%rH ... 0xFA = 100%rH
- # $db[1] is the temperature where 0x00 = 0°C ... 0xFA = +40°C
- # $db[0] bit D0 is the occupy button, pushbutton or slide switch
- my $temp = sprintf "%0.1f", $db[1] * 40 / 250;
- my $humi = sprintf "%d", $db[2] / 2.5;
- my $switch = $db[0] & 1;
- push @event, "3:humidity:$humi";
- push @event, "3:temperature:$temp";
- if ($manufID eq "039") {
- my $brightness = sprintf "%d", $db[3] * 117;
- push @event, "3:brightness:$brightness";
- push @event, "3:state:T: $temp H: $humi B: $brightness";
- } else {
- push @event, "3:setpoint:$db[3]";
- push @event, "3:state:T: $temp H: $humi SP: $db[3] SW: $switch";
- push @event, "3:switch:$switch";
- my $setpointScaled = EnOcean_ReadingScaled($hash, $db[3], 0, 255);
- if (defined $setpointScaled) {
- push @event, "3:setpointScaled:" . $setpointScaled;
- }
- }
- } elsif ($st eq "roomSensorControl.02") {
- # Room Sensor and Control Unit (A5-10-15 ... A5-10-17)
- # [untested]
- # $db[2] bit D7 ... D2 is the setpoint where 0 = min ... 63 = max
- # $db[2] bit D1 ... $db[1] bit D0 is the temperature where 0 = -10°C ... 1023 = +41.2°C
- # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
- my $temp = sprintf "%0.2f", -10 + ((($db[2] & 3) << 8) | $db[1]) / 19.98;
- my $setpoint = ($db[2] & 0xFC) >> 2;
- my $presence = $db[0] & 1 ? "absent" : "present";
- push @event, "3:state:T: $temp SP: $setpoint P: $presence";
- push @event, "3:presence:$presence";
- push @event, "3:setpoint:$setpoint";
- push @event, "3:temperature:$temp";
- my $setpointScaled = EnOcean_ReadingScaled($hash, $db[2], 0, 255);
- if (defined $setpointScaled) {
- push @event, "3:setpointScaled:" . $setpointScaled;
- }
- } elsif ($st eq "roomSensorControl.18") {
- # Room Sensor and Control Unit (A5-10-18)
- # [untested]
- # $db[3] is the illuminance where min 0x00 = 0 lx, max 0xFA = 1000 lx
- # $db[2] is the setpoint where 250 = 0 °C ... 0 = 40 °C
- # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
- # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
- # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
- # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
- my $lux = $db[3] << 2;
- if ($db[3] == 251) {$lux = "over range";}
- my $setpoint = sprintf "%0.1f", 40 - $db[2] * 40 / 250;
- my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
- my $fanSpeed;
- if ((($db[0] & 0x70) >> 4) == 0) {
- $fanSpeed = "auto";
- } elsif ((($db[0] & 0x70) >> 4) == 7) {
- $fanSpeed = "off";
- } else {
- $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
- }
- my $presence;
- if ($db[0] & 2) {
- $presence = "disabled";
- } else {
- $presence = $db[0] & 1 ? "absent" : "present";
- }
- push @event, "3:brightness:$lux";
- push @event, "3:fan:$fanSpeed";
- push @event, "3:presence:$presence";
- push @event, "3:setpoint:$setpoint";
- push @event, "3:temperature:$temp";
- push @event, "3:state:T: $temp B: $lux F: $fanSpeed SP: $setpoint P: $presence";
- } elsif ($st eq "roomSensorControl.19") {
- # Room Sensor and Control Unit (A5-10-19)
- # [untested]
- # $db[3] is the humidity where min 0x00 = 0 %rH, max 0xFA = 10 %rH
- # $db[2] is the setpoint where 250 = 0 °C ... 0 = 40 °C
- # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
- # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
- # $db[0]_bit_1 is Occupany Button where 0 = pressed, 1 = released
- # $db[0]_bit_0 is Occupany enable where 0 = enabled, 1 = disabled
- my $humi = $db[3] / 2.5;
- my $setpoint = sprintf "%0.1f", 40 - $db[2] * 40 / 250;
- my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
- my $fanSpeed;
- if ((($db[0] & 0x70) >> 4) == 0) {
- $fanSpeed = "auto";
- } elsif ((($db[0] & 0x70) >> 4) == 7) {
- $fanSpeed = "off";
- } else {
- $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
- }
- my $presence;
- if ($db[0] & 1) {
- $presence = "disabled";
- } else {
- $presence = $db[0] & 2 ? "absent" : "present";
- }
- push @event, "3:fan:$fanSpeed";
- push @event, "3:humidity:$humi";
- push @event, "3:presence:$presence";
- push @event, "3:setpoint:$setpoint";
- push @event, "3:temperature:$temp";
- push @event, "3:state:T: $temp H: $humi F: $fanSpeed SP: $setpoint P: $presence";
- } elsif ($st eq "roomSensorControl.1A") {
- # Room Sensor and Control Unit (A5-10-1A)
- # [untested]
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
- # $db[3] > 0xFA is error code
- # $db[2] is the setpoint where 250 = 0 °C ... 0 = 40 °C
- # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
- # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
- # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
- # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
- my $voltage = sprintf "%0.1f", $db[3] * 0.02;
- if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
- my $setpoint = sprintf "%0.1f", 40 - $db[2] * 40 / 250;
- my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
- my $fanSpeed;
- if ((($db[0] & 0x70) >> 4) == 0) {
- $fanSpeed = "auto";
- } elsif ((($db[0] & 0x70) >> 4) == 7) {
- $fanSpeed = "off";
- } else {
- $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
- }
- my $presence;
- if ($db[0] & 2) {
- $presence = "disabled";
- } else {
- $presence = $db[0] & 1 ? "absent" : "present";
- }
- push @event, "3:fan:$fanSpeed";
- push @event, "3:presence:$presence";
- push @event, "3:setpoint:$setpoint";
- push @event, "3:temperature:$temp";
- push @event, "3:voltage:$voltage";
- push @event, "3:state:T: $temp F: $fanSpeed SP: $setpoint P: $presence U: $voltage";
- } elsif ($st eq "roomSensorControl.1B") {
- # Room Sensor and Control Unit (A5-10-1B)
- # [untested]
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
- # $db[3] > 0xFA is error code
- # $db[2] is the illuminance where min 0x00 = 0 lx, max 0xFA = 1000 lx
- # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
- # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
- # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
- # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
- my $voltage = sprintf "%0.1f", $db[3] * 0.02;
- if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
- my $lux = $db[2] << 2;
- if ($db[2] == 251) {$lux = "over range";}
- my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
- my $fanSpeed;
- if ((($db[0] & 0x70) >> 4) == 0) {
- $fanSpeed = "auto";
- } elsif ((($db[0] & 0x70) >> 4) == 7) {
- $fanSpeed = "off";
- } else {
- $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
- }
- my $presence;
- if ($db[0] & 2) {
- $presence = "disabled";
- } else {
- $presence = $db[0] & 1 ? "absent" : "present";
- }
- push @event, "3:brightness:$lux";
- push @event, "3:fan:$fanSpeed";
- push @event, "3:presence:$presence";
- push @event, "3:temperature:$temp";
- push @event, "3:voltage:$voltage";
- push @event, "3:state:T: $temp B: $lux F: $fanSpeed P: $presence U: $voltage";
- } elsif ($st eq "roomSensorControl.1C") {
- # Room Sensor and Control Unit (A5-10-1C)
- # [untested]
- # $db[3] is the illuminance where min 0x00 = 0 lx, max 0xFA = 1000 lx
- # $db[2] is the illuminance setpoint where min 0x00 = 0 lx, max 0xFA = 1000 lx
- # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
- # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
- # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
- # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
- my $lux = $db[3] << 2;
- if ($db[3] == 251) {$lux = "over range";}
- my $setpoint = $db[2] << 2;
- my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
- my $fanSpeed;
- if ((($db[0] & 0x70) >> 4) == 0) {
- $fanSpeed = "auto";
- } elsif ((($db[0] & 0x70) >> 4) == 7) {
- $fanSpeed = "off";
- } else {
- $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
- }
- my $presence;
- if ($db[0] & 2) {
- $presence = "disabled";
- } else {
- $presence = $db[0] & 1 ? "absent" : "present";
- }
- push @event, "3:brightness:$lux";
- push @event, "3:fan:$fanSpeed";
- push @event, "3:presence:$presence";
- push @event, "3:setpoint:$setpoint";
- push @event, "3:temperature:$temp";
- push @event, "3:state:T: $temp B: $lux F: $fanSpeed SP: $setpoint P: $presence";
- } elsif ($st eq "roomSensorControl.1D") {
- # Room Sensor and Control Unit (A5-10-1D)
- # [untested]
- # $db[3] is the humidity where min 0x00 = 0 %rH, max 0xFA = 10 %rH
- # $db[2] is the humidity setpoint where min 0x00 = 0 %rH, max 0xFA = 10 %rH
- # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
- # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
- # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
- # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
- my $humi = sprintf "%d", $db[3] / 2.5;
- my $setpoint = $db[2] / 2.5;
- my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
- my $fanSpeed;
- if ((($db[0] & 0x70) >> 4) == 0) {
- $fanSpeed = "auto";
- } elsif ((($db[0] & 0x70) >> 4) == 7) {
- $fanSpeed = "off";
- } else {
- $fanSpeed = (($db[0] & 0x70) >> 4) - 1;
- }
- my $presence;
- if ($db[0] & 2) {
- $presence = "disabled";
- } else {
- $presence = $db[0] & 1 ? "absent" : "present";
- }
- push @event, "3:fan:$fanSpeed";
- push @event, "3:humidity:$humi";
- push @event, "3:presence:$presence";
- push @event, "3:setpoint:$setpoint";
- push @event, "3:temperature:$temp";
- push @event, "3:state:T: $temp H: $humi F: $fanSpeed SP: $setpoint P: $presence";
- } elsif ($st eq "roomSensorControl.1F") {
- # Room Sensor and Control Unit (A5-10-1F)
- # [untested]
- # $db[3] is the fan speed
- # $db[2] is the setpoint where 0 = 0 ... 255 = 255
- # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
- # $db[0]_bit_6 ... $db[0]_bit_4 is the fan speed
- # $db[0]_bit_6 ... $db[0]_bit_4 are flags
- # $db[0]_bit_1 is Occupany enable where 0 = enabled, 1 = disabled
- # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
- my $fanSpeed = "unknown";
- if ($db[0] & 0x10) {
- $fanSpeed = 3;
- $fanSpeed = 2 if ($db[3] >= 145);
- $fanSpeed = 1 if ($db[3] >= 165);
- $fanSpeed = 0 if ($db[3] >= 190);
- $fanSpeed = "auto" if ($db[3] >= 210);
- }
- my $setpoint = "unknown";
- $setpoint = $db[2] if ($db[0] & 0x20);
- my $temp = "unknown";
- $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250 if ($db[0] & 0x40);
- my $presence = "unknown";
- $presence = "absent" if (!($db[0] & 2));
- $presence = "present" if (!($db[0] & 1));
- push @event, "3:fan:$fanSpeed";
- push @event, "3:presence:$presence";
- push @event, "3:setpoint:$setpoint";
- push @event, "3:temperature:$temp";
- push @event, "3:state:T: $temp F: $fanSpeed SP: $setpoint P: $presence";
- my $setpointScaled = EnOcean_ReadingScaled($hash, $db[2], 0, 255);
- if (defined $setpointScaled) {
- push @event, "3:setpointScaled:" . $setpointScaled;
- }
- } elsif ($st eq "roomSensorControl.20") {
- # Room Operation Panel (A5-10-20, A5-10-21)
- # [untested]
- # $db[3] is the setpoint where 0 = 0 ... 255 = 255
- # $db[2] is the humidity setpoint where min 0x00 = 0 %rH, max 0xFA = 100 %rH
- # $db[1] is the temperature where 250 = 0 °C ... 0 = 40 °C
- # $db[0]_bit_6 ... $db[0]_bit_5 is setpoint mode
- # $db[0]_bit_4 is battery state 0 = ok, 1 = low
- # $db[0]_bit_0 is user activity where 0 = no, 1 = yes
- my $humi = sprintf "%d", $db[2] / 2.5;
- my $setpoint = $db[3];
- my $temp = sprintf "%0.1f", 40 - $db[1] * 40 / 250;
- my $setpointMode;
- if ((($db[0] & 0x60) >> 5) == 3) {
- $setpointMode = "reserved";
- } elsif ((($db[0] & 0x60) >> 5) == 2) {
- $setpointMode = "auto";
- } elsif ((($db[0] & 0x60) >> 1) == 1){
- $setpointMode = "frostProtection";
- } else {
- $setpointMode = "setpoint";
- }
- my $battery = ($db[0] & 0x10) ? "low" : "ok";
- push @event, "3:activity:" . ($db[0] & 1 ? "yes" : "no");
- push @event, "3:battery:$battery";
- push @event, "3:humidity:$humi";
- push @event, "3:setpoint:$setpoint";
- push @event, "3:setpointMode:$setpointMode";
- push @event, "3:temperature:$temp";
- push @event, "3:state:T: $temp H: $humi SP: $setpoint B: $battery";
- my $setpointScaled = EnOcean_ReadingScaled($hash, $db[3], 0, 255);
- if (defined $setpointScaled) {
- push @event, "3:setpointScaled:" . $setpointScaled;
- }
- } elsif ($st eq "roomSensorControl.22") {
- # Room Operation Panel (A5-10-22, A5-10-23)
- my $setpoint = $db[3];
- my $humi = sprintf "%d", $db[2] / 2.5;
- my $temp = sprintf "%0.1f", $db[1] * 40 / 250;
- my $fanSpeed;
- if ((($db[0] & 0xE0) >> 5) == 4) {
- $fanSpeed = 3;
- } elsif ((($db[0] & 0xE0) >> 5) == 3) {
- $fanSpeed = 2;
- } elsif ((($db[0] & 0xE0) >> 5) == 2) {
- $fanSpeed = 1;
- } elsif ((($db[0] & 0xE0) >> 5) == 1){
- $fanSpeed = "off";
- } else {
- $fanSpeed = "auto";
- }
- my $occupancy = ($db[0] & 1) ? "occupied" : "unoccupied";
- push @event, "3:occupancy:$occupancy";
- push @event, "3:humidity:$humi";
- push @event, "3:setpoint:$setpoint";
- push @event, "3:fanSpeed:$fanSpeed";
- push @event, "3:temperature:$temp";
- push @event, "3:state:T: $temp H: $humi SP: $setpoint F: $fanSpeed O: $occupancy";
- my $setpointScaled = EnOcean_ReadingScaled($hash, $db[3], 0, 255);
- if (defined $setpointScaled) {
- push @event, "3:setpointScaled:" . $setpointScaled;
- }
- } elsif ($st eq "tempHumiSensor.02") {
- # Temperatur and Humidity Sensor(EEP A5-04-02)
- # [Eltako FAFT60, FIFT63AP]
- # $db[3] is the voltage where 0x59 = 2.5V ... 0x9B = 4V, only at Eltako
- # $db[2] is the humidity where 0x00 = 0%rH ... 0xFA = 100%rH
- # $db[1] is the temperature where 0x00 = -20°C ... 0xFA = +60°C
- my $humi = sprintf "%d", $db[2] / 2.5;
- my $temp = sprintf "%0.1f", -20 + $db[1] * 80 / 250;
- my $battery = "unknown";
- if ($manufID eq "00D") {
- # Eltako sensor
- my $voltage = sprintf "%0.1f", $db[3] * 6.58 / 255;
- my $energyStorage = "unknown";
- if ($db[3] <= 0x58) {
- $energyStorage = "empty";
- $battery = "low";
- }
- elsif ($db[3] <= 0xDC) {
- $energyStorage = "charged";
- $battery = "ok";
- }
- else {
- $energyStorage = "full";
- $battery = "ok";
- }
- push @event, "3:battery:$battery";
- push @event, "3:energyStorage:$energyStorage";
- push @event, "3:voltage:$voltage";
- }
- push @event, "3:state:T: $temp H: $humi B: $battery";
- push @event, "3:humidity:$humi";
- push @event, "3:temperature:$temp";
- } elsif ($st eq "tempHumiSensor.03") {
- # Temperatur and Humidity Sensor(EEP A5-04-03)
- # [untested]
- # $db[3] is the humidity where 0x00 = 0%rH ... 0xFF = 100%rH
- # $db[2] .. $db[1] is the temperature where 0x00 = -20°C ... 0x3FF = +60°C
- my $humi = sprintf "%d", $db[3] / 2.55;
- my $temp = sprintf "%0.1f", -20 + ($db[2] << 8 | $db[1]) * 80 / 1023;
- push @event, "3:state:T: $temp H: $humi";
- push @event, "3:humidity:$humi";
- push @event, "3:temperature:$temp";
- push @event, "3:telegramType:" . ($db[0] & 1 ? "event" : "heartbeat");
- } elsif ($st eq "baroSensor.01") {
- # Barometric Sensor(EEP A5-04-03)
- # [untested]
- # $db[3] .. $db[2] is the barometric where 0x00 = 500 hPa ... 0x3FF = 1150 hPa
- my $baro = sprintf "%d", 500 + ($db[2] << 8 | $db[1]) * 650 / 1023;
- push @event, "3:state:$baro";
- push @event, "3:airPressure:$baro";
- push @event, "3:telegramType:" . ($db[0] & 1 ? "event" : "heartbeat");
- } elsif ($st eq "lightSensor.01") {
- # Light Sensor (EEP A5-06-01)
- # [Eltako FAH60, FAH63, FIH63, Thermokon SR65 LI, untested]
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
- # $db[3] is the low illuminance for Eltako devices where
- # min 0x00 = 0 lx, max 0xFF = 100 lx, if $db[2] = 0
- # $db[2] is the illuminance (ILL2) where min 0x00 = 300 lx, max 0xFF = 30000 lx
- # $db[1] is the illuminance (ILL1) where min 0x00 = 600 lx, max 0xFF = 60000 lx
- # $db[0]_bit_0 is Range select where 0 = ILL1, 1 = ILL2
- my $lux;
- my $voltage = "unknown";
- if ($manufID eq "00D") {
- if($db[2] == 0) {
- $lux = sprintf "%d", $db[3] * 100 / 255;
- } else {
- $lux = sprintf "%d", $db[2] * 116.48 + 300;
- }
- } else {
- $voltage = sprintf "%0.1f", $db[3] * 0.02;
- if($db[0] & 1) {
- $lux = sprintf "%d", $db[2] * 116.48 + 300;
- } else {
- $lux = sprintf "%d", $db[1] * 232.94 + 600;
- }
- push @event, "3:voltage:$voltage";
- }
- push @event, "3:brightness:$lux";
- push @event, "3:state:$lux";
- } elsif ($st eq "lightSensor.02") {
- # Light Sensor (EEP A5-06-02)
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
- # $db[2] is the illuminance (ILL2) where min 0x00 = 0 lx, max 0xFF = 510 lx
- # $db[1] is the illuminance (ILL1) where min 0x00 = 0 lx, max 0xFF = 1020 lx
- # $db[0]_bit_0 is Range select where 0 = ILL1, 1 = ILL2
- my $lux;
- my $voltage = sprintf "%0.1f", $db[3] * 0.02;
- if($db[0] & 1) {
- $lux = $db[2] << 1;
- } else {
- $lux = $db[1] << 2;
- }
- push @event, "3:voltage:$voltage";
- push @event, "3:brightness:$lux";
- push @event, "3:state:$lux";
- } elsif ($st eq "lightSensor.03") {
- # Light Sensor (EEP A5-06-03)
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
- # $db[3] > 0xFA is error code
- # $db[2]_bit_7 ... $db[1]_bit_6 is the illuminance where min 0x000 = 0 lx, max 0x3E8 = 1000 lx
- my $lux = $db[2] << 2 | $db[1] >> 6;
- if ($lux == 1001) {$lux = "over range";}
- my $voltage = sprintf "%0.1f", $db[3] * 0.02;
- if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
- push @event, "3:voltage:$voltage";
- push @event, "3:brightness:$lux";
- push @event, "3:state:$lux";
- } elsif ($st eq "lightSensor.04") {
- # Light Sensor (EEP A5-06-04)
- my $temperature;
- if ($db[0] & 2) {
- $temperature = sprintf "%0.1f", $db[3] * 80 / 255 - 20;
- push @event, "3:temperature:$temperature";
- } else {
- $temperature = '-';
- CommandDeleteReading(undef, "$name temperature");
- }
- my $brightness;
- if ($db[0] & 1) {
- $brightness = $db[2] << 8 | $db[1];
- push @event, "3:brightness:$brightness";
- } else {
- $brightness = '-';
- CommandDeleteReading(undef, "$name brightness");
- }
- my $energyStorage = sprintf "%d", ($db[0] >> 4) * 100 / 15;
- my $battery;
- if ($energyStorage <= 6) {
- $battery = 'low';
- push @event, "3:battery:low";
- push @event, "3:energyStorage:$energyStorage";
- } else {
- $battery = 'ok';
- push @event, "3:battery:ok";
- push @event, "3:energyStorage:$energyStorage";
- }
- push @event, "3:state:T: $temperature E: $brightness B: $battery";
- } elsif ($st eq "lightSensor.05") {
- # Light Sensor (EEP A5-06-05)
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
- # $db[2] is the illuminance (ILL2) where min 0x00 = 0 lx, max 0xFF = 5100 lx
- # $db[1] is the illuminance (ILL1) where min 0x00 = 0 lx, max 0xFF = 1020000 lx
- # $db[0]_bit_0 is Range select where 0 = ILL1, 1 = ILL2
- my $lux;
- if($db[0] & 1) {
- $lux = sprintf "%d", $db[2] * 20;
- } else {
- $lux = sprintf "%d", $db[1] * 40;
- }
- push @event, "3:voltage:" . sprintf "%0.1f", $db[3] * 0.02;
- push @event, "3:brightness:$lux";
- push @event, "3:state:$lux";
- } elsif ($st eq "occupSensor.01") {
- # Occupancy Sensor (EEP A5-07-01)
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
- # $db[3] > 0xFA is error code
- # $db[2] is solar panel current where =0 uA ... 0xFF = 127 uA
- # $db[1] is PIR Status (motion) where 0 ... 127 = off, 128 ... 255 = on
- my $motion = "off";
- if ($db[1] >= 128) {$motion = "on";}
- if ($db[0] & 1) {push @event, "3:voltage:" . sprintf "%0.1f", $db[3] * 0.02;}
- if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
- if ($manufID eq "00B") {
- push @event, "3:current:" . sprintf "%0.1f", $db[2] / 2;
- if ($db[0] & 2) {
- push @event, "3:sensorType:ceiling";
- } else {
- push @event, "3:sensorType:wall";
- }
- }
- if ($model eq "tracker") {
- @{$hash->{helper}{motionTimer}} = ($hash, 'motion', 'off', 1, 5);
- @{$hash->{helper}{stateTimer}} = ($hash, 'state', 'off', 1, 5);
- RemoveInternalTimer($hash->{helper}{motionTimer});
- RemoveInternalTimer($hash->{helper}{stateTimer});
- InternalTimer(gettimeofday() + AttrVal($name, 'trackerWakeUpCycle', 30) * 1.1, 'EnOcean_readingsSingleUpdate', $hash->{helper}{motionTimer}, 0);
- InternalTimer(gettimeofday() + AttrVal($name, 'trackerWakeUpCycle', 30) * 1.1, 'EnOcean_readingsSingleUpdate', $hash->{helper}{stateTimer}, 0);
- }
- if (!exists($hash->{helper}{lastVoltage}) || $hash->{helper}{lastVoltage} != $db[3]) {
- push @event, "3:battery:" . ($db[3] * 0.02 > 2.8 ? "ok" : "low");
- $hash->{helper}{lastVoltage} = $db[3];
- }
- push @event, "3:button:" . ($db[0] & 4 ? "released" : "pressed") if ($manufID eq "7FF");
- push @event, "3:motion:$motion";
- push @event, "3:state:$motion";
- } elsif ($st eq "occupSensor.02") {
- # Occupancy Sensor (EEP A5-07-02)
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
- # $db[3] > 0xFA is error code
- # $db[0]_bit_7 is PIR Status (motion) where 0 = off, 1 = on
- my $motion = $db[0] >> 7 ? "on" : "off";
- if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
- push @event, "3:battery:" . ($db[3] * 0.02 > 2.9 ? "ok" : "low");
- push @event, "3:motion:$motion";
- push @event, "3:voltage:" . sprintf "%0.1f", $db[3] * 0.02;
- push @event, "3:state:$motion";
- } elsif ($st eq "occupSensor.03") {
- # Occupancy Sensor (EEP A5-07-03)
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
- # $db[3] > 0xFA is error code
- # $db[2]_bit_7 ... $db[1]_bit_6 is the illuminance where min 0x000 = 0 lx, max 0x3E8 = 1000 lx
- # $db[0]_bit_7 is PIR Status (motion) where 0 = off, 1 = on
- my $motion = $db[0] >> 7 ? "on" : "off";
- my $lux = $db[2] << 2 | $db[1] >> 6;
- if ($lux == 1001) {$lux = "over range";}
- my $voltage = sprintf "%0.1f", $db[3] * 0.02;
- if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
- push @event, "3:battery:" . ($db[3] * 0.02 > 2.9 ? "ok" : "low");
- push @event, "3:brightness:$lux";
- push @event, "3:motion:$motion";
- push @event, "3:voltage:$voltage";
- push @event, "3:state:M: $motion E: $lux U: $voltage";
- } elsif ($st =~ m/^lightTempOccupSensor/) {
- # Light, Temperatur and Occupancy Sensor (EEP A5-08-01 ... A5-08-03)
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V
- # $db[2] is the illuminance where min 0x00 = 0 lx, max 0xFF = 510 lx, 1020 lx, (2048 lx)
- # $db[1] is the temperature whrere 0x00 = 0 °C ... 0xFF = 51 °C or -30 °C ... 50°C
- # $db[0]_bit_1 is PIR Status (motion) where 0 = on, 1 = off
- # $db[0]_bit_0 is Occupany Button where 0 = pressed, 1 = released
- my $lux;
- my $temp;
- my $voltage = sprintf "%0.1f", $db[3] * 0.02;
- my $motion = $db[0] & 2 ? "off" : "on";
- my $presence = $db[0] & 1 ? "absent" : "present";
- if ($st eq "lightTempOccupSensor.01") {
- # Light, Temperatur and Occupancy Sensor (EEP A5-08-01)
- # [Eltako FABH63, FBH55, FBH63, FIBH63]
- if ($manufID eq "00D") {
- $lux = sprintf "%d", $db[2] * 2048 / 255;
- push @event, "3:state:M: $motion E: $lux";
- } else {
- $lux = $db[2] << 1;
- $temp = sprintf "%0.1f", $db[1] * 0.2;
- push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage";
- push @event, "3:presence:$presence";
- push @event, "3:temperature:$temp";
- push @event, "3:voltage:$voltage";
- }
- } elsif ($st eq "lightTempOccupSensor.02") {
- # Light, Temperatur and Occupancy Sensor (EEP A5-08-02)
- $lux = $db[2] << 2;
- $temp = sprintf "%0.1f", $db[1] * 0.2;
- push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage";
- push @event, "3:presence:$presence";
- push @event, "3:temperature:$temp";
- push @event, "3:voltage:$voltage";
- } elsif ($st eq "lightTempOccupSensor.03") {
- # Light, Temperatur and Occupancy Sensor (EEP A5-08-03)
- $lux = $db[2] * 6;
- $temp = sprintf "%0.1f", -30 + $db[1] * 80 / 255;
- push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage";
- push @event, "3:presence:$presence";
- push @event, "3:temperature:$temp";
- push @event, "3:voltage:$voltage";
- }
- push @event, "3:brightness:$lux";
- push @event, "3:motion:$motion";
- } elsif ($st eq "lightCtrlState.01") {
- # Lighting Controller State (EEP A5-11-01)
- # $db[3] is the illumination where 0x00 = 0 lx ... 0xFF = 510 lx
- # $db[2] is the illumination Setpoint where 0x00 = 0 ... 0xFF = 255
- # $db[1] is the Dimming Output Level where 0x00 = 0 ... 0xFF = 255
- # $db[0]_bit_7 is the Repeater state where 0 = disabled, 1 = enabled
- # $db[0]_bit_6 is the Power Relay Timer state where 0 = disabled, 1 = enabled
- # $db[0]_bit_5 is the Daylight Harvesting state where 0 = disabled, 1 = enabled
- # $db[0]_bit_4 is the Dimming mode where 0 = switching, 1 = dimming
- # $db[0]_bit_2 is the Magnet Contact state where 0 = open, 1 = closed
- # $db[0]_bit_1 is the Occupancy (prensence) state where 0 = absent, 1 = present
- # $db[0]_bit_0 is the Power Relay state where 0 = off, 1 = on
- push @event, "3:brightness:" . ($db[3] << 1);
- push @event, "3:illum:$db[2]";
- push @event, "3:dim:$db[1]";
- push @event, "3:powerRelayTimer:" . ($db[0] & 0x80 ? "enabled" : "disabled");
- push @event, "3:repeater:" . ($db[0] & 0x40 ? "enabled" : "disabled");
- push @event, "3:daylightHarvesting:" . ($db[0] & 0x20 ? "enabled" : "disabled");
- push @event, "3:mode:" . ($db[0] & 0x10 ? "dimming" : "switching");
- push @event, "3:contact:" . ($db[0] & 4 ? "closed" : "open");
- push @event, "3:presence:" . ($db[0] & 2 ? "present" : "absent");
- push @event, "3:powerSwitch:" . ($db[0] & 1 ? "on" : "off");
- push @event, "3:state:" . ($db[0] & 1 ? "on" : "off");
- } elsif ($st eq "tempCtrlState.01") {
- # Temperature Controller Output (EEP A5-11-02)
- # $db[3] is the Control Variable where 0x00 = 0 % ... 0xFF = 100 %
- # $db[2] is the Fan Stage
- # $db[1] is the Actual Setpoint where 0x00 = 0 °C ... 0xFF = 51.2 °C
- # $db[0]_bit_7 is the Alarm state where 0 = no, 1 = yes
- # $db[0]_bit_6 ... $db[0]_bit_5 is the Controller Mode
- # $db[0]_bit_4 is the Controller State where 0 = auto, 1 = override
- # $db[0]_bit_2 is the Energy hold-off where 0 = normal, 1 = hold-off
- # $db[0]_bit_1 ... $db[0]_bit_0is the Occupancy (prensence) state where 0 = present
- # 1 = absent, 3 = standby, 4 = frost
- push @event, "3:controlVar:" . sprintf "%d", $db[3] * 100 / 255;
- if (($db[2] & 3) == 0) {
- push @event, "3:fan:0";
- } elsif (($db[2] & 3) == 1){
- push @event, "3:fan:1";
- } elsif (($db[2] & 3) == 2){
- push @event, "3:fan:2";
- } elsif (($db[2] & 3) == 3){
- push @event, "3:fan:3";
- } elsif ($db[2] == 255){
- push @event, "3:fan:unknown";
- }
- push @event, "3:fanMode:" . ($db[2] & 0x10 ? "auto" : "manual");
- my $setpointTemp = sprintf "%0.1f", $db[1] * 0.2;
- push @event, "3:setpointTemp:$setpointTemp";
- push @event, "3:alarm:" . ($db[0] & 1 ? "on" : "off");
- my $controllerMode = ($db[0] & 0x60) >> 5;
- if ($controllerMode == 0) {
- push @event, "3:controllerMode:auto";
- } elsif ($controllerMode == 1) {
- push @event, "3:controllerMode:heating";
- } elsif ($controllerMode == 2) {
- push @event, "3:controllerMode:cooling";
- } elsif ($controllerMode == 3) {
- push @event, "3:controllerMode:off";
- }
- push @event, "3:controllerState:" . ($db[0] & 0x10 ? "override" : "auto");
- push @event, "3:energyHoldOff:" . ($db[0] & 4 ? "holdoff" : "normal");
- if (($db[0] & 3) == 0) {
- push @event, "3:presence:present";
- } elsif (($db[0] & 3) == 1){
- push @event, "3:presence:absent";
- } elsif (($db[0] & 3) == 2){
- push @event, "3:presence:standby";
- } elsif (($db[0] & 3) == 3){
- push @event, "3:presence:frost";
- }
- push @event, "3:state:$setpointTemp";
- } elsif ($st eq "shutterCtrlState.01") {
- # Blind Status (EEP A5-11-03)
- # $db[3] is the Shutter Position where 0 = 0 % ... 100 = 100 %
- # $db[2]_bit_7 is the Angle sign where 0 = positive, 1 = negative
- # $db[2]_bit_6 ... $db[2]_bit_0 where 0 = 0° ... 90 = 180°
- # $db[1]_bit_7 is the Position Value Flag where 0 = no available, 1 = available
- # $db[1]_bit_6 is the Angle Value Flag where 0 = no available, 1 = available
- # $db[1]_bit_5 ... $db[1]_bit_4 is the Error State (alarm)
- # $db[1]_bit_3 ... $db[1]_bit_2 is the End-position State
- # $db[1]_bit_1 ... $db[1]_bit_0 is the Shutter State
- # $db[0]_bit_7 is the Service Mode where 0 = no, 1 = yes
- # $db[0]_bit_6 is the Position Mode where 0 = normal, 1 = inverse
- if ($db[1] & 0x80) {
- push @event, "3:position:" . $db[3];
- }
- my $anglePos = ($db[2] & 0x7F) << 1;
- if ($db[2] & 0x80) {$anglePos *= -1;}
- if ($db[1] & 0x40) {
- push @event, "3:anglePos:" . $anglePos;
- }
- my $alarm = ($db[1] & 0x30) >> 4;
- if ($alarm == 0) {
- push @event, "3:alarm:off";
- } elsif ($alarm == 1){
- push @event, "3:alarm:no_endpoints_defined";
- } elsif ($alarm == 2){
- push @event, "3:alarm:on";
- } elsif ($alarm == 3){
- push @event, "3:alarm:not_used";
- }
- my $endPosition = ($db[1] & 0x0C) >> 2;
- if ($endPosition == 0) {
- push @event, "3:endPosition:not_available";
- push @event, "3:state:not_available";
- } elsif ($endPosition == 1) {
- push @event, "3:endPosition:not_reached";
- push @event, "3:state:not_reached";
- } elsif ($endPosition == 2) {
- push @event, "3:endPosition:open";
- push @event, "3:state:open";
- } elsif ($endPosition == 3){
- push @event, "3:endPosition:closed";
- push @event, "3:state:closed";
- }
- my $shutterState = $db[1] & 3;
- if (($db[1] & 3) == 0) {
- push @event, "3:shutterState:not_available";
- } elsif (($db[1] & 3) == 1) {
- push @event, "3:shutterState:stopped";
- } elsif (($db[1] & 3) == 2){
- push @event, "3:shutterState:opens";
- } elsif (($db[1] & 3) == 3){
- push @event, "3:shutterState:closes";
- }
- push @event, "3:serviceOn:" . ($db[0] & 0x80 ? "yes" : "no");
- push @event, "3:positionMode:" . ($db[0] & 0x40 ? "inverse" : "normal");
- } elsif ($st eq "lightCtrlState.02") {
- # Extended Lighting Status (EEP A5-11-04)
- # $db[3] the contents of the variable depends on the parameter mode
- # $db[2] the contents of the variable depends on the parameter mode
- # $db[1] the contents of the variable depends on the parameter mode
- # $db[0]_bit_7 is the Service Mode where 0 = no, 1 = yes
- # $db[0]_bit_6 is the operating hours flag where 0 = not_available, 1 = available
- # $db[0]_bit_5 ... $db[0]_bit_4 is the Error State (alarm)
- # $db[0]_bit_2 ... $db[0]_bit_1 is the parameter mode
- # $db[0]_bit_0 is the lighting status where 0 = off, 1 = on
- push @event, "3:serviceOn:" . ($db[1] & 0x80 ? "yes" : "no");
- my $alarm = ($db[0] & 0x30) >> 4;
- if ($alarm == 0) {
- push @event, "3:alarm:off";
- } elsif ($alarm == 1){
- push @event, "3:alarm:lamp_failure";
- } elsif ($alarm == 2){
- push @event, "3:alarm:internal_failure";
- } elsif ($alarm == 3){
- push @event, "3:alarm:external_periphery_failure";
- }
- my $mode = ($db[0] & 6) >> 1;
- if ($mode == 0) {
- # dimmer value and lamp operating hours
- push @event, "3:dim:$db[3]";
- if ($db[0] & 40) {
- push @event, "3:lampOpHours:" . ($db[2] << 8 | $db[1]);
- } else {
- push @event, "3:lampOpHours:unknown";
- }
- } elsif ($mode == 1){
- # RGB value
- push @event, "3:red:$db[3]";
- push @event, "3:green:$db[2]";
- push @event, "3:blue:$db[1]";
- push @event, "3:rgb:" . substr($data, 0, 6);
- } elsif ($mode == 2){
- # energy metering value
- my @measureUnit = ("mW", "W", "kW", "MW", "Wh", "kWh", "MWh", "GWh",
- "mA", "A", "mV", "V");
- if ($db[1] < 4) {
- push @event, "3:power:" . ($db[3] << 8 | $db[2]);
- push @event, "3:powerUnit:" . $measureUnit[$db[1]];
- } elsif ($db[1] < 8) {
- push @event, "3:energy:" . ($db[3] << 8 | $db[2]);
- push @event, "3:energyUnit:" . $measureUnit[$db[1]];
- } elsif ($db[1] == 8) {
- push @event, "3:current:" . ($db[3] << 8 | $db[2]);
- push @event, "3:currentUnit:" . $measureUnit[$db[1]];
- } elsif ($db[1] == 9) {
- push @event, "3:current:" . sprintf "%0.1f", ($db[3] << 8 | $db[2]) / 10;
- push @event, "3:currentUnit:" . $measureUnit[$db[1]];
- } elsif ($db[1] == 10) {
- push @event, "3:voltage:" . ($db[3] << 8 | $db[2]);
- push @event, "3:voltageUnit:" . $measureUnit[$db[1]];
- } elsif ($db[1] == 11) {
- push @event, "3:voltage:" . sprintf "%0.1f", ($db[3] << 8 | $db[2]) / 10;
- push @event, "3:voltageUnit:" . $measureUnit[$db[1]];
- } else {
- push @event, "3:measuredValue:" . ($db[3] << 8 | $db[2]);
- push @event, "3:measureUnit:unknown";
- }
- } elsif ($mode == 3){
- # not used
- }
- push @event, "3:powerSwitch:" . ($db[0] & 1 ? "on" : "off");
- push @event, "3:state:" . ($db[0] & 1 ? "on" : "off");
- } elsif ($st eq "switch.05") {
- # Dual Channel Switch Actuator
- # (A5-11-05)
- if ($db[0] & 1) {
- push @event, "3:workingMode:" . (($db[0] & 0x70) >> 4);
- push @event, "3:channel1:" . ($db[0] & 2 ? "on" : "off");
- push @event, "3:channel2:" . ($db[0] & 4 ? "on" : "off");
- push @event, "3:state:1: " . ($db[0] & 2 ? "on" : "off") . " 2: " . ($db[0] & 4 ? "on" : "off");
- }
- } elsif ($st =~ m/^autoMeterReading\.0[0-3]$/ || $st eq "actuator.01" && $manufID eq "033") {
- # Automated meter reading (AMR) (EEP A5-12-00 ... A5-12-03)
- # $db[3] (MSB) + $db[2] + $db[1] (LSB) is the Meter reading
- # $db[0]_bit_7 ... $db[0]_bit_4 is the Measurement channel
- # $db[0]_bit_2 is the Data type where 0 = cumulative value, 1 = current value
- # $db[0]_bit_1 ... $db[0]_bit_0 is the Divisor where 0 = x/1, 1 = x/10,
- # 2 = x/100, 3 = x/1000
- my $dataType = ($db[0] & 4) >> 2;
- my $divisor = $db[0] & 3;
- my $meterReading;
- if ($divisor == 3) {
- $meterReading = sprintf "%.3f", ($db[3] << 16 | $db[2] << 8 | $db[1]) / 1000;
- } elsif ($divisor == 2) {
- $meterReading = sprintf "%.2f", ($db[3] << 16 | $db[2] << 8 | $db[1]) / 100;
- } elsif ($divisor == 1) {
- $meterReading = sprintf "%.1f", ($db[3] << 16 | $db[2] << 8 | $db[1]) / 10;
- } else {
- $meterReading = $db[3] << 16 | $db[2] << 8 | $db[1];
- }
- my $channel = $db[0] >> 4;
- if ($st eq "autoMeterReading.00") {
- # Automated meter reading (AMR), Counter (EEP A5-12-01)
- # [Thermokon SR-MI-HS, untested]
- if ($dataType == 1) {
- # current value
- push @event, "3:currentValue" . sprintf('%02d', $channel) . ":$meterReading";
- push @event, "3:state:$meterReading";
- } else {
- # cumulative counter
- push @event, "3:counter" . sprintf('%02d', $channel) . ":$meterReading";
- }
- } elsif ($st eq "autoMeterReading.01" || $st eq "actuator.01" && $manufID eq "033") {
- # Automated meter reading (AMR), Electricity (EEP A5-12-01)
- # [Eltako FSS12, FWZ12, DSZ14DRS, DSZ14WDRS, DWZ61]
- # $db[0]_bit_7 ... $db[0]_bit_4 is the Tariff info
- # $db[0]_bit_2 is the Data type where 0 = cumulative value kWh,
- # 1 = current value W
- if ($db[0] == 0x8F && $manufID eq "00D") {
- # Eltako, read meter serial number
- my $serialNumber;
- if ($db[1] == 0) {
- # first 2 digits of the serial number
- $serialNumber = substr(ReadingsVal($name, "serialNumber", "S-------"), 4, 4);
- $serialNumber = sprintf "S-%01x%01x%4s", $db[3] >> 4, $db[3] & 0x0F, $serialNumber;
- } else {
- # last 4 digits of the serial number
- $serialNumber = substr(ReadingsVal($name, "serialNumber", "S---"), 0, 4);
- $serialNumber = sprintf "%4s%01x%01x%01x%01x", $serialNumber,
- $db[2] >> 4, $db[2] & 0x0F, $db[3] >> 4, $db[3] & 0x0F;
- }
- push @event, "3:serialNumber:$serialNumber";
- } elsif ($dataType == 1) {
- # momentary power
- push @event, "3:power:$meterReading";
- if (!($st eq "actuator.01" && $manufID eq "033")) {
- push @event, "3:state:$meterReading";
- }
- } else {
- # power consumption
- push @event, "3:energy$channel:$meterReading";
- push @event, "3:currentTariff:$channel";
- }
- } elsif ($st eq "autoMeterReading.02" || $st eq "autoMeterReading.03") {
- # Automated meter reading (AMR), Gas, Water (EEP A5-12-02, A5-12-03)
- if ($dataType == 1) {
- # current value
- push @event, "3:flowrate:$meterReading";
- push @event, "3:state:$meterReading";
- } else {
- # cumulative counter
- push @event, "3:consumption$channel:$meterReading";
- push @event, "3:currentTariff:$channel";
- }
- }
- } elsif ($st =~ m/^autoMeterReading\.0[45]$/) {
- # $db[1] is the temperature 0 .. 0xFF >> -40 ... 40
- # $db[0]_bit_1 ... $db[0]_bit_0 is the battery level
- my $temperature = sprintf "%0.1f", ($db[1] / 255 * 80) - 40;
- my $battery = $db[0] & 3;
- if ($battery == 3) {
- $battery = "empty";
- } elsif ($battery == 2) {
- $battery = "low";
- } elsif ($battery == 1) {
- $battery = "ok";
- } else {
- $battery = "full";
- }
- push @event, "3:battery:$battery";
- push @event, "3:temperature:$temperature";
- if ($st eq "autoMeterReading.04") {
- # Automated meter reading (AMR), Temperature, Load (EEP A5-12-04)
- # $db[3] ... $db[2]_bit_2 is the Current Value in gram
- my $weight = $db[3] << 6 | $db[2];
- push @event, "3:weight:$weight";
- push @event, "3:state:T: $temperature W: $weight B: $battery";
- } elsif ($st eq "autoMeterReading.05") {
- # Automated meter reading (AMR), Temperature, Container (EEP A5-12-05)
- # $db[3] ... $db[2]_bit_6 is position sensor
- my @sp;
- $sp[0] = ($db[3] & 128) >> 7;
- $sp[1] = ($db[3] & 64) >> 6;
- $sp[2] = ($db[3] & 32) >> 5;
- $sp[3] = ($db[3] & 16) >> 4;
- $sp[4] = ($db[3] & 8) >> 3;
- $sp[5] = ($db[3] & 4) >> 2;
- $sp[6] = ($db[3] & 2) >> 1;
- $sp[7] = $db[3] & 1;
- $sp[8] = ($db[2] & 128) >> 7;
- $sp[9] = ($db[2] & 64) >> 6;
- my $amount = 0;
- for (my $spCntr = 0; $spCntr <= 9; $spCntr ++) {
- push @event, "3:location" . $spCntr . ":" . ($sp[$spCntr] ? "possessed" : "not_possessed");
- $amount += $sp[$spCntr];
- }
- push @event, "3:amount:$amount";
- push @event, "3:state:T: $temperature L: " . $sp[0] . $sp[1] . " " . $sp[2] . $sp[3] .
- " " . $sp[4] . $sp[5] . " " . $sp[6] . $sp[7] . " " . $sp[8] . $sp[9] . " B: $battery";
- }
- } elsif ($st eq "autoMeterReading.10") {
- # Automated meter reading (AMR), current meter 16 channels (EEP A5-12-10)
- my $meterReading = hex(substr($data, 0, 6)) / 10 ** ($db[0] & 3);
- my $channel = sprintf '%02d', $db[0] & 0xF0 >> 4;
- my $scaleDecimals = "%0." . ($db[0] & 3) . "f";
- push @event, "3:currentTariff:$channel";
- if ($db[0] & 4) {
- # current
- push @event, "3:current$channel:" . sprintf "$scaleDecimals", $meterReading;
- push @event, "3:state:" . sprintf "$scaleDecimals", $meterReading;
- } else {
- # electric charge
- push @event, "3:electricChange$channel:" . sprintf "$scaleDecimals", $meterReading;
- }
- } elsif ($st eq "environmentApp") {
- # Environmental Applications (EEP A5-13-01 ... EEP A5-13-06, EEP A5-13-10)
- # [Eltako FWS61]
- # $db[0]_bit_7 ... $db[0]_bit_4 is the Identifier
- my $identifier = $db[0] >> 4;
- if ($identifier == 1) {
- # Weather Station (EEP A5-13-01)
- # $db[3] is the dawn sensor where 0x00 = 0 lx ... 0xFF = 999 lx
- # $db[2] is the temperature where 0x00 = -40 °C ... 0xFF = 80 °C
- # $db[1] is the wind speed where 0x00 = 0 m/s ... 0xFF = 70 m/s
- # $db[0]_bit_2 is day / night where 0 = day, 1 = night
- # $db[0]_bit_1 is rain indication where 0 = no (no rain), 1 = yes (rain)
- my $dawn = sprintf "%d", $db[3] * 999 / 255;
- my $temp = sprintf "%0.1f", -40 + $db[2] * 120 / 255;
- my $windSpeed = sprintf "%0.1f", $db[1] * 70 / 255;
- my $dayNight = $db[0] & 4 ? "night" : "day";
- my $isRaining = $db[0] & 2 ? "yes" : "no";
- push @event, "3:brightness:$dawn";
- push @event, "3:dayNight:$dayNight";
- push @event, "3:isRaining:$isRaining";
- push @event, "3:temperature:$temp";
- push @event, "3:windSpeed:$windSpeed";
- push @event, "3:state:T: $temp B: $dawn W: $windSpeed IR: $isRaining";
- } elsif ($identifier == 2) {
- # Sun Intensity (EEP A5-13-02)
- # $db[3] is the sun exposure west where 0x00 = 1 lx ... 0xFF = 150 klx
- # $db[2] is the sun exposure south where 0x00 = 1 lx ... 0xFF = 150 klx
- # $db[1] is the sun exposure east where 0x00 = 1 lx ... 0xFF = 150 klx
- # $db[0]_bit_2 is hemisphere where 0 = north, 1 = south
- push @event, "3:hemisphere:" . ($db[0] & 4 ? "south" : "north");
- push @event, "3:sunWest:" . sprintf "%d", 1 + $db[3] * 149999 / 255;
- push @event, "3:sunSouth:" . sprintf "%d", 1 + $db[2] * 149999 / 255;
- push @event, "3:sunEast:" . sprintf "%d", 1 + $db[1] * 149999 / 255;
- } elsif ($identifier == 3) {
- # Date exchange (EEP A5-13-03)
- push @event, "3:date:" . ($db[1] + 2000) . '-' . $db[2] . '-' . $db[3];
- } elsif ($identifier == 4) {
- # Time und Day exchange (EEP A5-13-04)
- my @day = ('', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday');
- my $day = ($db[3] & 0xF0) >> 4;
- push @event, "3:weekday:" . $day[$day];
- if ($db[0] & 4) {
- # 12 h time format
- push @event, "3:time:" . ($db[3] & 0x0F) . ':' . $db[2] . ':' . $db[1] . ' ' . ($db[1] & 2 ? 'PM' : 'AM');
- } else {
- push @event, "3:time:" . ($db[3] & 0x0F) . ':' . $db[2] . ':' . $db[1];
- }
- push @event, "3:timeSource:" . ($db[0] & 1 ? 'GPS' : 'RTC');
- } elsif ($identifier == 5) {
- # Direction exchange (EEP A5-13-05)
- push @event, "3:elevation:" . ($db[3] - 90);
- push @event, "3:azimuth:" . hex(substr($data, 2, 4));
- } elsif ($identifier == 6) {
- # Geographic Position exchange (EEP A5-13-06)
- push @event, "3:latitude:" . sprintf("%0.3f", hex(substr($data, 0, 1) . substr($data, 2, 2)) / 22.75 - 90);
- push @event, "3:longitude:" . sprintf("%0.3f", hex(substr($data, 1, 1) . substr($data, 4, 2)) / 13.375 - 180);
- } elsif ($identifier == 7) {
- # Sun Position and Radiation (EEP A5-13-10)
- # $db[3]_bit_7 ... $db[3]_bit_1 is Sun Elevation where 0 = 0 ° ... 90 = 90 °
- # $db[3]_bit_0 is day / night where 0 = day, 1 = night
- # $db[2] is Sun Azimuth where 0 = -90 ° ... 180 = 90 °
- # $db[1] and $db[0]_bit_2 ... $db[0]_bit_0 is Solar Radiation where
- # 0 = 0 W/m2 ... 2000 = 2000 W/m2
- my $sunElev = $db[3] >> 1;
- my $sunAzim = $db[2] - 90;
- my $solarRad = $db[1] << 3 | $db[0] & 7;
- push @event, "3:dayNight:" . ($db[3] & 1 ? "night" : "day");
- push @event, "3:solarRadiation:$solarRad";
- push @event, "3:sunAzimuth:$sunAzim";
- push @event, "3:sunElevation:$sunElev";
- push @event, "3:state:SRA: $solarRad SNA: $sunAzim SNE: $sunElev";
- } else {
- # EEP A5-13-03 ... EEP A5-13-06 not implemented
- }
- } elsif ($st eq "windSensor.01") {
- # Wind Sensor (EEP A5-13-07)
- my @windDirection = ('NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N');
- push @event, "3:battery:" . ($db[0] & 1 ? 'low' : 'ok');
- push @event, "3:windDirection:" . $windDirection[$db[3]];
- push @event, "3:windSpeedAverage:" . $db[2] / 1.275;
- push @event, "3:windSpeedMax:" . $db[1] / 1.275;
- push @event, "3:state:" . $db[2] / 1.275;
- } elsif ($st eq "rainSensor.01") {
- # Rain Sensor (EEP A5-13-08)
- my $ras = ($db[3] & 0x40) >> 6;
- my $rfa = ($db[3] & 0x3F) / 10;
- my $rfc = hex(substr($data, 2, 4));
- my $rain = $rfc * 0.6875 * (1 + $ras * $rfa / 100);
- push @event, "3:battery:" . ($db[0] & 1 ? 'low' : 'ok');
- push @event, "3:rain:$rain";
- push @event, "3:state:$rain";
- } elsif ($st eq "multiFuncSensor") {
- # Multi-Func Sensor (EEP A5-14-01 ... A5-14-06)
- # $db[3] is the voltage where 0x00 = 0 V ... 0xFA = 5.0 V
- # $db[3] > 0xFA is error code
- # $db[2] is the illuminance where min 0x00 = 0 lx, max 0xFA = 1000 lx
- # $db[0]_bit_1 is Vibration where 0 = off, 1 = on
- # $db[0]_bit_0 is Contact where 0 = closed, 1 = open
- my $lux = $db[2] << 2;
- if ($db[2] == 251) {$lux = "over range";}
- my $voltage = sprintf "%0.1f", $db[3] * 0.02;
- if ($db[3] > 250) {push @event, "3:errorCode:$db[3]";}
- my $vibration = $db[0] & 2 ? "on" : "off";
- my $contact = $db[0] & 1 ? "open" : "closed";
- push @event, "3:brightness:$lux";
- push @event, "3:contact:$contact";
- push @event, "3:vibration:$vibration";
- push @event, "3:voltage:$voltage";
- push @event, "3:state:C: $contact V: $vibration E: $lux U: $voltage";
- } elsif ($st eq "doorContact") {
- # dual door contact (EEP A5-14-07, A5-14-08)
- if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} ne $data) {
- my $voltage = sprintf "%0.1f", $db[3] * 0.02;
- my $doorContact = $db[0] & 4 ? 'open' : 'closed';
- my $lockContact = $db[0] & 2 ? 'unlocked' : 'locked';
- my $vibration = $db[0] & 1 ? 'on' : 'off';
- push @event, "3:voltage:$voltage";
- push @event, "3:contact:$doorContact";
- push @event, "3:block:$lockContact";
- push @event, "3:vibration:$vibration";
- push @event, "3:state:C: $doorContact B: $lockContact V: $vibration U: $voltage";
- $hash->{helper}{lastEvent} = $data;
- }
- CommandDeleteReading(undef, "$name alarm");
- @{$hash->{helper}{alarmTimer}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
- RemoveInternalTimer($hash->{helper}{alarmTimer});
- InternalTimer(gettimeofday() + 66, 'EnOcean_readingsSingleUpdate', $hash->{helper}{alarmTimer}, 0);
- } elsif ($st eq "windowContact") {
- # window contact (EEP A5-14-09, A5-14-0A)
- if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} ne $data) {
- my $voltage = sprintf "%0.1f", $db[3] * 0.02;
- my %window = (0 => 'closed', 1 => 'tilt', 2 => 'reserved', 3 => 'open');
- my $window = $window{(($db[0] & 6) >> 1)};
- my $vibration = $db[0] & 1 ? 'on' : 'off';
- push @event, "3:voltage:$voltage";
- push @event, "3:window:$window";
- push @event, "3:vibration:$vibration";
- push @event, "3:state:W: $window V: $vibration U: $voltage";
- $hash->{helper}{lastEvent} = $data;
- }
- CommandDeleteReading(undef, "$name alarm");
- @{$hash->{helper}{alarmTimer}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
- RemoveInternalTimer($hash->{helper}{alarmTimer});
- InternalTimer(gettimeofday() + 66, 'EnOcean_readingsSingleUpdate', $hash->{helper}{alarmTimer}, 0);
- } elsif ($st =~ m/^digitalInput\.0[12]$/) {
- # Digital Input (EEP A5-30-01, A5-30-02)
- my $contact;
- if ($st eq "digitalInput.01") {
- # Single Input Contact, Batterie Monitor (EEP A5-30-01)
- # [Thermokon SR65 DI, untested]
- # $db[2] is the supply voltage, if >= 121 = battery ok
- # $db[1] is the input state, if <= 195 = contact closed
- my $battery = $db[2] >= 121 ? "ok" : "low";
- $contact = $db[1] <= 195 ? "closed" : "open";
- push @event, "3:battery:$battery";
- } else {
- # Single Input Contact (EEP A5-30-02)
- # $db[0]_bit_0 is the input state where 0 = closed, 1 = open
- $contact = $db[0] & 1 ? "open" : "closed";
- }
- push @event, "3:contact:$contact";
- push @event, "3:state:$contact";
- } elsif ($st eq "digitalInput.03") {
- # 4 digital inputs, wake, temperature (EEP A5-30-03)
- my $in0 = $db[1] & 1;
- my $in1 = ($db[1] & 2) > 1;
- my $in2 = ($db[1] & 4) > 2;
- my $in3 = ($db[1] & 8) > 3;
- my $wake = $db[1] & 16 ? 'high' : 'low';
- my $temperature = sprintf "%0.1f", 40 - $db[2] * 40 / 255;
- push @event, "3:in0:$in0";
- push @event, "3:in1:$in1";
- push @event, "3:in2:$in2";
- push @event, "3:in3:$in3";
- push @event, "3:wake:$wake";
- push @event, "3:temperature:$temperature";
- push @event, "3:state:T: $temperature I: " . $in0 . $in1 . $in2 . $in3 . " W: " . $wake;
- } elsif ($st eq "digitalInput.04") {
- # 3 digital inputs, 1 digital input 8 bit (EEP A5-30-04)
- my $in0 = $db[0] & 1;
- my $in1 = ($db[0] & 2) > 1;
- my $in2 = ($db[0] & 4) > 2;
- my $in3 = $db[1];
- push @event, "3:in0:$in0";
- push @event, "3:in1:$in1";
- push @event, "3:in2:$in2";
- push @event, "3:in3:$in3";
- push @event, "3:state:" . $in0 . $in1 . $in2 . " " . $in3;
- } elsif ($st eq "digitalInput.05") {
- # single input contact, retransmission, battery monitor (EEP A5-30-05)
- my $signalIdx = $db[1] & 0x7F;
- my $signalIdxLast = ReadingsVal($name, "signalIdx", undef);
- my $signalType = $db[1] & 0x80 ? "heartbeat" : "event";
- push @event, "3:voltage:" . sprintf "%0.1f", $db[2] / 255 * 3.3;
- push @event, "3:signalIdx:$signalIdx";
- push @event, "3:telegramType:$signalType";
- if (defined $signalIdxLast) {
- if ($signalIdx == $signalIdxLast + 1 || $signalIdx == 0 && $signalIdxLast == 127) {
- push @event, "3:state:$signalType";
- } else {
- push @event, "3:state:error";
- }
- } else {
- push @event, "3:state:$signalType";
- }
- } elsif ($st eq "gateway") {
- # Gateway (EEP A5-38-08)
- # $db[3] is the command ID ($gwCmdID)
- # Eltako devices not send teach-in telegrams
- if(($db[0] & 8) == 0) {
- # teach-in, identify and store command type in attr gwCmd
- my $gwCmd = AttrVal($name, "gwCmd", undef);
- if (!$gwCmd) {
- $gwCmd = $EnO_gwCmd[$db[3] - 1];
- $attr{$name}{gwCmd} = $gwCmd;
- }
- }
- if ($db[3] == 1) {
- # Switching
- # Eltako devices not send A5 telegrams
- push @event, "3:executeTime:" . sprintf "%0.1f", (($db[2] << 8) | $db[1]) / 10;
- push @event, "3:block:" . ($db[0] & 4 ? "lock" : "unlock");
- push @event, "3:executeType" . ($db[0] & 2 ? "delay" : "duration");
- push @event, "3:state:" . ($db[0] & 1 ? "on" : "off");
- } elsif ($db[3] == 2) {
- # Dimming
- # $db[0]_bit_2 is store final value, not used, because
- # dimming value is always stored
- push @event, "3:rampTime:$db[1]";
- push @event, "3:state:" . ($db[0] & 0x01 ? "on" : "off");
- if ($db[0] & 4) {
- # Relative Dimming Range
- push @event, "3:dim:" . sprintf "%d", $db[2] * 100 / 255;
- } else {
- push @event, "3:dim:$db[2]";
- }
- push @event, "3:dimValueLast:$db[2]" if ($db[2] > 0);
- } elsif ($db[3] == 3) {
- # Setpoint shift
- # $db1 is setpoint shift where 0 = -12.7 K ... 255 = 12.8 K
- my $setpointShift = sprintf "%0.1f", -12.7 + $db[1] / 10;
- push @event, "3:setpointShift:$setpointShift";
- push @event, "3:state:$setpointShift";
- } elsif ($db[3] == 4) {
- # Basic Setpoint
- # $db1 is setpoint where 0 = 0 °C ... 255 = 51.2 °C
- my $setpoint = sprintf "%0.1f", $db[1] / 5;
- push @event, "3:setpoint:$setpoint";
- push @event, "3:state:$setpoint";
- } elsif ($db[3] == 5) {
- # Control variable
- # $db1 is control variable override where 0 = 0 % ... 255 = 100 %
- push @event, "3:controlVar:" . sprintf "%d", $db[1] * 100 / 255;
- my $controllerMode = ($db[0] & 0x60) >> 5;
- if ($controllerMode == 0) {
- push @event, "3:controllerMode:auto";
- push @event, "3:state:auto";
- } elsif ($controllerMode == 1) {
- push @event, "3:controllerMode:heating";
- push @event, "3:state:heating";
- } elsif ($controllerMode == 2){
- push @event, "3:controllerMode:cooling";
- push @event, "3:state:cooling";
- } elsif ($controllerMode == 3){
- push @event, "3:controllerMode:off";
- push @event, "3:state:off";
- }
- push @event, "3:controllerState:" . ($db[0] & 0x10 ? "override" : "auto");
- push @event, "3:energyHoldOff:" . ($db[0] & 4 ? "holdoff" : "normal");
- my $occupancy = $db[0] & 3;
- if ($occupancy == 0) {
- push @event, "3:presence:present";
- } elsif ($occupancy == 1){
- push @event, "3:presence:absent";
- } elsif ($occupancy == 2){
- push @event, "3:presence:standby";
- }
- } elsif ($db[3] == 6) {
- # Fan stage
- if ($db[1] == 0) {
- push @event, "3:fan:0";
- push @event, "3:state:0";
- } elsif ($db[1] == 1) {
- push @event, "3:fan:1";
- push @event, "3:state:1";
- } elsif ($db[1] == 2) {
- push @event, "3:fan:2";
- push @event, "3:state:2";
- } elsif ($db[1] == 3) {
- push @event, "3:fan:3";
- push @event, "3:state:3";
- } elsif ($db[1] == 255) {
- push @event, "3:fan:auto";
- push @event, "3:state:auto";
- }
- } else {
- push @event, "3:state:Gateway Command ID $db[3] unknown.";
- }
- } elsif ($st eq "energyManagement.01") {
- # Energy Management, Demand Response
- # (A5-37-01)
- EnOcean_energyManagement_01Parse($hash, @db);
- } elsif ($st eq "manufProfile") {
- # Manufacturer Specific Applications (EEP A5-3F-7F)
- if ($manufID eq "002") {
- # [Thermokon SR65 3AI, untested]
- # $db[3] is the input 3 where 0x00 = 0 V ... 0xFF = 10 V
- # $db[2] is the input 2 where 0x00 = 0 V ... 0xFF = 10 V
- # $db[1] is the input 1 where 0x00 = 0 V ... 0xFF = 10 V
- my $input3 = sprintf "%0.2f", $db[3] * 10 / 255;
- my $input2 = sprintf "%0.2f", $db[2] * 10 / 255;
- my $input1 = sprintf "%0.2f", $db[1] * 10 / 255;
- push @event, "3:input1:$input1";
- push @event, "3:input2:$input2";
- push @event, "3:input3:$input3";
- push @event, "3:state:I1: $input1 I2: $input2 I3: $input3";
- } elsif ($manufID eq "005") {
- # omnio
- if ($db[0] == 0x0C) {
- # omnio UPH 2330/1x
- my $channel = ($db[1] & 0xF0) > 4;
- my $emergencyMode = $db[1] & 8 ? "on" : "off";
- my $window = $db[1] & 4 ? "open" : "closed";
- my $nightReduction = $db[1] & 2 ? "on" : "off";
- my $state = $db[1] & 1 ? "on" : "off";
- my $temperature = sprintf "%0.1f", $db[2] * 40 / 250;
- my $setpointTemp = sprintf "%0.1f", $db[3] * 40 / 250;
- push @event, "3:emergencyMode" . $channel . ":$emergencyMode";
- push @event, "3:window" . $channel . ":$window";
- push @event, "3:nightReduction" . $channel . ":$nightReduction";
- push @event, "3:temperature" . $channel . ":$temperature";
- push @event, "3:setpointTemp" . $channel . ":$setpointTemp";
- push @event, "3:state:$state";
- }
- } elsif ($manufID eq "00D") {
- # [Eltako shutter]
- my $angleMax = AttrVal($name, "angleMax", 90);
- my $angleMin = AttrVal($name, "angleMin", -90);
- my $anglePos = ReadingsVal($name, ".anglePosStart", undef);
- my $angleTime = AttrVal($name, "angleTime", 0);
- my $position = ReadingsVal($name, ".positionStart", undef);
- my $shutTime = AttrVal($name, "shutTime", 255);
- my $shutTimeStop = ($db[3] << 8 | $db[2]) * 0.1;
- my $state;
- $angleMax = 90 if ($angleMax !~ m/^[+-]?\d+$/);
- $angleMax = 180 if ($angleMax > 180);
- $angleMax = -180 if ($angleMax < -180);
- $angleMin = -90 if ($angleMin !~ m/^[+-]?\d+$/);
- $angleMin = 180 if ($angleMin > 180);
- $angleMin = -180 if ($angleMin < -180);
- ($angleMax, $angleMin) = ($angleMin, $angleMax) if ($angleMin > $angleMax);
- $angleMax ++ if ($angleMin == $angleMax);
- $angleTime = 0 if ($angleTime !~ m/^[+-]?\d+$/);
- $angleTime = 6 if ($angleTime > 6);
- $angleTime = 0 if ($angleTime < 0);
- $shutTime = 255 if ($shutTime !~ m/^[+-]?\d+$/);
- $shutTime = 255 if ($shutTime > 255);
- $shutTime = 1 if ($shutTime < 1);
- if ($db[0] == 0x0A) {
- push @event, "3:block:unlock";
- } elsif ($db[0] == 0x0E) {
- push @event, "3:block:lock";
- }
- if (defined $position) {
- if ($db[1] == 1) {
- # up
- $position -= $shutTimeStop / $shutTime * 100;
- if ($angleTime) {
- $anglePos -= ($angleMax - $angleMin) * $shutTimeStop / $angleTime;
- if ($anglePos < $angleMin) {
- $anglePos = $angleMin;
- }
- } else {
- $anglePos = $angleMin;
- }
- if ($position <= 0) {
- $anglePos = 0;
- $position = 0;
- push @event, "3:endPosition:open";
- $state = "open";
- } else {
- push @event, "3:endPosition:not_reached";
- $state = "stop";
- }
- push @event, "3:anglePos:" . sprintf("%d", $anglePos);
- push @event, "3:position:" . sprintf("%d", $position);
- push @event, "3:.anglePosStart:" . sprintf("%d", $anglePos);
- push @event, "3:.positionStart:" . sprintf("%d", $position);
- } elsif ($db[1] == 2) {
- # down
- $position += $shutTimeStop / $shutTime * 100;
- if ($angleTime) {
- $anglePos += ($angleMax - $angleMin) * $shutTimeStop / $angleTime;
- if ($anglePos > $angleMax) {
- $anglePos = $angleMax;
- }
- } else {
- $anglePos = $angleMax;
- }
- if($position > 100) {
- $anglePos = $angleMax;
- $position = 100;
- push @event, "3:endPosition:closed";
- $state = "closed";
- } else {
- push @event, "3:endPosition:not_reached";
- $state = "stop";
- }
- push @event, "3:anglePos:" . sprintf("%d", $anglePos);
- push @event, "3:position:" . sprintf("%d", $position);
- push @event, "3:.anglePosStart:" . sprintf("%d", $anglePos);
- push @event, "3:.positionStart:" . sprintf("%d", $position);
- } else {
- $state = "not_reached";
- }
- push @event, "3:state:$state";
- }
- } else {
- # Unknown Application
- push @event, "3:state:Manufacturer Specific Application unknown";
- }
- } elsif ($st eq "PM101") {
- # Light and Presence Sensor [Omnio Ratio eagle-PM101]
- # The sensor also sends switching commands (RORG F6) with the senderID-1
- # $db[2] is the illuminance where 0x00 = 0 lx ... 0xFF = 1000 lx
- my $channel2 = $db[0] & 2 ? "on" : "off";
- push @event, "3:brightness:" . ($db[2] << 2);
- push @event, "3:channel1:" . ($db[0] & 1 ? "on" : "off");
- push @event, "3:channel2:" . $channel2;
- push @event, "3:motion:" . $channel2;
- push @event, "3:state:" . $channel2;
- } elsif ($st eq "radioLinkTest") {
- # Radio Link Test (EEP A5-3F-00)
- @{$hash->{helper}{rlt}{param}} = ('parse', $hash, $data, $subDef, 'master', $RSSI);
- EnOcean_RLT($hash->{helper}{rlt}{param});
- } elsif ($st eq "raw") {
- # raw
- push @event, "3:state:RORG: $rorg DATA: $data STATUS: $status ODATA: $odata";
- } else {
- # unknown devices
- push @event, "3:state:$db[3]";
- push @event, "3:sensor1:$db[3]";
- push @event, "3:sensor2:$db[2]";
- push @event, "3:sensor3:$db[1]";
- push @event, "3:D3:" . ($db[0] & 8 ? 1 : 0);
- push @event, "3:D2:" . ($db[0] & 4 ? 1 : 0);
- push @event, "3:D1:" . ($db[0] & 2 ? 1 : 0);
- push @event, "3:D0:" . ($db[0] & 1 ? 1 : 0);
- }
- } elsif ($rorg eq "D2") {
- # VLD telegram
- if ($st eq "roomCtrlPanel.00") {
- # EEP D2-10-01 - D2-10-03
- my ($key, $val);
- # message identifier
- my $mid = hex(substr($data, 0, 2)) >> 5;
- # message continuation flag
- my $mcf = hex(substr($data, 0, 2)) & 3;
- my ($irc, $fbc, $gmt);
- if ($mcf == 0) {
- # message complete
- # read stored telegrams
- if (!defined($hash->{helper}{$mid}{messagePart})) {
- $hash->{helper}{$mid}{messagePart} = 1;
- $hash->{helper}{$mid}{data}{1} = $data;
- } else {
- $hash->{helper}{$mid}{messagePart} += 1;
- $hash->{helper}{$mid}{data}{$hash->{helper}{$mid}{messagePart}} = $data;
- }
- if ($mid == 4) {
- CommandDeleteAttr(undef, "$name timeProgram1");
- CommandDeleteAttr(undef, "$name timeProgram2");
- CommandDeleteAttr(undef, "$name timeProgram3");
- CommandDeleteAttr(undef, "$name timeProgram4");
- }
- for (my $partCntr = $hash->{helper}{$mid}{messagePart}; $partCntr > 0; $partCntr --) {
- $data = $hash->{helper}{$mid}{data}{$partCntr};
- delete $hash->{helper}{$mid}{data}{$partCntr};
- if ($partCntr == 1) {
- delete $hash->{helper}{$mid}{messagePart};
- } else {
- $hash->{helper}{$mid}{messagePart} --;
- }
- $dbCntr = 0;
- for (my $strCntr = length($data) / 2 - 1; $strCntr >= 0; $strCntr --) {
- $db[$dbCntr] = hex substr($data, $strCntr * 2, 2);
- $dbCntr++;
- }
- #Log3 $name, 2, "EnOcean $name EnOcean_Parse write MID $mid DATA $data to part $partCntr";
- #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];
- if ($mid == 0) {
- # general message
- $irc = ($db[0] & 56) >> 3;
- $fbc = ($db[0] & 6) >> 1;
- $gmt = $db[0] & 1;
- #push @event, "3:general:$data";
- } elsif ($mid == 1) {
- # data message
- my $temperature = "-";
- $temperature = sprintf "%.1f", $db[0] / 254 * 40 if ($db[2] & 1);
- push @event, "3:temperature:$temperature";
- my $setpointTemp = "-";
- $setpointTemp = sprintf "%.1f", $db[1] / 254 * 40 if ($db[2] & 2);
- push @event, "3:setpointTemp:$setpointTemp";
- my $roomCtrlMode = ($db[2] & 12) >> 2;
- if ($roomCtrlMode == 3) {
- $roomCtrlMode = "buildingProtection";
- } elsif ($roomCtrlMode == 2) {
- $roomCtrlMode = "preComfort";
- } elsif ($roomCtrlMode == 1) {
- $roomCtrlMode = "economy";
- } else{
- $roomCtrlMode = "comfort";
- }
- push @event, "3:roomCtrlMode:$roomCtrlMode";
- my $heating = ($db[2] & 48) >> 4;
- if ($heating == 3) {
- $heating = "auto";
- } elsif ($heating == 2) {
- $heating = "off";
- } elsif ($heating == 1) {
- $heating = "on";
- } else{
- $heating = "-";
- }
- if ($heating ne "-") {
- push @event, "3:heating:$heating";
- }
- my $cooling = ($db[2] & 192) >> 6;
- if ($cooling == 3) {
- $cooling = "auto";
- } elsif ($cooling == 2) {
- $cooling = "off";
- } elsif ($cooling == 1) {
- $cooling = "on";
- } else{
- $cooling = "-";
- }
- if ($cooling ne "-") {
- push @event, "3:cooling:$cooling";
- }
- my $occupancy = $db[3] & 3;
- if ($occupancy == 3) {
- $occupancy = "reserved";
- } elsif ($occupancy == 2) {
- $occupancy = "absent";
- } elsif ($occupancy == 1) {
- $occupancy = "present";
- } else{
- $occupancy = "-";
- }
- if ($occupancy eq "-") {
- $occupancy = ReadingsVal($name, "occupancy", "-");
- } else {
- push @event, "3:occupancy:$occupancy";
- }
- my $motion = ($db[3] & 12) >> 2;
- if ($motion == 3) {
- $motion = "reserved";
- } elsif ($motion == 2) {
- $motion = "on";
- } elsif ($motion == 1) {
- $motion = "off";
- } else{
- $motion = "-";
- }
- if ($motion eq "-") {
- $motion = ReadingsVal($name, "motion", "-");
- } else {
- push @event, "3:motion:$motion";
- }
- push @event, "3:solarPowered:" . ($db[3] & 16 ? "no" : "yes");
- my $battery = ($db[3] & 96) >> 5;
- if ($battery == 3) {
- $battery = "empty";
- } elsif ($battery == 2) {
- $battery = "low";
- } elsif ($battery == 1) {
- $battery = "ok";
- } else{
- $battery = "-";
- }
- if ($battery ne "-") {
- push @event, "3:battery:$battery";
- }
- my $window = $db[4] & 3;
- if ($window == 3) {
- $window = "reserved";
- } elsif ($window == 2) {
- $window = "open";
- } elsif ($window == 1) {
- $window = "closed";
- } else{
- $window = "-";
- }
- if ($window ne "-") {
- push @event, "3:window:$window";
- }
- push @event, "3:moldWarning:" . ($db[4] & 4 ? "on" : "off");
- push @event, "3:customWarning1:" . ($db[4] & 8 ? "on" : "off");
- push @event, "3:customWarning2:" . ($db[4] & 16 ? "on" : "off");
- push @event, "3:fanSpeedMode:" . ($db[4] & 64 ? "local" : "central");
- my $fanSpeed = 0;
- $fanSpeed = sprintf "%d", $db[5] & 127 if ($db[4] & 128);
- push @event, "3:fanSpeed:$fanSpeed";
- my $humi = "-";
- $humi = sprintf "%d", $db[6] / 2.55 if ($db[5] & 128);
- push @event, "3:humidity:$humi";
- push @event, "3:state:T: $temperature H: $humi F: $fanSpeed SPT: $setpointTemp O: $occupancy M: $motion";
- } elsif ($mid == 2) {
- # configuration message
- $attr{$name}{blockFanSpeed} = $db[6] & 1 ? "no" : "yes";
- $attr{$name}{blockSetpointTemp} = $db[6] & 2 ? "no" : "yes";
- $attr{$name}{blockOccupancy} = $db[6] & 4 ? "no" : "yes";
- $attr{$name}{blockTimeProgram} = $db[6] & 8 ? "no" : "yes";
- $attr{$name}{blockDateTime} = $db[6] & 16 ? "no" : "yes";
- $attr{$name}{blockDisplay} = $db[6] & 32 ? "no" : "yes";
- $attr{$name}{blockTemp} = $db[6] & 64 ? "no" : "yes";
- $attr{$name}{blockMotion} = $db[6] & 128 ? "no" : "yes";
- my $pollInterval = $db[5] >> 2;
- if ($pollInterval == 63) {
- $attr{$name}{pollInterval} = 1440;
- } elsif ($pollInterval == 62) {
- $attr{$name}{pollInterval} = 720;
- } elsif ($pollInterval == 61) {
- $attr{$name}{pollInterval} = 180;
- } else {
- $attr{$name}{pollInterval} = $pollInterval;
- }
- $attr{$name}{blockKey} = $db[5] & 2 ? "no" : "yes";
- my $displayContent = $db[4] >> 5;
- my %displayContent = (7 => "humidity",
- 6 => "off",
- 5 => "setpointTemp",
- 4 => "tempertureExtern",
- 3 => "temperatureIntern",
- 2 => "time",
- 1 => "default",
- 0 => "no_change"
- );
- while (($key, $val) = each(%displayContent)) {
- $attr{$name}{displayContent} = $val if ($key == $displayContent);
- }
- my $temperatureScale = ($db[4] & 24) >> 3;
- my %temperatureScale = (3 => "F",
- 2 => "C",
- 1 => "default",
- 0 => "no_change"
- );
- while (($key, $val) = each(%temperatureScale)) {
- $attr{$name}{temperatureScale} = $val if ($key == $temperatureScale);
- }
- $attr{$name}{daylightSavingTime} = $db[4] & 4 ? "not_supported" : "supported";
- my $timeNotation = $db[4] & 3;
- if ($timeNotation == 0) {
- $attr{$name}{timeNotation} = "no_change";
- } elsif ($timeNotation == 1) {
- $attr{$name}{timeNotation} = "default";
- } elsif ($timeNotation == 2) {
- $attr{$name}{timeNotation} = 24;
- } elsif ($timeNotation == 3) {
- $attr{$name}{timeNotation} = 12;
- }
- EnOcean_CommandSave(undef, undef);
- } elsif ($mid == 3) {
- # room control setup
- my $setpointComfort = "-";
- $setpointComfort = sprintf "%.1f", $db[1] / 254 * 40 if ($db[0] & 1);
- push @event, "3:setpointComfortTemp:$setpointComfort";
- my $setpointEconomy = "-";
- $setpointEconomy = sprintf "%.1f", $db[2] / 254 * 40 if ($db[0] & 2);
- push @event, "3:setpointEconomyTemp:$setpointEconomy";
- my $setpointPreComfort = "-";
- $setpointPreComfort = sprintf "%.1f", $db[3] / 254 * 40 if ($db[0] & 4);
- push @event, "3:setpointPreComfortTemp:$setpointPreComfort";
- my $setpointBuildingProtection = "-";
- $setpointBuildingProtection = sprintf "%.1f", $db[3] / 254 * 40 if ($db[0] & 8);
- push @event, "3:setpointBuildingProtectionTemp:$setpointBuildingProtection";
- } elsif ($mid == 4) {
- # time program setup
- my $timeProgram = "timeProgram" . $partCntr;
- my $period = $db[0] >> 4;
- my $periodVal = "";
- my %period = (15 => "FrMo",
- 14 => "FrSu",
- 13 => "ThFr",
- 12 => "WeFr",
- 11 => "TuTh",
- 10 => "MoWe",
- 9 => "Su",
- 8 => "Sa",
- 7 => "Fr",
- 6 => "Th",
- 5 => "We",
- 4 => "Tu",
- 3 => "Mo",
- 2 => "SaSu",
- 1 => "MoFr",
- 0 => "MoSu"
- );
- while (($key, $val) = each(%period)) {
- $periodVal = $val if ($key == $period);
- }
- my $roomCtrlMode = ($db[0] & 12) >> 2;
- if ($roomCtrlMode == 3) {
- $roomCtrlMode = "buildingProtection";
- } elsif ($roomCtrlMode == 2) {
- $roomCtrlMode = "preComfort";
- } elsif ($roomCtrlMode == 1) {
- $roomCtrlMode = "economy";
- } else{
- $roomCtrlMode = "comfort";
- }
- my ($startHour, $startMinute, $endHour, $endMinute) = ($db[1], $db[2], $db[3], $db[4]);
- $startHour = $startHour < 10 ? $startHour = "0" . $startHour : $startHour;
- $startMinute = $startMinute < 10 ? $startMinute = "0" . $startMinute : $startMinute;
- $endHour = $endHour < 10 ? $endHour = "0" . $endHour : $endHour;
- $endMinute = $endMinute < 10 ? $endMinute = "0" . $endMinute : $endMinute;
- #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];
- #Log3 $name, 2, "EnOcean $name EnOcean_Parse write 3 MID $mid DATA $data to $timeProgram VAL: $periodVal $startHour:$startMinute $endHour:$endMinute $roomCtrlMode";
- $attr{$name}{$timeProgram} = "$periodVal $startHour:$startMinute $endHour:$endMinute $roomCtrlMode";
- #Log3 $name, 2, "EnOcean $name EnOcean_Parse write 4 MID $mid DATA $data to $timeProgram VAL: $attr{$name}{$timeProgram}";
- EnOcean_CommandSave(undef, undef);
- }
- }
- ($err, $response) = EnOcean_roomCtrlPanel_00Snd(undef, $hash, $packetType, $mid, $mcf, $irc, $fbc, $gmt);
- } elsif ($mcf == 1) {
- # message incomplete
- if (!defined($hash->{helper}{$mid}{messagePart})) {
- $hash->{helper}{$mid}{messagePart} = 1;
- } elsif ($hash->{helper}{$mid}{messagePart} >= 4) {
- # max 4 message parts stored
- for (my $partCntr = 1; $partCntr < $hash->{helper}{$mid}{messagePart}; $partCntr ++) {
- $hash->{helper}{$mid}{data}{$partCntr} = $hash->{helper}{$mid}{data}{$partCntr + 1};
- }
- } else {
- $hash->{helper}{$mid}{messagePart} += 1;
- }
- $hash->{helper}{$mid}{data}{$hash->{helper}{$mid}{messagePart}} = $data;
- #Log3 $name, 2, "EnOcean $name EnOcean_Parse store MID $mid DATA $data to messagePart $hash->{helper}{$mid}{messagePart}";
- ($err, $response) = EnOcean_roomCtrlPanel_00Snd(undef, $hash, $packetType, $mid, $mcf, undef, undef, undef);
- }
- } elsif ($st eq "actuator.01") {
- # Electronic switches and dimmers with Energy Measurement and Local Control
- # (D2-01-00 - D2-01-12)
- my $channel = (hex substr($data, 2, 2)) & 0x1F;
- if ($channel == 31) {$channel = "Input";}
- my $cmd = hex substr($data, 1, 1);
- if ($cmd == 4) {
- # actuator status response
- my $overCurrentOff;
- my $error;
- my $localControl;
- my $dim;
- push @event, "3:powerFailure" . $channel . ":" . ($db[2] & 0x80 ? "enabled" : "disabled");
- push @event, "3:powerFailureDetection" . $channel . ":" . ($db[2] & 0x40 ? "detected" : "not_detected");
- if (($db[1] & 0x80) == 0) {
- $overCurrentOff = "ready";
- } else {
- $overCurrentOff = "executed";
- }
- push @event, "3:overCurrentOff" . $channel . ":" . $overCurrentOff;
- if ((($db[1] & 0x60) >> 5) == 1) {
- $error = "warning";
- } elsif ((($db[1] & 0x60) >> 5) == 2) {
- $error = "failure";
- } elsif ((($db[1] & 0x60) >> 5) == 3) {
- $error = "not_supported";
- } else {
- $error = "ok";
- }
- push @event, "3:error" . $channel . ":" . $error;
- if (($db[0] & 0x80) == 0) {
- $localControl = "disabled";
- } else {
- $localControl = "enabled";
- }
- push @event, "3:localControl" . $channel . ":" . $localControl;
- my $dimValue = $db[0] & 0x7F;
- if ($dimValue == 0) {
- push @event, "3:channel" . $channel . ":off";
- push @event, "3:state:off";
- } else {
- push @event, "3:channel" . $channel . ":on";
- push @event, "3:state:on";
- }
- if ($channel ne "Input") {
- push @event, "3:dim:" . $dimValue;
- push @event, "3:dim" . $channel . ":" . $dimValue;
- } else {
- push @event, "3:dim" . $channel . ":" . $dimValue;
- }
- } elsif ($cmd == 7) {
- # actuator measurement response
- my $unit = $db[4] >> 5;
- if ($unit == 1) {
- #$unit = "Wh";
- $unit = "KWh";
- push @event, "3:energyUnit" . $channel . ":" . $unit;
- push @event, "3:energy" . $channel . ":" . sprintf("%.3f", (hex substr($data, 4, 8)) / 1000);
- } elsif ($unit == 2) {
- $unit = "KWh";
- push @event, "3:energyUnit" . $channel . ":" . $unit;
- push @event, "3:energy" . $channel . ":" . hex substr($data, 4, 8);
- } elsif ($unit == 3) {
- $unit = "W";
- push @event, "3:powerUnit" . $channel . ":" . $unit;
- push @event, "3:power" . $channel . ":" . hex substr($data, 4, 8);
- } elsif ($unit == 4) {
- $unit = "KW";
- push @event, "3:powerUnit" . $channel . ":" . $unit;
- push @event, "3:power" . $channel . ":" . hex substr($data, 4, 8);
- } else {
- $unit = "Ws";
- push @event, "3:energyUnit" . $channel . ":" . $unit;
- push @event, "3:energy" . $channel . ":" . hex substr($data, 4, 8);
- }
- } elsif ($cmd == 10) {
- # pilot wire mode response
- my $roomCtrlMode = $db[0] & 3;
- my %roomCtrlMode = (
- 0 => "off",
- 1 => "comfort",
- 2 => "economy",
- 3 => "buildingProtection",
- 4 => "comfort-1",
- 5 => "comfort-2"
- );
- push @event, "3:roomCtrlMode" . $roomCtrlMode{$roomCtrlMode} if (exists $roomCtrlMode{$roomCtrlMode});
- } elsif ($cmd == 13) {
- # external interface settings
- push @event, "3:autoOffTime" . $channel . ":" . sprintf("%0.1f", hex(substr($data, 4, 4)) * 0.1);
- push @event, "3:delayOffTime" . $channel . ":" . sprintf("%0.1f", hex(substr($data, 8, 4)) * 0.1);
- my $extSwitchMode = ($db[0] & 0xC0) >> 6;
- my %extSwitchMode = (
- 0 => "unavailable",
- 1 => "switch",
- 2 => "pushbutton",
- 3 => "auto"
- );
- push @event, "3:extSwitchMode" . $extSwitchMode{$extSwitchMode} if (exists $extSwitchMode{$extSwitchMode});
- push @event, "3:extSwitchType" . ($db[0] & 0x20 ? 'direction' : 'toggle');
- } else {
- # unknown response
- }
- } elsif ($st eq "switch.00" || $st eq "windowHandle.10") {
- if ($db[0] == 1) {
- push @event, "3:state:open_from_tilted";
- } elsif ($db[0] == 2) {
- push @event, "3:state:closed";
- } elsif ($db[0] == 3) {
- push @event, "3:state:open";
- } elsif ($db[0] == 4) {
- push @event, "3:state:tilted";
- } elsif ($db[0] == 5) {
- push @event, "3:state:AI,B0";
- push @event, "3:channelA:AI";
- push @event, "3:channelB:B0";
- push @event, "3:energyBow:pressed";
- } elsif ($db[0] == 6) {
- CommandDeleteReading(undef, "$name channel.*");
- push @event, "3:state:pressed34";
- push @event, "3:energyBow:pressed";
- } elsif ($db[0] == 7) {
- push @event, "3:state:A0,B0";
- push @event, "3:channelA:A0";
- push @event, "3:channelB:B0";
- push @event, "3:energyBow:pressed";
- } elsif ($db[0] == 8) {
- if (AttrVal($name, "sensorMode", "switch") eq "pushbutton") {
- push @event, "3:state:pressed";
- }
- push @event, "3:energyBow:pressed";
- } elsif ($db[0] == 9) {
- push @event, "3:state:AI,BI";
- push @event, "3:channelA:AI";
- push @event, "3:channelB:BI";
- push @event, "3:energyBow:pressed";
- } elsif ($db[0] == 10) {
- push @event, "3:state:A0,BI";
- push @event, "3:channelA:A0";
- push @event, "3:channelB:BI";
- push @event, "3:energyBow:pressed";
- } elsif ($db[0] == 11) {
- push @event, "3:state:BI";
- push @event, "3:channelB:BI";
- push @event, "3:energyBow:pressed";
- } elsif ($db[0] == 12) {
- push @event, "3:state:B0";
- push @event, "3:channelB:B0";
- push @event, "3:energyBow:pressed";
- } elsif ($db[0] == 13) {
- push @event, "3:state:AI";
- push @event, "3:channelA:AI";
- push @event, "3:energyBow:pressed";
- } elsif ($db[0] == 14) {
- push @event, "3:state:A0";
- push @event, "3:channelA:A0";
- push @event, "3:energyBow:pressed";
- } elsif ($db[0] == 15) {
- if (AttrVal($name, "sensorMode", "switch") eq "pushbutton") {
- push @event, "3:state:released";
- }
- push @event, "3:energyBow:released";
- }
- } elsif ($st =~ m/^blindsCtrl\.0[01]$/) {
- # EEP D2-05-0x
- my $channel = (($db[0] & 0xF0) >> 4) + 1;
- my $cmd = $db[0] & 0x0F;
- if ($cmd == 4) {
- # actuator status response
- if ($db[3] == 0) {
- push @event, "3:state:open";
- push @event, "3:endPosition" . sprintf('%02d', $channel) . ":open";
- push @event, "3:position" . sprintf('%02d', $channel) . ":" . $db[3];
- push @event, "3:position:" . $db[3];
- } elsif ($db[3] == 100) {
- push @event, "3:state:closed";
- push @event, "3:endPosition" . sprintf('%02d', $channel) . ":closed";
- push @event, "3:position" . sprintf('%02d', $channel) . ":" . $db[3];
- push @event, "3:position:" . $db[3];
- } elsif ($db[3] == 127) {
- push @event, "3:state:unknown";
- push @event, "3:endPosition" . sprintf('%02d', $channel) . ":unknown";
- push @event, "3:position" . sprintf('%02d', $channel) . ":unknown";
- push @event, "3:position:" . $db[3];
- } else {
- push @event, "3:state:" . $db[3];
- push @event, "3:endPosition" . sprintf('%02d', $channel) . ":not_reached";
- push @event, "3:position" . sprintf('%02d', $channel) . ":" . $db[3];
- push @event, "3:position:" . $db[3];
- }
- if ($db[2] == 127) {
- push @event, "3:anglePos" . sprintf('%02d', $channel) . ":unknown";
- } else {
- push @event, "3:anglePos" . sprintf('%02d', $channel) . ":" . $db[2];
- push @event, "3:anglePos:" . $db[2];
- }
- if ($db[1] == 0) {
- push @event, "3:block" . sprintf('%02d', $channel) . ":unlock";
- } elsif ($db[1] == 1) {
- push @event, "3:block" . sprintf('%02d', $channel) . ":lock";
- } elsif ($db[1] == 2) {
- push @event, "3:block" . sprintf('%02d', $channel) . ":alarm";
- } else {
- push @event, "3:block" . sprintf('%02d', $channel) . ":reserved";
- }
- } else {
- # unknown response
- }
- } elsif ($st eq "multisensor.01") {
- #####
- # Multisensor Windows Handle
- # (D2-06-01)
- # message type
- my $msgType = hex(substr($data, 0, 2));
- my $blinkInterval = 0;
- my $updateInterval = 0;
- my $waitingCmds = ReadingsVal($name, "waitingCmds", undef);
- if ($msgType == 0) {
- # sensor values
- my %onOffTrigger = (
- 0 => "off",
- 1 => "on",
- 14 => "invalid",
- 15 => "not_supported",
- );
- my $onOffTrigger = $db[8] >> 4;
- if (exists $onOffTrigger{$onOffTrigger}) {
- push @event, "3:burglaryAlarm:$onOffTrigger{$onOffTrigger}";
- } else {
- push @event, "3:burglaryAlarm:unknown";
- }
- $onOffTrigger = $db[8] & 15;
- if (exists $onOffTrigger{$onOffTrigger}) {
- push @event, "3:protectionAlarm:$onOffTrigger{$onOffTrigger}";
- } else {
- push @event, "3:protectionAlarm:unknown";
- }
- my %handlePosition = (
- 0 => "unknown",
- 1 => "up",
- 2 => "down",
- 3 => "left",
- 4 => "right",
- 14 => "invalid",
- 15 => "not_supported"
- );
- my %handleRPS = (
- 1 => 'D0',
- 2 => 'F0',
- 3 => 'E0',
- 4 => 'E0'
- );
- my $handlePosition = $db[7] >> 4;
- if (exists $handlePosition{$handlePosition}) {
- push @event, "3:handle:$handlePosition{$handlePosition}";
- } else {
- push @event, "3:handle:unknown";
- }
- # forward handle position (RPS telegam)
- if (exists($handleRPS{$handlePosition}) && defined(AttrVal($name, "subDefH", undef))) {
- EnOcean_SndRadio(undef, $hash, $packetType, "F6", $handleRPS{$handlePosition}, AttrVal($name, "subDefH", "00000000"), '20', 'FFFFFFFF');
- }
- my %windowState = (
- 0 => "undef",
- 1 => "not_tilted",
- 2 => "tilted",
- 14 => "invalid",
- 15 => "not_supported"
- );
- my %window1BS = (
- 1 => '09',
- 2 => '08'
- );
- my $windowState = $db[7] & 15;
- if (exists $windowState{$windowState}) {
- push @event, "3:window:$windowState{$windowState}";
- } else {
- push @event, "3:window:unknown";
- }
- # forward window state (1BS telegam)
- if (exists($window1BS{$windowState}) && defined(AttrVal($name, "subDefW", undef))) {
- EnOcean_SndRadio(undef, $hash, $packetType, "D5", $window1BS{$windowState}, AttrVal($name, "subDefW", "00000000"), '00', 'FFFFFFFF');
- }
- my %button = (
- 0 => "no_change",
- 1 => "pressed",
- 2 => "released",
- 14 => "invalid",
- 15 => "not_supported"
- );
- my $button = $db[6] >> 4;
- if (exists $button{$button}) {
- if ($button == 0 && ReadingsVal($name, "buttonRight", 'unknown') eq 'unknown') {
- push @event, "3:buttonRight:unknown";
- } elsif ($button == 0) {
- } else {
- push @event, "3:buttonRight:$button{$button}";
- }
- } else {
- push @event, "3:buttonRight:unknown";
- }
- $button = $db[6] & 15;
- if (exists $button{$button}) {
- if ($button == 0 && ReadingsVal($name, "buttonLeft", 'unknown') eq 'unknown') {
- push @event, "3:buttonLeft:unknown";
- } elsif ($button == 0) {
- } else {
- push @event, "3:buttonLeft:$button{$button}";
- }
- } else {
- push @event, "3:buttonLeft:unknown";
- }
- my $motion = $db[5] >> 4;
- if (exists $onOffTrigger{$motion}) {
- push @event, "3:motion:$onOffTrigger{$motion}";
- } else {
- push @event, "3:motion:unknown";
- }
- my %vacation = (
- 0 => "no_change",
- 1 => "absent",
- 2 => "present",
- 14 => "invalid",
- 15 => "not_supported"
- );
- my $vacation = $db[5] & 15;
- if (exists $vacation{$vacation}) {
- if ($vacation == 0 && ReadingsVal($name, "presence", 'unknown') eq 'unknown') {
- push @event, "3:presence:unknown";
- } elsif ($vacation == 0) {
- } else {
- push @event, "3:presence:$vacation{$vacation}";
- }
- } else {
- push @event, "3:presence:unknown";
- }
- my $temperature;
- if ($db[4] <= 250) {
- $temperature = sprintf "%0.1f", $db[4] * 80 / 250 - 20;
- push @event, "3:temperature:$temperature";
- } elsif ($db[4] <= 254) {
- $temperature = '-';
- push @event, "3:temperature:invalid";
- } elsif ($db[4] <= 255) {
- $temperature = '-';
- push @event, "3:temperature:not_supported";
- } else {
- $temperature = '-';
- push @event, "3:temperature:unknown";
- }
- my $humidity;
- if ($db[3] <= 200) {
- $humidity = $db[3] / 2;
- push @event, "3:humidity:$humidity";
- } elsif ($db[3] <= 254) {
- $humidity = '-';
- push @event, "3:humidity:invalid";
- } elsif ($db[3] <= 255) {
- $humidity = '-';
- push @event, "3:humidity:not_supported";
- } else {
- $humidity = '-';
- push @event, "3:humidity:unknown";
- }
- my $brightness = hex(substr($data, 14, 4));
- if ($brightness <= 60000) {
- push @event, "3:brightness:$brightness";
- } elsif ($brightness == 60001) {
- $brightness = '-';
- push @event, "3:brightness:over_range";
- } elsif ($brightness == 65534) {
- $brightness = '-';
- push @event, "3:brightness:invalid";
- } elsif ($brightness == 65535) {
- $brightness = '-';
- push @event, "3:brightness:not_supported";
- } else {
- $brightness = '-';
- push @event, "3:brightness:unknown";
- }
- my $energyStorage = ($db[0] >> 3) * 5;
- my $battery;
- if ($energyStorage <= 5) {
- $battery = 'low';
- push @event, "3:battery:low";
- push @event, "3:energyStorage:$energyStorage";
- } elsif ($energyStorage <= 100) {
- $battery = 'ok';
- push @event, "3:battery:ok";
- push @event, "3:energyStorage:$energyStorage";
- } else {
- $battery = '-';
- push @event, "3:battery:-";
- $energyStorage = '-';
- push @event, "3:energyStorage:unknown";
- }
- push @event, "3:state:T: $temperature H: $humidity E: $brightness M: $onOffTrigger{$motion}";
- } elsif ($msgType == 0x10) {
- # configuration report
- if (defined $waitingCmds) {
- $waitingCmds = $db[3] & 0x80 ? $waitingCmds & 0xDF | 32 : $waitingCmds & 0xDF;
- }
- push @event, "3:presence:" . ($db[3] & 0x80 ? 'absent' : 'present');
- push @event, "3:handleClosedClick:" . ($db[3] & 0x40 ? 'enabled' : 'disabled');
- push @event, "3:batteryLowClick:" . ($db[3] & 0x20 ? 'enabled' : 'disabled');
- $updateInterval = hex(substr($data, 4, 4));
- if ($updateInterval <= 4) {
- $updateInterval = '-';
- push @event, "3:updateInterval:unknown";
- } else {
- push @event, "3:updateInterval:$updateInterval";
- }
- $blinkInterval = $db[0];
- if ($blinkInterval <= 2) {
- $blinkInterval = '-';
- push @event, "3:blinkInterval:unknown";
- } else {
- push @event, "3:blinkInterval:$blinkInterval";
- }
- } elsif ($msgType == 0x20) {
- # log data 01
- push @event, "3:powerOns:" . substr($data, 2, 8);
- push @event, "3:alarms:" . substr($data, 10, 8);
- } elsif ($msgType == 0x21) {
- # log data 02
- push @event, "3:handleMoveClosed:" . substr($data, 2, 8);
- push @event, "3:handleMoveOpend:" . substr($data, 10, 8);
- push @event, "3:handleMoveTilted:" . substr($data, 18, 8);
- } elsif ($msgType == 0x22) {
- # log data 03
- push @event, "3:windowTilts:" . substr($data, 2, 8);
- } elsif ($msgType == 0x23) {
- # log data 04
- push @event, "3:buttonRightPresses:" . substr($data, 2, 8);
- push @event, "3:buttonLeftPresses:" . substr($data, 10, 8);
- } else {
- }
- if (defined $waitingCmds) {
- $updateInterval = 0;
- $blinkInterval = 0;
- if ($waitingCmds & 1) {
- $waitingCmds &= 0xFE;
- $updateInterval = ReadingsVal($name, "updateIntervalSet", 0);
- $updateInterval = $updateInterval =~ m/^\d+$/ ? $updateInterval : 0;
- $blinkInterval = ReadingsVal($name, "blinkIntervalSet", 0);
- $blinkInterval = $blinkInterval =~ m/^\d+$/ ? $blinkInterval : 0;
- CommandDeleteReading(undef, "$name .*Set");
- }
- CommandDeleteReading(undef, "$name waitingCmds");
- $data = sprintf "80%02X%04X%02X", $waitingCmds, $updateInterval, $blinkInterval;
- EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- #EnOcean_multisensor_01Snd($crtl, $hash, $packetType);
- }
- } elsif ($st eq "roomCtrlPanel.01") {
- # Room Control Panel
- # (D2-11-01 - D2-11-08)
- my $msgType = hex(substr($data, 1, 1));
- my $setpointType = ReadingsVal($name, "setpointType", 'setpointShift');
- my $waitingCmds = ReadingsVal($name, "waitingCmds", 0);
- if (($waitingCmds & 0x80) == 0) {
- $setpointType = hex(substr($data, 0, 1)) & 8 ? 'setpointTemp' : 'setpointShift';
- push @event, "3:setpointType:$setpointType";
- }
- if ($msgType == 2) {
- my $trigger = ($db[5] & 0x60) >> 5;
- my %trigger = (0 => 'heartbeat', 1 => 'sensor', 2 => 'input');
- push @event, "3:trigger:" . $trigger{$trigger};
- my $temperature = sprintf "%0.1f", $db[4] / 255 * 40;
- push @event, "3:temperature:$temperature";
- my $humidity = sprintf "%d", $db[3] / 2.5;
- push @event, "3:humidity:$humidity";
- my $setpointShiftMax = ($db[0] & 0xF0) >> 4;
- push @event, "3:setpointShiftMax:$setpointShiftMax";
- my $setpointShift = int(0.5 + $db[2] * $setpointShiftMax / 128 * 10) / 10 - $setpointShiftMax;
- push @event, "3:setpointShift:" . sprintf "%0.1f", $setpointShift;
- push @event, "3:setpointBase:$db[1]";
- push @event, "3:setpointTemp:" . sprintf "%0.1f", ($db[1] + $setpointShift);
- my %fanSpeed = (0 => 'auto', 1 => 'off', 2 => 1, 3 => 2, 4 => 3);
- my $fanSpeed = ($db[0] & 0xE) >> 1;
- push @event, "3:fanSpeed:" . $fanSpeed{$fanSpeed};
- push @event, "3:occupancy:" . ($db[0] & 1 ? 'occupied' : 'unoccupied');
- push @event, "3:state:T: $temperature H: $humidity SPT: " . ($db[1] + $setpointShift) . " F: " . $fanSpeed{$fanSpeed};
- }
- CommandDeleteReading(undef, "$name waitingCmds");
- } elsif ($st eq "fanCtrl.00") {
- # Fan Control
- # (D2-20-00 - D2-20-02)
- if ($db[3] & 1) {
- # fan status message
- my $fanSpeed = $db[0];
- my $humidity = $db[1];
- my $roomSize = $db[2] & 15;
- my $roomSizeRef = ($db[2] & 0x30) >> 4;
- my $humidityCtrl = ($db[2] & 0xC0) >> 6;
- my $serviceInfo = ($db[3] & 14) >> 1;
- my $opMode = ($db[3] & 0xF0) >> 4;
- my %roomSizeTbl = (
- 0 => 25,
- 1 => 50,
- 2 => 75,
- 3 => 100,
- 4 => 125,
- 5 => 150,
- 6 => 175,
- 7 => 200,
- 8 => 225,
- 9 => 250,
- 10 => 275,
- 11 => 300,
- 12 => 325,
- 13 => 350,
- 14 => "max",
- 15 => "no_change"
- );
- if ($opMode == 0) {
- push @event, "3:state:off";
- } elsif ($opMode == 1) {
- push @event, "3:state:on";
- } elsif ($opMode == 15) {
- push @event, "3:state:not_supported";
- }
- if ($serviceInfo == 0) {
- push @event, "3:error:ok";
- } elsif ($serviceInfo == 1) {
- push @event, "3:error:air_filter";
- } elsif ($serviceInfo == 2) {
- push @event, "3:error:hardware";
- } elsif ($serviceInfo == 7) {
- push @event, "3:error:not_supported";
- }
- if ($humidityCtrl == 0) {
- push @event, "3:humidityCtrl:disabled";
- } elsif ($humidityCtrl == 1) {
- push @event, "3:humidityCtrl:enabled";
- } elsif ($humidityCtrl == 3) {
- push @event, "3:humidityCtrl:not_supported";
- }
- if ($roomSizeRef == 0) {
- push @event, "3:roomSizeRef:used";
- } elsif ($roomSizeRef == 1) {
- push @event, "3:roomSizeRef:not_used";
- } elsif ($roomSizeRef == 3) {
- push @event, "3:roomSizeRef:not_supported";
- }
- if ($roomSize < 15) {
- push @event, "3:roomSize:" . $roomSizeTbl{$roomSize};
- }
- if ($humidity >= 0 && $humidity <= 100) {
- push @event, "3:humidity:$humidity";
- } elsif ($humidity == 255) {
- push @event, "3:humidity:not_supported";
- }
- if ($fanSpeed >= 0 && $fanSpeed <= 100) {
- push @event, "3:fanSpeed:$fanSpeed";
- } elsif ($fanSpeed == 255) {
- push @event, "3:fanSpeed:not_supported";
- }
- }
- } elsif ($st eq "currentClamp.00") {
- # AC current clamp (EEP D2-32-00)
- if ($db[2] & 0x80) {
- # power fail
- push @event, "3:current1:0";
- push @event, "3:state:I1: 0";
- } else {
- my $current1 = hex(substr($data, 2, 3));
- if ($db[2] & 0x40) {
- # divisor = 1/10
- $current1 = $current1 / 10;
- push @event, "3:current1:" . sprintf "%0.1f", $current1;
- push @event, "3:state:I1: " . sprintf "%0.1f", $current1;
- } else {
- push @event, "3:current1:" . $current1;
- push @event, "3:state:I1: " . $current1;
- }
- }
- } elsif ($st eq "currentClamp.01") {
- # AC current clamp (EEP D2-32-01)
- if ($db[3] & 0x80) {
- # power fail
- push @event, "3:current1:0";
- push @event, "3:current2:0";
- push @event, "3:state:I1: 0 I2: 0";
- } else {
- my $current1 = hex(substr($data, 2, 3));
- my $current2 = hex(substr($data, 5, 3));
- if ($db[3] & 0x40) {
- # divisor = 1/10
- $current1 = $current1 / 10;
- $current2 = $current2 / 10;
- push @event, "3:current1:" . sprintf "%0.1f", $current1;
- push @event, "3:current2:" . sprintf "%0.1f", $current2;
- push @event, "3:state:I1: " . sprintf("%0.1f", $current1) . " I2: " . sprintf("%0.1f", $current2);
- } else {
- push @event, "3:current1:" . $current1;
- push @event, "3:current2:" . $current2;
- push @event, "3:state:I1: $current1 I2: $current2";
- }
- }
- } elsif ($st eq "currentClamp.02") {
- # AC current clamp (EEP D2-32-02)
- if ($db[5] & 0x80) {
- # power fail
- push @event, "3:current1:0";
- push @event, "3:current2:0";
- push @event, "3:current3:0";
- push @event, "3:state:I1: 0 I2: 0 I3: 0";
- } else {
- my $current1 = hex(substr($data, 2, 3));
- my $current2 = hex(substr($data, 5, 3));
- my $current3 = hex(substr($data, 8, 3));
- if ($db[5] & 0x40) {
- # divisor = 1/10
- $current1 = $current1 / 10;
- $current2 = $current2 / 10;
- $current3 = $current3 / 10;
- push @event, "3:current1:" . sprintf "%0.1f", $current1;
- push @event, "3:current2:" . sprintf "%0.1f", $current2;
- push @event, "3:current3:" . sprintf "%0.1f", $current3;
- push @event, "3:state:I1: " . sprintf("%0.1f", $current1) . " I2: " . sprintf("%0.1f", $current2) . " I3: " . sprintf("%0.1f", $current3);
- } else {
- push @event, "3:current1:" . $current1;
- push @event, "3:current2:" . $current2;
- push @event, "3:current3:" . $current3;
- push @event, "3:state:I1: $current1 I2: $current2 I3: $current3";
- }
- }
- } elsif ($st eq "ledCtrlState.00") {
- # LED Controller Status (EEP D2-40-00)
- if ($db[1] & 0x80) {
- # powerSwitch
- push @event, "3:powerSwitch:on";
- push @event, "3:state:on";
- } else {
- push @event, "3:powerSwitch:off";
- push @event, "3:state:off";
- }
- if ($db[1] & 0x40) {
- # Demand Response Mode
- push @event, "3:demandResp:on";
- } else {
- push @event, "3:demandResp:off";
- }
- if ($db[1] & 0x20) {
- # Daylight Harvesting
- push @event, "3:daylightHarvesting:on";
- } else {
- push @event, "3:daylightHarvesting:off";
- }
- my $occupancy = ($db[1] & 0x18) >> 3;
- if ($occupancy == 0) {
- push @event, "3:occupany:unoccupied";
- } elsif ($occupancy == 1) {
- push @event, "3:occupany:occupied";
- } else {
- push @event, "3:occupany:unknown";
- }
- push @event, "3:telegramType:" . ($db[1] & 4 ? "event" : "heartbeat");
- if ($db[0] >= 0 && $db[0] <= 200) {
- push @event, "3:dim:" . sprintf "%0.1f", $db[0] / 2;
- }
- } elsif ($st eq "ledCtrlState.01") {
- # LED Controller Status (EEP D2-40-01)
- if ($db[3] & 0x80) {
- # powerSwitch
- push @event, "3:powerSwitch:on";
- push @event, "3:state:on";
- } else {
- push @event, "3:powerSwitch:off";
- push @event, "3:state:off";
- }
- if ($db[3] & 0x40) {
- # Demand Response Mode
- push @event, "3:demandResp:on";
- } else {
- push @event, "3:demandResp:off";
- }
- if ($db[3] & 0x20) {
- # Daylight Harvesting
- push @event, "3:daylightHarvesting:on";
- } else {
- push @event, "3:daylightHarvesting:off";
- }
- my $occupancy = ($db[3] & 0x18) >> 3;
- if ($occupancy == 0) {
- push @event, "3:occupany:unoccupied";
- } elsif ($occupancy == 1) {
- push @event, "3:occupany:occupied";
- } else {
- push @event, "3:occupany:unknown";
- }
- push @event, "3:telegramType:" . ($db[3] & 4 ? "event" : "heartbeat");
- my ($red, $green, $blue) = ('00', '00', '00');
- if ($db[2] >= 0 && $db[2] <= 200) {
- push @event, "3:red:" . sprintf "%0.1f", $db[2] / 2;
- $red = sprintf "%02X", abs($db[2] / 200 * 255);
- }
- if ($db[1] >= 0 && $db[1] <= 200) {
- push @event, "3:green:" . sprintf "%0.1f", $db[1] / 2;
- $green = sprintf "%02X", abs($db[1] / 200 * 255);
- }
- if ($db[0] >= 0 && $db[0] <= 200) {
- push @event, "3:blue:" . sprintf "%0.1f", $db[0] / 2;
- $blue = sprintf "%02X", abs($db[0] / 200 * 255);
- }
- push @event, "3:rgb:" . $red . $green . $blue;
- } elsif ($st eq "heatRecovery.00") {
- # heat recovery ventilation
- # (D2-50-00)
- my $msgType = hex(substr($data, 0, 1)) >> 1;
- if ($msgType == 2) {
- my $ventilation = 'unknown';
- my %ventilation = (
- 0 => "off",
- 1 => 1,
- 2 => 2,
- 3 => 3,
- 4 => 4,
- 11 => "auto",
- 12 => "demand",
- 13 => "supplyAir",
- 14 => "exhaustAir"
- );
- $ventilation = $db[13] & 15;
- $ventilation = $ventilation{$ventilation} if (exists $ventilation{$ventilation});
- push @event, "3:ventilation:$ventilation";
- push @event, "3:fireplaceSafetyMode:" . ($db[12] & 8 ? 'enabled' : 'disabled');
- push @event, "3:heatExchangerBypass:" . ($db[12] & 4 ? 'opened' : 'closed');
- push @event, "3:supplyAirFlap:" . ($db[12] & 2 ? 'opened' : 'closed');
- push @event, "3:exhaustAirFlap:" . ($db[12] & 1 ? 'opened' : 'closed');
- push @event, "3:defrost:" . ($db[11] & 0x80 ? 'on' : 'off');
- push @event, "3:coolingProtection:" . ($db[11] & 0x40 ? 'on' : 'off');
- push @event, "3:outdoorAirHeater:" . ($db[11] & 0x20 ? 'on' : 'off');
- push @event, "3:supplyAirHeater:" . ($db[11] & 0x10 ? 'on' : 'off');
- push @event, "3:drainHeater:" . ($db[11] & 8 ? 'on' : 'off');
- push @event, "3:timerMode:" . ($db[11] & 4 ? 'on' : 'off');
- push @event, "3:filterMaintenance:" . ($db[11] & 2 ? 'required' : 'not_required');
- push @event, "3:weeklyTimer:" . ($db[11] & 1 ? 'on' : 'off');
- push @event, "3:roomTempCtrl:" . ($db[10] & 0x80 ? 'on' : 'off');
- my $airQuatity = $db[10] & 0x7F;
- if ($airQuatity <= 100) {
- push @event, "3:airQuality1:$airQuatity";
- } else {
- CommandDeleteReading(undef, "$name airQuality1");
- }
- push @event, "3:deviceMode:" . ($db[9] & 0x80 ? 'slave' : 'master');
- $airQuatity = $db[9] & 0x7F;
- if ($airQuatity <= 100) {
- push @event, "3:airQuality2:$airQuatity";
- } else {
- CommandDeleteReading(undef, "$name airQuality2");
- }
- my $outdoorTemp = ($db[8] & 0xFE) >> 1;
- $outdoorTemp -= $outdoorTemp if ($outdoorTemp & 0x40);
- push @event, "3:outdoorTemp:$outdoorTemp";
- my $supplyTemp = ($db[8] & 1) << 6 | ($db[7] & 0xFC) >> 2;
- $supplyTemp -= $supplyTemp if ($supplyTemp & 0x40);
- push @event, "3:supplyTemp:$supplyTemp";
- my $roomTemp = ($db[7] & 3) << 5 | ($db[6] & 0xF8) >> 3;
- $roomTemp -= $roomTemp if ($roomTemp & 0x40);
- push @event, "3:roomTemp:$roomTemp";
- my $exhaustTemp = ($db[6] & 7) << 4 | ($db[5] & 0xF0) >> 4;
- $exhaustTemp -= $exhaustTemp if ($exhaustTemp & 0x40);
- push @event, "3:exhaustTemp:$exhaustTemp";
- push @event, "3:supplyAirFlow:". (($db[5] & 0x0F) << 2 | ($db[4] & 0xFC) >> 2);
- push @event, "3:exhaustAirFlow:" . (($db[4] & 3) << 8 | $db[3]);
- push @event, "3:supplyFanSpeed:". ($db[2] << 4 | ($db[1] & 0xF0) >> 4);
- push @event, "3:exhaustFanSpeed:" . (($db[1] & 0x0F) << 8 | $db[0]);
- push @event, "3:state:$ventilation";
- } elsif ($msgType == 3) {
- push @event, "3:SWVersion:" . (($db[13] & 0x0F) << 8 | $db[12]);
- push @event, "3:operationHours:" . (($db[11] << 8 | $db[10]) * 3);
- push @event, "3:input:" . sprintf("%02b %02b", $db[9], $db[8]);
- push @event, "3:output:" . sprintf("%02b %02b", $db[7], $db[6]);
- push @event, "3:info:" . sprintf("%02b %02b", $db[5], $db[4]);
- push @event, "3:fault:" . sprintf("%02b %02b %02b %02b", $db[3], $db[2], $db[1], $db[0]);
- }
- } elsif ($st eq "valveCtrl.00") {
- # Valve Control
- # (D2-A0-01)
- $db[0] &= 3;
- if (AttrVal($name, "devMode", "master") eq "slave") {
- # devMode slave
- if ($db[0] == 1 || $db[0] == 3) {
- push @event, "3:state:closes";
- } elsif ($db[0] == 2) {
- push @event, "3:state:opens";
- } elsif ($db[0] == 0) {
- my $state = ReadingsVal($name, "state", "-");
- if ($state eq "closed" || $state eq "closes") {
- $state = "01";
- } elsif ($state eq "open" || $state eq "opens") {
- $state = "02";
- } else {
- $state = "00";
- }
- EnOcean_SndRadio(undef, $hash, 1, "D2", $state, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- }
- } else {
- #devMode master
- if ($db[0] == 1) {
- push @event, "3:state:closed";
- } elsif ($db[0] == 2) {
- push @event, "3:state:open";
- } elsif ($db[0] == 0 || $db[0] == 3) {
- push @event, "3:state:-";
- }
- }
- } elsif ($st eq "liquidLeakage.51") {
- # liquid leakage sensor
- push @event, "3:state:" . $db[0] & 3 ? 'wet' : 'dry';
- } elsif ($st eq "raw") {
- # raw
- push @event, "3:state:RORG: $rorg DATA: $data STATUS: $status ODATA: $odata";
- # display data bytes $db[0] ... $db[x]
- for (my $dbCntr = 0; $dbCntr <= $#db; $dbCntr++) {
- push @event, "3:DB_" . $dbCntr . ":" . $db[$dbCntr];
- }
- } else {
- # unknown devices
- push @event, "3:state:$data";
- }
- } elsif ($rorg eq "D1") {
- # MSC telegram
- if ($st eq "actuator.01") {
- if ($manufID eq "033") {
- if (substr($data, 3, 1) == 4) {
- my $getParam = ReadingsVal($name, "getParam", 0);
- if ($getParam == 8) {
- push @event, "3:loadClassification:no";
- push @event, "3:loadLink:" . ($db[1] & 16 ? "connected" : "disconnected");
- push @event, "3:loadOperation:3-wire";
- push @event, "3:loadState:" . ($db[1] & 64 ? "on" : "off");
- CommandDeleteReading(undef, "$name getParam");
- } elsif ($getParam == 7) {
- if ($db[0] & 4) {
- push @event, "3:devTempState:warning";
- } elsif ($db[0] & 2) {
- push @event, "3:devTempState:max";
- } else {
- push @event, "3:devTempState:ok";
- }
- push @event, "3:mainsPower:" . ($db[0] & 8 ? "failure" : "ok");
- if ($db[1] == 0xFF) {
- push @event, "3:devTemp:invalid";
- } else {
- push @event, "3:devTemp:" . $db[1];
- }
- CommandDeleteReading(undef, "$name getParam");
- } elsif ($getParam == 9) {
- push @event, "3:voltage:" . sprintf("%.2f", (hex(substr($data, 4, 4)) * 0.01));
- CommandDeleteReading(undef, "$name getParam");
- } elsif ($getParam == 0x81) {
- $hash->{READINGS}{serialNumber}{VAL} = substr($data, 4, 4);
- $hash->{READINGS}{getParam}{VAL} = 0x82;
- EnOcean_SndRadio(undef, $hash, $packetType, "D1", "033182", AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- } elsif ($getParam == 0x82) {
- push @event, "3:serialNumber:" . $hash->{READINGS}{serialNumber}{VAL} . substr($data, 4, 4);
- CommandDeleteReading(undef, "$name getParam");
- }
- }
- } elsif ($manufID eq "046") {
- my $cmd = hex(substr($data, 4, 2));
- if ($cmd == 3) {
- push @event, "3:firmwareVersion:" . substr($data, 6, 6);
- } elsif ($cmd == 5) {
- push @event, "3:taughtInDevNum:$db[0]";
- } elsif ($cmd == 7) {
- CommandDeleteReading(undef, "$name taughtInDevID.*");
- push @event, "3:taughtInDevID" . sprintf('%02d', $db[4]) . ":" . substr($data, 8, 8);
- }
- }
- } elsif ($st eq "raw") {
- # raw
- push @event, "3:state:RORG: $rorg DATA: $data STATUS: $status ODATA: $odata";
- push @event, "3:manufID:" . substr($data, 0, 3);
- # display data bytes $db[0] ... $db[x]
- for (my $dbCntr = 0; $dbCntr <= $#db; $dbCntr++) {
- push @event, "3:DB_" . $dbCntr . ":" . $db[$dbCntr];
- }
- } else {
- # unknown devices
- if(AttrVal($name, "blockUnknownMSC", "no") eq "yes") {
- push @event, "3:MSC:$data";
- }
- }
- } elsif ($rorg eq "D0") {
- my %smartAckMailbox = (0 => 'empty', 1 => 'not_exits', 2 => 'reset');
- push @event, "3:smartAckMailbox:" . $smartAckMailbox{$db[0]};
- } elsif ($rorg eq "B2") {
- # GP complete data (GPCD)
- my ($channel, $channelName, $value, $gpDef, $resolution) = (0, undef, undef, AttrVal($name, 'gpDef', undef), undef);
- return "generic profil not defined" if (!defined $gpDef);
- my @gpDef = split("[ \t][ \t]*", $gpDef);
- my ($readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType);
- $data = EnOcean_convHexToBit($data);
- while (defined $gpDef[$channel]) {
- ($channelName, undef, undef, undef, undef, $resolution, undef, undef, undef, undef) = split(':', $gpDef[$channel]);
- $resolution = 0 if (!defined $resolution);
- $data =~ m/^(.{$EnO_resolution[$resolution]})(.*)$/;
- $value = hex(unpack('H8', pack('B32', '0' x (32 - $EnO_resolution[$resolution]) . $1)));
- $data = $2;
- ($err, $logLevel, $response, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType)=
- EnOcean_gpConvDataToValue (undef, $hash, $channel, $value, $gpDef[$channel]);
- $channel ++;
- next if (defined $err);
- push @event, "3:$readingName:" . sprintf("$readingFormat", $readingValue);
- push @event, "3:" . $readingName . "Unit:$readingUnit";
- push @event, "3:" . $readingName . "ValueType:$valueType";
- push @event, "3:" . $readingName . "ChannelType:$readingType";
- }
- } elsif ($rorg eq "B3") {
- # GP selective data (GPSD)
- my $gpDef = AttrVal($name, "gpDef", undef);
- return "generic profil not defined" if (!defined $gpDef);
- my @gpDef = split("[ \t][ \t]*", $gpDef);
- my ($channel, $channelName, $resolution, $value);
- my ($readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType);
- #Log3 $name, 2, "EnOcean $name parse GPSD data: $data start";
- $data =~ m/^(.)(.*)$/;
- my $header = hex $1;
- $data = substr(EnOcean_convHexToBit($data), 4);
- #Log3 $name, 2, "EnOcean $name parse GPSD header: $header data: $data start";
- for (my $cntr = 1; $cntr <= $header; $cntr ++) {
- $data =~ m/^(.{6})(.*)$/;
- ($channel, $data) = (unpack('C', pack('B8', '00' . $1)), $2);
- #Log3 $name, 2, "EnOcean $name parse GPSD channel: $channel data: $data";
- if (defined $gpDef[$channel]) {
- ($channelName, undef, undef, undef, undef, $resolution, undef, undef, undef, undef) = split(':', $gpDef[$channel]);
- $resolution = 0 if (!defined $resolution || $resolution eq '');
- $data =~ m/^(.{$EnO_resolution[$resolution]})(.*)$/;
- $value = hex(unpack('H8', pack('B32', '0' x (32 - $EnO_resolution[$resolution]) . $1)));
- $data = $2;
- #Log3 $name, 2, "EnOcean $name parse GPSD channel: $channel value: " . $value . " data: $data";
- ($err, $logLevel, $response, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType) =
- EnOcean_gpConvDataToValue(undef, $hash, $channel, $value, $gpDef[$channel]);
- push @event, "3:$readingName:" . sprintf("$readingFormat", $readingValue);
- push @event, "3:" . $readingName . "Unit:$readingUnit";
- push @event, "3:" . $readingName . "ValueType:$valueType";
- push @event, "3:" . $readingName . "ChannelType:$readingType";
- }
- }
- } elsif ($rorg eq "B0" && $teach) {
- # GP teach in request (GPTI)
- #
- $data =~ m/^(....)(.*)$/;
- my $header = hex($1);
- $data = $2;
- my $purpose = ($header & 12) >> 2;
- if ($purpose == 0 || ($purpose == 2 && AttrVal($name, "subType", "") ne "genericProfile")) {
- # teach-in request
- $attr{$name}{comMode} = $header & 16 ? "biDir" : "uniDir";
- $attr{$name}{eep} = "B0-00-00";
- $attr{$name}{manufID} = sprintf "%03X", ($header & 0xFFE0) >> 5;
- $attr{$name}{subType} = "genericProfile";
- $attr{$name}{teachMethod} = 'GP';
- my $channel = 0;
- my $channelDir = "I";
- my $channelDef;
- my $channelName;
- my $channelType;
- my $cntr = 0;
- my $dataOutboundDefLen = 0;
- my @gpDef;
- my $signalType;
- #Log3 $name, 2, "EnOcean $name parse GPTI header: $header data: $data start";
- $data = EnOcean_convHexToBit($data);
- #Log3 $name, 2, "EnOcean $name parse GPTI data: $data start";
- while (length($data) >= 12) {
- last if ($cntr > 64);
- $cntr ++;
- $data =~ m/^(..)(.{8})(.*)$/;
- $channelType = unpack('C', pack('B8', '000000' . $1));
- $signalType = unpack('C', pack('B8', $2));
- $data = $3;
- #Log3 $name, 2, "EnOcean $name parse GPTI channel: $channel channelType: $channelType signalType: $signalType data: $data";
- if ($channelType == 0) {
- # teach-in information
- if ($signalType == 1) {
- # outbound channel description
- $channelDir = "O";
- $data =~ m/^(.{8})(.*)$/;
- $dataOutboundDefLen = unpack('C', pack('B8', $1));
- $data = $2;
- } elsif ($signalType == 2) {
- # produkt ID
- $data =~ m/^(.{32})(.*)$/;
- $attr{$name}{productID} = EnOcean_convBitToHex($1);
- $data = $2;
- }
- } elsif ($channelType == 1) {
- # data
- $data =~ m/^(..)(....)(.{8})(....)(.{8})(....)(.*)$/;
- $channelDef = $channelDir . ':' . $channelType . ':' . $signalType . ':' .
- unpack('C', pack('B8', '000000' . $1)) . ':' . unpack('C', pack('B8', '0000' . $2)) . ':' .
- unpack('c', pack('B8', $3)) . ':' . unpack('C', pack('B8', '0000' . $4)) . ':' .
- unpack('c', pack('B8', $5)) . ':' . unpack('C', pack('B8', '0000' . $6));
- $data = $7;
- if (defined $EnO_gpValueData{$signalType}{name}) {
- $channelName = $EnO_gpValueData{$signalType}{name};
- } else {
- $channelName = "none";
- }
- $gpDef[$channel] = $channelName . ':' . $channelDef;
- $channel ++;
- } elsif ($channelType == 2) {
- # flag
- $data =~ m/^(..)(.*)$/;
- $channelDef = $channelDir . ':' .$channelType . ':' . $signalType . ':' .
- unpack('C', pack('B8', '000000' . $1));
- $data = $2;
- if (defined $EnO_gpValueFlag{$signalType}{name}) {
- $channelName = $EnO_gpValueFlag{$signalType}{name};
- } else {
- $channelName = "none";
- }
- $gpDef[$channel] = $channelName . ':' . $channelDef;
- $channel ++;
- } elsif ($channelType == 3) {
- # enumeration
- $data =~ m/^(..)(....)(.*)$/;
- $channelDef = $channelDir . ':' .$channelType . ':' . $signalType . ':' .
- unpack('C', pack('B8', '000000' . $1)) . ':' . unpack('C', pack('B8', '0000' . $2));
- $data = $3;
- if (defined $EnO_gpValueEnum{$signalType}{name}) {
- $channelName = $EnO_gpValueEnum{$signalType}{name};
- } else {
- $channelName = "none";
- }
- $gpDef[$channel] = $channelName . ':' . $channelDef;
- $channel ++;
- }
- }
- $attr{$name}{gpDef} = join(' ', @gpDef);
- if (AttrVal($name, "comMode", "uniDir") eq "biDir") {
- # send GP Teach-In Response message
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "biDir");
- $data = sprintf "%04X", ((hex(AttrVal($name, "manufID", "7FF")) << 5) | 8);
- EnOcean_SndCdm(undef, $hash, $packetType, "B1", $data, $subDef, "00", $senderID);
- Log3 $name, 2, "EnOcean $name GP teach-in response sent to $senderID";
- }
- my $mid = $attr{$name}{manufID};
- $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
- push @event, "3:teach:GP teach-in accepted Manufacturer: $mid";
- Log3 $name, 2, "EnOcean $name GP teach-in accepted Manufacturer: $mid";
- # store attr subType, manufID, gpDef ...
- EnOcean_CommandSave(undef, undef);
- } elsif ($purpose == 1 || ($purpose == 2 && AttrVal($name, "subType", "") eq "genericProfile")) {
- # teach-in deletion request
- $deleteDevice = $name;
- if (AttrVal($name, "comMode", "uniDir") eq "biDir") {
- # send GP Teach-In Deletion Response message
- $data = sprintf "%04X", (hex(AttrVal($name, "manufID", "7FF")) << 5) | 16;
- EnOcean_SndCdm(undef, $hash, $packetType, "B1", $data, AttrVal($name, "subDef", "00000000"), "00", $senderID);
- Log3 $name, 2, "EnOcean $name GP teach-in deletion response send to $senderID";
- }
- Log3 $name, 2, "EnOcean $name GP teach-in delete request executed";
- }
- } elsif ($rorg eq "B1" && $teach) {
- # GP teach-in response (GPTR)
- $data =~ m/^(....)(.*)$/;
- my $header = hex($1);
- $data = $2;
- my $mid = sprintf "%03X", ($header & 0xFFE0) >> 5;
- my $purpose = ($header & 24) >> 3;
- if (exists $hash->{IODev}{helper}{gpRespWait}{$destinationID}) {
- if ($purpose == 0) {
- # teach-in rejected generally
- if ($hash->{IODev}{helper}{gpRespWait}{$destinationID}{teachInReq} eq "in") {
- push @event, "3:teach:GP teach-in rejected";
- Log3 $name, 2, "EnOcean $name GP teach-in rejected by $senderID";
- }
- # clear teach-in request
- delete $hash->{IODev}{helper}{gpRespWait}{$destinationID};
- } elsif ($purpose == 1) {
- # teach-in accepted
- if ($hash->{IODev}{helper}{gpRespWait}{$destinationID}{teachInReq} eq "in") {
- $attr{$name}{manufID} = $mid;
- # substitute subDef with DEF
- $attr{$name}{subDef} = $hash->{DEF};
- $hash->{DEF} = $senderID;
- $modules{EnOcean}{defptr}{$senderID} = $hash;
- delete $modules{EnOcean}{defptr}{$destinationID};
- EnOcean_CommandSave(undef, undef);
- $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
- push @event, "3:teach:GP teach-in accepted Manufacturer: $mid";
- Log3 $name, 2, "EnOcean $name GP teach-in accepted by $senderID";
- }
- # clear teach-in request
- delete $hash->{IODev}{helper}{gpRespWait}{$destinationID};
- } elsif ($purpose == 2) {
- # teach-out accepted
- if ($hash->{IODev}{helper}{gpRespWait}{$destinationID}{teachInReq} eq "out") {
- if (defined $attr{$name}{subDef}) {
- delete $modules{EnOcean}{defptr}{$hash->{DEF}};
- $hash->{DEF} = $attr{$name}{subDef};
- $modules{EnOcean}{defptr}{$hash->{DEF}} = $hash;
- delete $attr{$name}{subDef};
- EnOcean_CommandSave(undef, undef);
- }
- push @event, "3:teach:GP teach-out accepted";
- Log3 $name, 2, "EnOcean $name GP teach-out accepted";
- }
- # clear teach-in request
- delete $hash->{IODev}{helper}{gpRespWait}{$destinationID};
- } else {
- if ($hash->{IODev}{helper}{gpRespWait}{$destinationID}{teachInReq} eq "in") {
- # rejected channels outbound or inbound, sent teach-in response with teach-out
- $data = sprintf "%04X", (hex(AttrVal($name, "manufID", "7FF")) << 5) | 16;
- EnOcean_SndCdm(undef, $hash, $packetType, "B1", $data, $destinationID, "00", $senderID);
- push @event, "3:teach:GP teach-in channels rejected, sent teach-out";
- Log3 $name, 2, "EnOcean $name GP teach-in channels rejected, sent teach-out to $senderID";
- }
- # clear teach-in request
- delete $hash->{IODev}{helper}{gpRespWait}{$destinationID};
- }
- } else {
- Log3 $name, 2, "EnOcean $name GP teach-in response from $senderID received, teach-in request unknown";
- }
- } elsif ($rorg eq "D4" && $teach) {
- # UTE - Universal Uni- and Bidirectional Teach-In / Teach-Out
- #
- Log3 $name, 5, "EnOcean $name UTE teach-in received from $senderID";
- my $rorg = sprintf "%02X", $db[0];
- my $func = sprintf "%02X", $db[1];
- my $type = sprintf "%02X", $db[2];
- my $mid = sprintf "%03X", ((($db[3] & 7) << 8) | $db[4]);
- my $devChannel = $db[5];
- my $comMode = $db[6] & 0x80 ? "biDir" : "uniDir";
- my $comModeUTE = AttrVal($hash->{IODev}{NAME}, "comModeUTE", "auto");
- $comMode = $comModeUTE if ($comModeUTE ne 'auto');
- my $subType = "$rorg.$func.$type";
- if (($db[6] & 0xF) == 0) {
- # Teach-In Query telegram received
- my $teachInReq = ($db[6] & 0x30) >> 4;
- if ($teachInReq == 0 || $teachInReq == 2) {
- # Teach-In Request
- $attr{$name}{teachMethod} = 'UTE';
- if(exists $EnO_eepConfig{$subType}) {
- # Teach-In EEP supported
- foreach my $attrCntr (keys %{$EnO_eepConfig{$subType}{attr}}) {
- $attr{$name}{$attrCntr} = $EnO_eepConfig{$subType}{attr}{$attrCntr};
- }
- $attr{$name}{manufID} = $mid;
- $attr{$name}{devChannel} = $devChannel;
- $attr{$name}{comMode} = $comMode;
- $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
- $attr{$name}{eep} = "$rorg-$func-$type";
- if (exists($hash->{helper}{teachInWait}) && $hash->{helper}{teachInWait} =~ m/^UTE|STE$/) {
- $attr{$filelogName}{logtype} = $EnO_eepConfig{$subType}{GPLOT} . 'text'
- if (exists $attr{$filelogName}{logtype});
- EnOcean_CreateSVG(undef, $hash, undef);
- delete $hash->{helper}{teachInWait};
- }
- $subType = $EnO_eepConfig{$subType}{attr}{subType};
- #$attr{$name}{subType} = $subType;
- push @event, "3:teach:UTE teach-in accepted EEP $rorg-$func-$type Manufacturer: $mid";
- if (!($db[6] & 0x40)) {
- # UTE Teach-In-Response expected
- # send UTE Teach-In Response message
- $data = (sprintf "%02X", $db[6] & 0x80 | 0x11) . substr($data, 2, 12);
- if ($comMode eq "biDir") {
- ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", $comMode);
- } else {
- $subDef = "00000000";
- }
- EnOcean_SndRadio(undef, $hash, $packetType, "D4", $data, $subDef, "00", $senderID);
- Log3 $name, 2, "EnOcean $name UTE teach-in response send to $senderID";
- }
- Log3 $name, 2, "EnOcean $name UTE teach-in accepted EEP $rorg-$func-$type Manufacturer: $mid";
- } else {
- # Teach-In EEP not supported
- $attr{$name}{subType} = "raw";
- $attr{$name}{manufID} = $mid;
- $attr{$name}{devChannel} = $devChannel;
- $attr{$name}{comMode} = $comMode;
- $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
- push @event, "3:teach:UTE teach-in accepted EEP $rorg-$func-$type not supported Manufacturer: $mid";
- # send EEP Teach-In Response message
- if (!($db[6] & 0x40)) {
- # UTE Teach-In-Response expected
- # send UTE Teach-In Response message
- $data = (sprintf "%02X", $db[6] & 0x80 | 0x31) . substr($data, 2, 12);
- EnOcean_SndRadio(undef, $hash, $packetType, "D4", $data, "00000000", "00", $senderID);
- Log3 $name, 2, "EnOcean $name UTE teach-in response send to $senderID";
- }
- Log3 $name, 2, "EnOcean $name UTE teach-in accepted EEP $rorg-$func-$type not supported Manufacturer: $mid";
- }
- # store attr subType, manufID ...
- EnOcean_CommandSave(undef, undef);
- } elsif ($teachInReq == 1) {
- # Teach-In Deletion Request
- $deleteDevice = $name;
- if (!($db[6] & 0x40)) {
- # UTE Teach-In Deletion Response expected
- # send UTE Teach-In Deletion Response message
- $data = (sprintf "%02X", $db[6] & 0x80 | 0x21) . substr($data, 2, 12);
- EnOcean_SndRadio(undef, $hash, $packetType, "D4", $data, AttrVal($name, "subDef", "00000000"), "00", $senderID);
- Log3 $name, 2, "EnOcean $name UTE teach-in deletion response send to $senderID";
- }
- Log3 $name, 2, "EnOcean $name UTE teach-in delete request executed";
- }
- } else {
- # Teach-In Respose telegram received
- my $teachInAccepted = ($db[6] & 0x30) >> 4;
- Log3 $name, 5, "EnOcean $name UTE teach-in response message from $senderID received";
- if (exists $hash->{IODev}{helper}{UTERespWait}{$destinationID}) {
- if ($comMode eq "uniDir") {
- $attr{$name}{manufID} = $mid;
- if ($teachInAccepted == 0) {
- $teachInAccepted = "request not accepted";
- } elsif ($teachInAccepted == 1){
- $teachInAccepted = "teach-in accepted";
- } elsif ($teachInAccepted == 2){
- $teachInAccepted = "teach-out accepted";
- } else {
- $teachInAccepted = "EEP not supported";
- }
- $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
- push @event, "3:teach:UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
- Log3 $name, 2, "EnOcean $name UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
- } else {
- if ($hash->{IODev}{helper}{UTERespWait}{$destinationID}{teachInReq} eq "in") {
- # Teach-In Request
- if ($teachInAccepted == 0) {
- $teachInAccepted = "request not accepted";
- } elsif ($teachInAccepted == 1){
- $teachInAccepted = "teach-in accepted";
- $attr{$name}{subDef} = $hash->{DEF};
- $hash->{DEF} = $senderID;
- $modules{EnOcean}{defptr}{$senderID} = $hash;
- delete $modules{EnOcean}{defptr}{$destinationID};
- $attr{$name}{manufID} = $mid;
- # store attr subType, manufID ...
- EnOcean_CommandSave(undef, undef);
- } elsif ($teachInAccepted == 2){
- $teachInAccepted = "teach-out accepted";
- } else {
- $teachInAccepted = "EEP not supported";
- }
- $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
- push @event, "3:teach:UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
- Log3 $name, 2, "EnOcean $name UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
- } elsif ($hash->{IODev}{helper}{UTERespWait}{$destinationID}{teachInReq} eq "out") {
- # Teach-In Deletion Request
- if ($teachInAccepted == 0) {
- $teachInAccepted = "request not accepted";
- } elsif ($teachInAccepted == 1){
- $teachInAccepted = "teach-in accepted";
- } elsif ($teachInAccepted == 2){
- $teachInAccepted = "teach-out accepted";
- if (defined $attr{$name}{subDef}) {
- delete $modules{EnOcean}{defptr}{$hash->{DEF}};
- $hash->{DEF} = $attr{$name}{subDef};
- $modules{EnOcean}{defptr}{$hash->{DEF}} = $hash;
- delete $attr{$name}{subDef};
- EnOcean_CommandSave(undef, undef);
- }
- } else {
- $teachInAccepted = "EEP not supported";
- }
- $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid});
- push @event, "3:teach:UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
- Log3 $name, 2, "EnOcean $name UTE $teachInAccepted EEP $rorg-$func-$type Manufacturer: $mid";
- }
- }
- # clear teach-in request
- delete $hash->{IODev}{helper}{UTERespWait}{$destinationID};
- } else {
- # teach-in request unknown, delete response device, no action
- $deleteDevice = $name;
- Log3 $name, 2, "EnOcean $name UTE teach-in request unknown";
- }
- }
- } elsif ($rorg eq "35" && $teach) {
- # Secure Teach-In
- ($err, $msg) = EnOcean_sec_parseTeachIn($hash, $data, $subDef, $destinationID);
- if (defined $err) {
- Log3 $name, 2, "EnOcean $name secure teach-in ERROR: $err";
- return "";
- }
- Log3 $name, 3, "EnOcean $name secure teach-in $msg";
- EnOcean_CommandSave(undef, undef);
- return "";
- } elsif ($rorg eq "C6" && $smartAckLearn) {
- # Smart Ack Learn Request
- #####
- $data =~ m/^(....)(..)(..)(..)(..)(........)$/;
- my $subType = "$2.$3.$4";
- my $postmasterID = '0' x 8;
- my $mid = '7FF';
- my $responseTime = 150;
- my $sendData = '';
- if (exists $EnO_eepConfig{$subType}) {
- if (($db[9] & 0xF8) == 0xF8) {
- # Smart Ack send by sensor
- $attr{$name}{subType} = $EnO_eepConfig{$subType}{attr}{subType};
- $attr{$name}{eep} = "$3-$4-$5";
- $mid = substr(sprintf("%04X", hex($1) & 0x7FF), 1);
- $attr{$name}{manufID} = $mid;
- $mid = $EnO_manuf{$mid} if(exists $EnO_manuf{$mid});
- $attr{$name}{repeaterID} = $6;
- $postmasterID = $6;
- $attr{$name}{postmasterID} = $postmasterID;
- $attr{$name}{teachMethod} = 'smartAck';
- $hash->{SmartAckRSSI} = - hex($db[4]);
- foreach my $attrCntr (keys %{$EnO_eepConfig{$subType}{attr}}) {
- if ($attrCntr ne "subDef") {
- $attr{$name}{$attrCntr} = $EnO_eepConfig{$subType}{attr}{$attrCntr};
- }
- }
- if (defined AttrVal($name, 'subDef', undef)) {
- $subDef = $attr{$name}{subDef};
- } else {
- $subDef = EnOcean_CheckSenderID('getNextID', $hash->{IODev}{NAME}, "00000000");
- $attr{$name}{subDef} = $subDef;
- }
- # create mailbox
- $sendData = sprintf "03%04X00%04X%04X", $responseTime, $postmasterID, $hash->{DEF};
- EnOcean_SndRadio(undef, $hash, 6, $rorg, $sendData, $subDef, '00', $hash->{DEF});
- # next commands will be sent with a delay
- #usleep($responseTime * 1000);
- # send learn reply
- $sendData = sprintf "01%04X00%04X", $responseTime, $hash->{DEF};
- EnOcean_SndRadio(undef, $hash, 1, 'C7', $sendData, $subDef, '00', $hash->{DEF});
- push @event, "3:teach:Smart Ack teach-in accepted EEP " . $attr{$name}{eep} . " Manufacturer: $mid";
- Log3 $name, 2, "EnOcean $name Smart Ack teach-in accepted EEP " . $attr{$name}{eep} . " Manufacturer: $mid";
- EnOcean_CommandSave(undef, undef);
- }
- } else {
- # EEP not supported
- # send learn reply
- $sendData = sprintf "01%04X10%04X", $responseTime, $hash->{DEF};
- EnOcean_SndRadio(undef, $hash, 1, 'C7', $sendData, '0' x 8, '00', $hash->{DEF});
- CommandDelete(undef, $name);
- Log3 $name, 2, "EnOcean $name Smart Ack teach-in not accepted EEP " . $attr{$name}{eep} . " Manufacturer: $mid";
- }
- } elsif ($packetType == 7) {
- # packet type REMOTE_MAN_COMMAND
- my $remoteCode = AttrVal($name, 'remoteCode', '0' x 8);
- my $remoteLastStatusReturnCode = $manufID eq '7FF' ? '00' : '04';
- my $remoteManagement = AttrVal($name, "remoteManagement", "off");
- my $sendData = '';
- push @event, "3:remoteLastFunctionNumber:" . sprintf("%03X", $funcNumber);
- if ($funcNumber == 1 && $remoteManagement eq 'client') {
- # unlock
- if ($data eq uc($remoteCode) && !exists($hash->{RemoteClientUnlockFailed})) {
- $hash->{RemoteClientUnlock} = 1;
- my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 1800, 'EnOcean_cdmClearHashVal', \%functionHash, 0);
- Log3 $name, 2, "EnOcean $name RMCC unlock request executed.";
- } else {
- $remoteLastStatusReturnCode = '02';
- $hash->{RemoteClientUnlockFailed} = 1;
- my %functionHash = (hash => $hash, param => 'RemoteClientUnlockFailed');
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 30, 'EnOcean_cdmClearHashVal', \%functionHash, 0);
- Log3 $name, 2, "EnOcean $name RMCC unlock request not executed, remote Code $data wrong.";
- }
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- } elsif ($funcNumber == 2 && $remoteManagement eq 'client') {
- # lock
- if ($hash->{RemoteClientUnlock} && $data eq uc($remoteCode)) {
- delete $hash->{RemoteClientUnlock};
- my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
- RemoveInternalTimer(\%functionHash);
- Log3 $name, 2, "EnOcean $name RMCC lock request executed.";
- } else {
- $remoteLastStatusReturnCode = '02';
- Log3 $name, 2, "EnOcean $name RMCC lock request not executed.";
- }
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- } elsif ($funcNumber == 3 && $remoteManagement eq 'client') {
- # set code
- if ($hash->{RemoteClientUnlock} && $data =~ m/^[A-Fa-f0-9]{8}$/ && uc($data) ne 'FFFFFFFF') {
- $attr{$name}{remoteCode} = $data;
- EnOcean_CommandSave(undef, undef);
- Log3 $name, 2, "EnOcean $name RMCC set code request executed.";
- } else {
- $remoteLastStatusReturnCode = '05';
- Log3 $name, 2, "EnOcean $name RMCC set code request not executed.";
- }
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- } elsif ($funcNumber == 4 && $remoteManagement eq 'client') {
- # query ID
- if ($hash->{RemoteClientUnlock}) {
- my $eepRcv = hex(substr($data, 0, 6)) >> 3;
- my $rorg = sprintf "%02X", ($eepRcv >> 13);
- my $func = sprintf "%02X", (($eepRcv & 0x1F80) >> 7);
- my $type = sprintf "%02X", ($eepRcv & 127);
- $eepRcv = "$rorg-$func-$type";
- if ($hash->{RemoteClientUnlock} && $eep eq $eepRcv) {
- $sendData = '06040' . AttrVal($name, 'manufID', '7FF') . substr($data, 0, 4) . sprintf("%02X", hex(substr($data, 4, 2)) & 0xF8);
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
- Log3 $name, 2, "EnOcean $name RMCC query ID answer sent.";
- } else {
- $remoteLastStatusReturnCode = '03';
- Log3 $name, 2, "EnOcean $name RMCC query ID request not executed, EEP $eepRcv wrong or client locked.";
- }
- } else {
- $remoteLastStatusReturnCode = '07';
- Log3 $name, 2, "EnOcean $name RMCC query ID request not executed.";
- }
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- } elsif ($funcNumber == 5 && $remoteManagement eq 'client') {
- # action
- if ($hash->{RemoteClientUnlock}) {
- Log3 $name, 2, "EnOcean $name RMCC action request executed";
- } else {
- $remoteLastStatusReturnCode = '01';
- Log3 $name, 2, "EnOcean $name RMCC action request not executed.";
- }
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- } elsif ($funcNumber == 6 && $remoteManagement eq 'client') {
- # ping
- my $eep = AttrVal($name, "eep", "C5-00-00");
- if ($eep =~ m/^([A-Fa-f0-9]{2})-([A-Fa-f0-9]{2})-([A-Fa-f0-9]{2})$/i) {
- $eep = (((hex($1) << 6) | hex($2)) << 7) | hex($3);
- } else {
- $eep = (((hex("C5") << 6) | hex("00")) << 7) | hex("00");
- }
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- push @event, "3:remoteRSSI:" . -$RSSI;
- $sendData = '06060' . AttrVal($name, 'manufID', '7FF') . sprintf("%04X%02X", $eep << 3, $RSSI);
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
- Log3 $name, 2, "EnOcean $name RMCC ping answer executed";
- } elsif ($funcNumber == 7 && $remoteManagement eq 'client') {
- # query function
- if ($hash->{RemoteClientUnlock}) {
- $sendData = '06070' . AttrVal($name, 'manufID', '7FF') . '020107FF';
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
- Log3 $name, 2, "EnOcean $name RMCC query function answer sent.";
- } else {
- $remoteLastStatusReturnCode = '07';
- Log3 $name, 2, "EnOcean $name RMCC query function request not executed.";
- }
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- } elsif ($funcNumber == 8 && $remoteManagement eq 'client') {
- # query status
- if ($hash->{RemoteClientUnlock}) {
- $sendData = '06080' . AttrVal($name, 'manufID', '7FF') . '000' . ReadingsVal($name, "remoteLastFunctionNumber", "000") .
- ReadingsVal($name, "$remoteLastStatusReturnCode", '00');
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
- Log3 $name, 2, "EnOcean $name RMCC query status answer sent.";
- } else {
- $remoteLastStatusReturnCode = '07';
- Log3 $name, 2, "EnOcean $name RMCC query status request not executed.";
- }
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- } elsif ($funcNumber == 0x201) {
- $data =~ m/^(..)(..)(..)(..)$/;
- my ($rorg, $func, $type, $flag) = ($1, $2, $3, hex($4));
- if ($flag == 1) {
- # learn in
- if (exists $EnO_eepConfig{"$rorg.$func.$type"}) {
- $attr{$name}{eep} = "$rorg-$func-$type";
- #$attr{$name}{remoteID} = $remoteID if (defined $remoteID);
- $attr{$name}{remoteManagement} = "client";
- $attr{$name}{teachMethod} = 'RPC';
- foreach my $attrCntr (keys %{$EnO_eepConfig{"$rorg.$func.$type"}{attr}}) {
- if ($attrCntr eq "subDef") {
- if (!exists $attr{$name}{$attrCntr}) {
- $attr{$name}{$attrCntr} = EnOcean_CheckSenderID($EnO_eepConfig{"$rorg.$func.$type"}{attr}{$attrCntr}, $hash->{IODev}{NAME}, "00000000");
- }
- } else {
- $attr{$name}{$attrCntr} = $EnO_eepConfig{"$rorg.$func.$type"}{attr}{$attrCntr};
- }
- }
- EnOcean_CreateSVG(undef, $hash, $attr{$name}{eep});
- push @event, "3:teach:RPC teach-in accepted EEP $rorg-$func-$type Manufacturer: " .
- (exists($EnO_manuf{$manufID}) ? $EnO_manuf{$manufID} : $manufID);
- Log3 $name, 2, "EnOcean $name RPC teach-in with EEP $rorg-$func-$type ManufacturerID: $manufID accepted.";
- } else {
- Log3 $name, 2, "EnOcean $name RPC teach-in with EEP $rorg-$func-$type ManufacturerID: $manufID not supported.";
- }
- } elsif ($flag == 3) {
- # learn out
- #Log3 $name, 2, "EnOcean $name device $name deleted";
- CommandDelete(undef, $name);
- Log3 $name, 2, "EnOcean $name RPC teach-out with EEP $rorg-$func-$type ManufacturerID: $manufID executed.";
- EnOcean_CommandSave(undef, undef);
- } else {
- Log3 $name, 2, "EnOcean $name RPC learn function $flag not supported";
- }
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- } elsif ($funcNumber == 0x240 && $remoteManagement eq 'manager') {
- # acknowledge
- $remoteLastStatusReturnCode = '00';
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- Log3 $name, 2, "EnOcean $name RPC acknowledge received";
- } elsif ($funcNumber == 0x604 && $remoteManagement eq 'manager') {
- # query id answer
- my $eep = hex(substr($data, 0, 6)) >> 3;
- my $rorg = sprintf "%02X", ($eep >> 13);
- my $func = sprintf "%02X", (($eep & 0x1F80) >> 7);
- my $type = sprintf "%02X", ($eep & 127);
- $attr{$name}{remoteManufID} = $manufID;
- $manufID = $EnO_manuf{$manufID} if($EnO_manuf{$manufID});
- $attr{$name}{remoteEEP} = "$rorg-$func-$type";
- my $subType = "$rorg.$func.$type";
- #$attr{$name}{subType} = $EnO_eepConfig{$subType}{attr}{subType} if($EnO_manuf{$subType});
- $remoteLastStatusReturnCode = '00';
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- Log3 $name, 2, "EnOcean $name RMCC query ID answer received EEP $rorg-$func-$type Manufacturer: $manufID";
- EnOcean_CommandSave(undef, undef);
- } elsif ($funcNumber == 0x606 && $remoteManagement eq 'manager') {
- # ping answer
- my $eep = hex(substr($data, 0, 6)) >> 3;
- my $rorg = sprintf "%02X", ($eep >> 13);
- my $func = sprintf "%02X", (($eep & 0x1F80) >> 7);
- my $type = sprintf "%02X", ($eep & 127);
- $attr{$name}{remoteManufID} = $manufID;
- $manufID = $EnO_manuf{$manufID} if($EnO_manuf{$manufID});
- $attr{$name}{remoteEEP} = "$rorg-$func-$type";
- my $subType = "$rorg.$func.$type";
- #$attr{$name}{subType} = $EnO_eepConfig{$subType}{attr}{subType} if($EnO_manuf{$subType});
- push @event, "3:remoteRSSI:" . -$RSSI;
- $remoteLastStatusReturnCode = '00';
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- Log3 $name, 2, "EnOcean $name RMCC ping answer received EEP $rorg-$func-$type Manufacturer: $manufID";
- EnOcean_CommandSave(undef, undef);
- } elsif ($funcNumber == 0x607 && $remoteManagement eq 'manager') {
- # query function answer
- CommandDeleteReading(undef, "$name remoteFunction.*");
- my $count = 1;
- my $len = length($data);
- while ($len > 0) {
- $data =~ m/^.(...).(...)(.*)$/;
- push @event, "3:remoteFunction" . sprintf("%02d", $count) . ":$1:$2:" . (exists($EnO_extendedRemoteFunctionCode{hex($1)}) ? $EnO_extendedRemoteFunctionCode{hex($1)} : '-');
- $count ++;
- $data = $3;
- $len -= 8;
- }
- $remoteLastStatusReturnCode = '00';
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- Log3 $name, 2, "EnOcean $name RMCC query function answer received";
- } elsif ($funcNumber == 0x608 && $remoteManagement eq 'manager') {
- # query status answer
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- push @event, "3:remoteLastFunctionNumber:" . substr($data, 3, 3);
- push @event, "3:remoteLastStatusReturnCode:" . substr($data, 6, 2);
- Log3 $name, 2, "EnOcean $name RMCC query status answer received LastFunction: " . substr($data, 3, 3) .
- " LastFunctionCode: " . substr($data, 6, 2);
- } elsif ($funcNumber == 0x810 && $remoteManagement eq 'manager') {
- # teach-in supported link tables response
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- my $supportFlags = hex(substr($data, 0, 2));
- my $linkTableInCurrent = substr($data, 6, 2) && $supportFlags & 0x10 ? substr($data, 6, 2) : '00';
- my $linkTableInMax = substr($data, 8, 2) && $supportFlags & 0x10 ? substr($data, 8, 2) : '00';
- my $linkTableOutCurrent = substr($data, 2, 2) && $supportFlags & 0x20 ? substr($data, 2, 2) : '00';
- my $linkTableOutMax = substr($data, 4, 2) && $supportFlags & 0x20 ? substr($data, 4, 2) : '00';
- push @event, "3:remoteLearn:" . ($supportFlags & 0x80 ? 'supported' : 'not_supported');
- push @event, "3:remoteLinkTableIn:" . ($supportFlags & 0x10 ? 'supported' : 'not_supported');
- push @event, "3:remoteLinkTableOut:" . ($supportFlags & 0x20 ? 'supported' : 'not_supported');
- push @event, "3:remoteLinkTableInCurrent:" . $linkTableInCurrent if ($supportFlags & 0x10);
- push @event, "3:remoteLinkTableInMax:" . $linkTableInMax if ($supportFlags & 0x10);
- push @event, "3:remoteLinkTableOutCurrent:" . $linkTableOutCurrent if ($supportFlags & 0x20);
- push @event, "3:remoteLinkTableOutMax:" . $linkTableOutMax if ($supportFlags & 0x20);
- push @event, "3:remoteTeach:" . ($supportFlags & 0x40 ? 'supported' : 'not_supported');
- Log3 $name, 2, "EnOcean $name RPC teach-in supported link tables response received Data: $data";
- # request outbound table
- #$hash->{IODev}{helper}{remoteAnswerWait}{0x821}{hash} = $hash;
- #$hash->{IODev}{helper}{remoteLinkTableStartRef} = 0;
- #my %functionHash = (hash => $hash, param => 0x821);
- #RemoveInternalTimer(\%functionHash);
- #InternalTimer(gettimeofday() + 2.5, 'EnOcean_cdmClearRemoteWait', \%functionHash, 0);
- #$sendData = '022107FF';
- #EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
- #Log3 $name, 2, "EnOcean $name RPC request teach-in outbound table";
- } elsif ($funcNumber == 0x811 && $remoteManagement eq 'manager') {
- # link table response
- CommandDeleteReading(undef, "$name remoteLinkTableDesc.*");
- $data =~ m/^(..)(.*)$/;
- my $direction = hex($1) & 0x80 ? 'Out' : 'In';
- $data = $2;
- while (length($data) > 0) {
- $data =~ m/^(..)(........)(..)(..)(..)(..)(.*)$/;
- push @event, "3:remoteLinkTableDesc" . $direction . "$1:S2:S3-S4-$5:$6";
- $data = $7;
- }
- $remoteLastStatusReturnCode = '00';
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- Log3 $name, 2, "EnOcean $name RPC link table response received";
- } elsif ($funcNumber == 0x813 && $remoteManagement eq 'manager') {
- # link table GP response
- CommandDeleteReading(undef, "$name remoteLinkTableGPDesc.*");
- $data =~ m/^(..)(.*)$/;
- my $direction = hex($1) & 0x80 ? 'Out' : 'In';
- $data = $2;
- my $channel = 0;
- my $channelDir = hex($1) & 0x80 ? 'O' : 'I';
- my $channelDef;
- my $channelName;
- my $channelType;
- my $dataOutboundDefLen = 0;
- my $gpData;
- my @gpDef;
- my $gpIdx;
- my $signalType;
- while (length($data) > 0) {
- $data =~ m/^(..)(.{12})(.*)$/;
- $gpIdx = $1;
- $gpData = EnOcean_convHexToBit($2);
- $data = $3;
- $gpData =~ m/^(..)(.{8})(.*)$/;
- $channelType = unpack('C', pack('B8', '000000' . $1));
- $signalType = unpack('C', pack('B8', $2));
- $data = $3;
- #Log3 $name, 2, "EnOcean $name parse RPC link table GP idx: $gpIdx channelType: $channelType signalType: $signalType data: $data";
- if ($channelType == 0) {
- # teach-in information
- if ($signalType == 1) {
- # outbound channel description
- } elsif ($signalType == 2) {
- # produkt ID
- }
- } elsif ($channelType == 1) {
- # data
- $gpData =~ m/^(..)(....)(.{8})(....)(.{8})(....)(.*)$/;
- $channelDef = $channelDir . ':' . $channelType . ':' . $signalType . ':' .
- unpack('C', pack('B8', '000000' . $1)) . ':' . unpack('C', pack('B8', '0000' . $2)) . ':' .
- unpack('c', pack('B8', $3)) . ':' . unpack('C', pack('B8', '0000' . $4)) . ':' .
- unpack('c', pack('B8', $5)) . ':' . unpack('C', pack('B8', '0000' . $6));
- if (defined $EnO_gpValueData{$signalType}{name}) {
- $channelName = $EnO_gpValueData{$signalType}{name};
- } else {
- $channelName = "none";
- }
- $gpDef[$channel] = $channelName . ':' . $channelDef;
- } elsif ($channelType == 2) {
- # flag
- $gpData =~ m/^(..)(.*)$/;
- $channelDef = $channelDir . ':' .$channelType . ':' . $signalType . ':' .
- unpack('C', pack('B8', '000000' . $1));
- if (defined $EnO_gpValueFlag{$signalType}{name}) {
- $channelName = $EnO_gpValueFlag{$signalType}{name};
- } else {
- $channelName = "none";
- }
- $gpDef[$channel] = $channelName . ':' . $channelDef;
- } elsif ($channelType == 3) {
- # enumeration
- $gpData =~ m/^(..)(....)(.*)$/;
- $channelDef = $channelDir . ':' .$channelType . ':' . $signalType . ':' .
- unpack('C', pack('B8', '000000' . $1)) . ':' . unpack('C', pack('B8', '0000' . $2));
- if (defined $EnO_gpValueEnum{$signalType}{name}) {
- $channelName = $EnO_gpValueEnum{$signalType}{name};
- } else {
- $channelName = "none";
- }
- $gpDef[$channel] = $channelName . ':' . $channelDef;
- }
- push @event, "3:remoteLinkTableGPDesc" . $direction . "$gpIdx:$gpDef[0]";
- }
- $remoteLastStatusReturnCode = '00';
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- Log3 $name, 2, "EnOcean $name RPC link table GP response received";
- } elsif ($funcNumber == 0x827 && $remoteManagement eq 'manager') {
- # product ID answer
- $manufID = substr($data, 1, 3);
- $attr{$name}{remoteManufID} = $manufID;
- $manufID = $EnO_manuf{$manufID} if($EnO_manuf{$manufID});
- $remoteLastStatusReturnCode = '00';
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- $sendData = '024007FF';
- EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $sendData, '0' x 8, '0F', $senderID);
- push @event, "3:remoteProductID:" . substr($data, 4, 8);
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- Log3 $name, 2, "EnOcean $name RPC Product ID answer received ProductID: " . substr($data, 4, 8) . " Manufacturer: $manufID";
- Log3 $name, 2, "EnOcean $name RPC acknowledge sent";
- EnOcean_CommandSave(undef, undef);
- } elsif ($funcNumber == 0x830 && $remoteManagement eq 'manager') {
- # device config response
- CommandDeleteReading(undef, "$name remoteDevCfg.*");
- my $idx;
- my $valueLen;
- while (length($data) > 0) {
- $data =~ m/^(....)(..)(.*)$/;
- $idx = $1;
- $valueLen = hex($2) * 2;
- $data = $3;
- $data =~ m/^(.{$valueLen})(.*)$/;
- push @event, "3:remoteDevCfg$idx:S1";
- $data = $2;
- }
- $remoteLastStatusReturnCode = '00';
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- Log3 $name, 2, "EnOcean $name RPC device configuration response received";
- } elsif ($funcNumber == 0x832 && $remoteManagement eq 'manager') {
- # link based configuration response
- $data =~ m/^(..)(..)(.*)$/;
- my $direction = hex($1) & 0x80 ? 'Out' : 'In';
- my $linkTableIdx = $2;
- CommandDeleteReading(undef, "$name remoteLinkCfg$direction$linkTableIdx.*");
- $data = $3;
- my $idx;
- my $valueLen;
- while (length($data) > 0) {
- $data =~ m/^(....)(..)(.*)$/;
- $idx = $1;
- $valueLen = hex($2) * 2;
- $data = $3;
- $data =~ m/^(.{$valueLen})(.*)$/;
- push @event, "3:remoteLinkCfg$direction$linkTableIdx:$idx:S1";
- $data = $2;
- }
- $remoteLastStatusReturnCode = '00';
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- push @event, "3:remoteLastStatusReturnCode:$remoteLastStatusReturnCode";
- Log3 $name, 2, "EnOcean $name RPC link table response received";
- } elsif ($funcNumber == 0x850 && $remoteManagement eq 'manager') {
- # query status answer
- delete $iohash->{helper}{remoteAnswerWait}{$funcNumber}{hash};
- my $repeaterFunction = 'off';
- if (($db[0] & 0xC0) == 0) {
- $repeaterFunction = 'off';
- } elsif (($db[0] & 0xC0) == 0x40) {
- $repeaterFunction = 'on';
- } elsif (($db[0] & 0xC0) == 0x80) {
- $repeaterFunction = 'filter';
- }
- push @event, "3:remoteRepeaterFunction:$repeaterFunction";
- push @event, "3:remoteRepeaterLevel:" . (($db[0] & 0x30) == 0x20 ? 2 : 1);
- push @event, "3:remoteRepeaterFilter:" . ($db[0] & 8 ? 'OR' : 'AND');
- Log3 $name, 2, "EnOcean $name RPC repeater functions response received";
- } else {
- Log3 $name, 2, "EnOcean $name RMCC/RPC function number " . sprintf("%03X", $funcNumber) . " not supported.";
- }
- } elsif ($rorg eq "C5" && $packetType == 1) {
- # remote management >> packetType = 7
- return $name;
- }
- readingsBeginUpdate($hash);
- for(my $i = 0; $i < int(@event); $i++) {
- # Flag & 1: reading, Flag & 2: changed. Currently ignored.
- my ($flag, $vn, $vv) = split(':', $event[$i], 3);
- readingsBulkUpdate($hash, $vn, $vv);
- my @cmdObserve = ($name, $vn, $vv);
- EnOcean_observeParse(2, $hash, @cmdObserve);
- }
- readingsEndUpdate($hash, 1);
- if (defined $deleteDevice) {
- # delete device and save config
- CommandDelete(undef, $deleteDevice);
- Log3 $name, 2, "EnOcean $name device $deleteDevice deleted";
- if (defined $oldDevice) {
- Log3 $name, 2, "EnOcean $name renamed $oldDevice to $deleteDevice";
- CommandRename(undef, "$oldDevice $deleteDevice");
- EnOcean_CommandSave(undef, undef);
- return $deleteDevice;
- } else {
- EnOcean_CommandSave(undef, undef);
- return '';
- }
- }
- return $name;
- }
- sub EnOcean_Attr(@)
- {
- my ($cmd, $name, $attrName, $attrVal) = @_;
- my $hash = $defs{$name};
- # return if attribute list is incomplete
- return undef if (!$init_done);
- my $err;
- my $loglevel = 2;
- my $waitingCmds = AttrVal($name, "waitingCmds", 0);
- if ($attrName eq "angleTime") {
- my $channel;
- my $data;
- if (!defined $attrVal) {
- if (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
- # no rotation
- $data = "7FFF0007F5";
- EnOcean_SndRadio(undef, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- }
- } elsif (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
- my @attrVal = split(':', $attrVal);
- for (my $channel = 0; $channel <= $#attrVal && $channel < 4; $channel ++) {
- if ($attrVal[$channel] =~ m/^\d+(\.\d+)?$/ && $attrVal[$channel] >= 0 && $attrVal[$channel] <= 2.54) {
- if ($attrVal[$channel] < 0.01) {
- $attrVal[$channel] = 0;
- } else {
- $attrVal[$channel] = int($attrVal[$channel] * 100);
- }
- $data = sprintf "7FFF%02X07%02X", $attrVal[$channel], $channel << 4 | 5;
- EnOcean_SndRadio(0.2 + $channel * 0.5, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- }
- } elsif (AttrVal($name, "subType", "") eq "manufProfile" && AttrVal($name, "manufID", "") eq "00D" &&
- $attrVal =~ m/^[+-]?\d+?$/ && $attrVal >= 1 && $attrVal <= 6) {
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- #Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong";
- #CommandDeleteAttr(undef, "$name $attrName");
- }
- } elsif ($attrName eq "alarmAction") {
- my $data;
- if (!defined $attrVal) {
- if (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
- # no alarm action
- $data = "7FFFFF00F5";
- EnOcean_SndRadio(undef, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- }
- } elsif (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
- my @attrVal = split(':', $attrVal);
- for (my $channel = 0; $channel <= $#attrVal && $channel < 4; $channel ++) {
- if ($attrVal =~ m/no|stop|opens|closes$/) {
- my $alarmAction = 0;
- if ($attrVal[$channel] eq "no") {
- $alarmAction = 0;
- } elsif ($attrVal[$channel] eq "stop") {
- $alarmAction = 1;
- } elsif ($attrVal[$channel] eq "opens") {
- $alarmAction = 2;
- } elsif ($attrVal[$channel] eq "closes") {
- $alarmAction = 3;
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- $data = sprintf "7FFFFF%02X%02X", $alarmAction, $channel << 4 | 5;
- EnOcean_SndRadio(0.2 + $channel * 0.5, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- }
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName =~ m/^block.*/) {
- if (!defined $attrVal){
- } elsif ($attrVal =~ m/^(no|yes)$/) {
- if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
- $waitingCmds |= 64;
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
- }
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "comMode") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^biDir|uniDir|confirm$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "creator") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^autocreate|manual$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "dataEnc") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^VAES|AES-CBC$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "daylightSavingTime") {
- if (!defined $attrVal){
- } elsif ($attrVal =~ m/^(supported|not_supported)$/) {
- if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
- $waitingCmds |= 64;
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
- }
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "defaultChannel") {
- my $defaultChannel = '';
- my $subType = AttrVal($name, "subType", "");
- if ($subType eq "actuator.01") {
- $defaultChannel = join("|", @EnO_defaultChannel);
- } elsif ($subType =~ m/^blindsCtrl\.0[01]$/) {
- $defaultChannel = '[1234]|all';
- }
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^($defaultChannel)$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "demandRespAction") {
- if (!defined $attrVal){
- } else {
- my %specials = ("%TARGETNAME" => $name,
- "%NAME" => $name,
- "%TARGETTYPE" => '',
- "%TYPE" => '',
- "%LEVEL" => '',
- "%SETPOINT" => '',
- "%POWERUSAGE" => '',
- "%POWERUSAGESCALE" => '',
- "%POWERUSAGELEVEL" => '',
- "%TARGETSTATE" => '',
- "%STATE" => ''
- );
- $err = perlSyntaxCheck($attrVal, %specials);
- }
- } elsif ($attrName eq "demandRespRandomTime") {
- if (!defined $attrVal) {
- } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 1) {
- $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
- }
- } elsif ($attrName =~ m/^(demandRespMax|demandRespMin)$/) {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^(A0|AI|B0|BI|C0|CI|D0|DI)$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- CommandDeleteAttr(undef, "$name $attrName");
- }
- } elsif ($attrName eq "demandRespThreshold") {
- if (!defined $attrVal) {
- } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 0 || $attrVal > 15) {
- $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
- }
- } elsif ($attrName eq "devChannel") {
- if (!defined $attrVal){
- } elsif ($attrVal =~ m/^[\dA-Fa-f]{2}$/) {
- # actions see EnOcean_Notify, global ATTR
- } elsif ($attrVal =~ m/^\d+$/ && $attrVal >= 0 && $attrVal <= 255) {
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "devMode") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^master|slave$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "devUpdate") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^(off|auto|demand|polling|interrupt)$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName =~ m/^dimMax|dimMin$/) {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^off|[\d+]$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "displayContent") {
- if (!defined $attrVal){
- } elsif ($attrVal =~ m/^(humidity|off|setpointTemp|tempertureExtern|temperatureIntern|time|default|no_change)$/) {
- if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
- $waitingCmds |= 64;
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
- }
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "displayOrientation") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^0|90|180|270$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "eep" || $attrName eq "remoteEEP") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^[\dA-Fa-f]{2}-[\dA-Fa-f]{2}-[\dA-Fa-f]{2}$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "gpDef") {
- if (!defined $attrVal){
- } else {
- my @gpDef = split("[ \t][ \t]*", $attrVal);
- my ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax);
- for (my $channel = 0; $channel < @gpDef; $channel ++) {
- my @err;
- ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
- split(':', $gpDef[$channel]);
- push(@err, "channelName") if (!defined $channelName);
- push(@err, "channelDir") if (!defined($channelDir) || $channelDir !~ m/^O|I$/);
- push(@err, "channelType") if (!defined($channelType) || $channelType !~ m/^\d+$/ || $channelType > 3);
- push(@err, "signalType") if (!defined($signalType) || $signalType !~ m/^\d+$/ || $signalType > 255);
- push(@err, "valueType") if (!defined($valueType) || $valueType !~ m/^\d+$/ || $valueType > 3);
- if ($channelType == 1 || $channelType == 3) {
- push(@err, "resolution") if (!defined($resolution) || $resolution !~ m/^\d+$/ || $resolution > 12);
- }
- if ($channelType == 1) {
- push(@err, "engMin") if (!defined($engMin) || $engMin !~ m/^[+-]?\d+$/ || $engMin < -128 || $engMin > 127);
- push(@err, "scalingMin") if (!defined($scalingMin) || $scalingMin !~ m/^\d+$/ || $scalingMin < 1 || $scalingMin > 13);
- push(@err, "engMax") if (!defined($engMax) || $engMax !~ m/^[+-]?\d+$/ || $engMax < -128 || $engMax > 127);
- push(@err, "scalingMax") if (!defined($scalingMax) || $scalingMax !~ m/^\d+$/ || $scalingMax < 1 || $scalingMax > 13);
- }
- $err = "attribute-value $attrName/channel " . sprintf('%02d', $channel) . ": " .
- join(', ', @err) . " wrong" if (defined $err[0]);
- }
- }
- } elsif ($attrName eq "humidity") {
- if (!defined $attrVal) {
- } elsif ($attrVal =~ m/^\d+$/ && $attrVal >= 0 && $attrVal <= 100) {
- } else {
- #RemoveInternalTimer($hash);
- $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
- }
- } elsif ($attrName =~ m/^key/) {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^[\dA-Fa-f]{32}$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "macAlgo") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^no|[3-4]$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "manufID" || $attrName eq "remoteManufID") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^[0-7][\dA-Fa-f]{2}$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "measurementCtrl") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^disable|enable$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "observe") {
- if (!defined $attrVal){
- } elsif (lc($attrVal) !~ m/^(off|on)$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "observeCmdRepetition") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^[1-5]$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "observeErrorAction") {
- if (!defined $attrVal){
- } else {
- my %specials = ("%NAME" => $name,
- "%FAILEDDEV" => '',
- "%TYPE" => '',
- "%EVENT" => ''
- );
- $err = perlSyntaxCheck($attrVal, %specials);
- }
- } elsif ($attrName eq "observeInterval") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 1) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "observeLogic") {
- if (!defined $attrVal){
- } elsif (lc($attrVal) !~ m/^and|or$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "pidActorErrorAction") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^errorPos|freeze$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "pidCtrl") {
- if (!defined $attrVal){
- } elsif (lc($attrVal) eq "on") {
- EnOcean_setPID(undef, $hash, 'start', ReadingsVal($name, "setpoint", ''));
- } elsif (lc($attrVal) eq "off") {
- EnOcean_setPID(undef, $hash, 'stop', '');
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "pidActorErrorPos") {
- if (!defined $attrVal) {
- } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 0 || $attrVal > 100) {
- $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
- }
- } elsif ($attrName eq "pidDeltaTreshold") {
- my $reFloatpos = '^([\\+]?\\d+\\.?\d*$)';
- if (!defined $attrVal) {
- } elsif ($attrVal !~ m/$reFloatpos/) {
- $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
- }
- } elsif ($attrName eq "pidSensorTimeout") {
- if (!defined $attrVal) {
- } elsif ($attrVal !~ m/^\d+?$/) {
- $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
- }
- } elsif ($attrName =~ m/^pidActorLimitLower|pidActorLimitUpper$/) {
- if (!defined $attrVal) {
- } elsif ($attrVal !~ m/^\d+?$/ || $attrVal < 0 || $attrVal > 100) {
- $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
- }
- } elsif ($attrName =~ m/^pidFactor_.$/) {
- my $reFloatpos = '^([\\+]?\\d+\\.?\d*$)';
- if (!defined $attrVal) {
- } elsif ($attrVal !~ m/$reFloatpos/) {
- $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
- }
- } elsif ($attrName eq "pollInterval") {
- if (!defined $attrVal) {
- } elsif ($attrVal =~ m/^\d+?$/) {
- if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
- $waitingCmds |= 64;
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
- }
- } else {
- #RemoveInternalTimer($hash);
- $err = "attribute-value [$attrName] = $attrVal is not a integer number or not valid";
- }
- } elsif ($attrName eq "productID") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^[\dA-Fa-f]{8}$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "rampTime") {
- if (!defined $attrVal){
- } elsif ($attrVal =~ m/^\d+?$/) {
- if (AttrVal($name, "subType", "") eq "gateway") {
- if ($attrVal < 0 || $attrVal > 255) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif (AttrVal($name, "subType", "") eq "lightCtrl.01") {
- if ($attrVal < 0 || $attrVal > 65535) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } else {
- $err = "attribute $attrName not supported for subType " . AttrVal($name, "subType", "");
- }
- } elsif ($attrName eq "releasedChannel") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^A|B|C|D|I|0|auto$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "rcvRespAction") {
- if (!defined $attrVal){
- } elsif (AttrVal($name, "subType", "") eq "hvac.01") {
- my %specials = ("%ACTUATORSTATE" => '',
- "%BATTERY" => '',
- "%COVER" => '',
- "%ENERGYINPUT" => '',
- "%ENERGYSTORAGE" => '',
- "%MAINTENANCEMODE" => '',
- "%NAME" => $name,
- "%OPERATIONMODE" => '',
- "%ROOMTEMP" => '',
- "%SELFCTRL" => '',
- "%SETPOINT" => '',
- "%SETPOINTTEMP" => '',
- "%SUMMERMODE" => '',
- "%TEMPERATURE" => '',
- "%WINDOW" => ''
- );
- $err = perlSyntaxCheck($attrVal, %specials);
- } elsif (AttrVal($name, "subType", "") eq "hvac.04") {
- my %specials = ("%BATTERY" => '',
- "%FEEDTEMP" => '',
- "%MAINTENANCEMODE" => '',
- "%NAME" => $name,
- "%OPERATIONMODE" => '',
- "%ROOMTEMP" => '',
- "%SETPOINT" => '',
- "%SETPOINTTEMP" => '',
- "%SUMMERMODE" => '',
- "%TEMPERATURE" => ''
- );
- $err = perlSyntaxCheck($attrVal, %specials);
- }
- } elsif ($attrName eq "remoteID") {
- if (!defined $attrVal){
- # delete old pointer
- delete $modules{EnOcean}{defptr}{$attr{$name}{$attrName}} if (exists($attr{$name}{$attrName}) && $attr{$name}{$attrName} ne $hash->{DEF});
- } elsif ($attrVal =~ m/^[\dA-F]{8}$/) {
- # delete old pointer
- delete $modules{EnOcean}{defptr}{$attr{$name}{$attrName}} if (exists($attr{$name}{$attrName}) && $attr{$name}{$attrName} ne $hash->{DEF});
- $modules{EnOcean}{defptr}{$attrVal} = $hash;
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "remoteManagement") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^client|manager|off$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "reposition") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^directly|opens|closes$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName =~ m/^rlcRcv|rlcSnd$/) {
- if (!defined $attrVal){
- } elsif (AttrVal($name, "rlcAlgo", "") eq "2++" && $attrVal =~ m/^[\dA-Fa-f]{4}$/) {
- } elsif (AttrVal($name, "rlcAlgo", "") eq "3++" && $attrVal =~ m/^[\dA-Fa-f]{6}$/) {
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "rlcAlgo") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^no|2++|3++$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "rlcTX") {
- if (!defined $attrVal){
- } elsif (lc($attrVal) !~ m/^true|false$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "rltType") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^1BS|4BS$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "rltRepeat") {
- if (!defined $attrVal){
- } elsif ($attrVal =~ m/^\d+?$/ && $attrVal >= 16 && $attrVal <= 256) {
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "remoteCode") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^[\dA-Fa-f]{8}$/ || $attrVal eq "00000000" || uc($attrVal) eq "FFFFFFFF") {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "secLevel") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^encapsulation|encryption|off$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "secMode") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^rcv|snd|bidir$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "sendDevStatus") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^no|yes$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "serviceOn") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^(no|yes)$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "setCmdTrigger") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^man|refDev$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "setpointSummerMode") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^\d+$/ && $attrVal >= 0 && $attrVal <= 100) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "settingAccuracy") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^high|low$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "shutTime") {
- my $data;
- if (!defined $attrVal) {
- if (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
- # set shutTime to max
- $data = "7530FF07F5";
- EnOcean_SndRadio(undef, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- }
- } elsif (AttrVal($name, "subType", "") =~ m/^blindsCtrl\.0[01]$/) {
- my @attrVal = split(':', $attrVal);
- for (my $channel = 0; $channel <= $#attrVal && $channel < 4; $channel ++) {
- if ($attrVal[$channel] =~ m/^\d+$/ && $attrVal[$channel] >= 5 && $attrVal[$channel] <= 300) {
- $attrVal[$channel] = int($attrVal[$channel] * 100);
- $data = sprintf "%04XFF07%02X", $attrVal[$channel], $channel << 4 | 5;
- EnOcean_SndRadio(0.2 + $channel * 0.5, $hash, 1, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- }
- } elsif (AttrVal($name, "subType", "") eq "manufProfile" && AttrVal($name, "manufID", "") eq "00D" &&
- $attrVal =~ m/^[+-]?\d+$/ && $attrVal >= 1 && $attrVal <= 255) {
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "summerMode") {
- if (!defined $attrVal){
- } elsif ($attrVal eq 'on') {
- if (AttrVal($name, 'subType', '') =~ m/^hvac\.0(1|4)$/ && AttrVal($name, 'summerMode', 'off') eq 'off') {
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, 'waitingCmds', 'summerMode');
- readingsBulkUpdate($hash, 'operationModeRestore', ReadingsVal($name, 'operationMode', 'setpoint'));
- readingsBulkUpdate($hash, 'setpointTempRestore', ReadingsVal($name, 'setpointTemp', 20));
- readingsBulkUpdate($hash, 'operationMode', 'setpoint');
- readingsEndUpdate($hash, 0);
- } else {
- # attr not changed
- }
- } elsif ($attrVal eq 'off') {
- if (AttrVal($name, 'subType', '') =~ m/^hvac\.0(1|4)$/ && AttrVal($name, 'summerMode', 'off') eq 'on') {
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, 'waitingCmds', 'runInit');
- readingsBulkUpdate($hash, 'operationMode', ReadingsVal($name, 'operationModeRestore', 'setpoint'));
- readingsBulkUpdate($hash, 'setpointTemp', ReadingsVal($name, 'setpointTempRestore', 20));
- readingsEndUpdate($hash, 0);
- CommandDeleteReading(undef, "$name .*Restore");
- } else {
- # attr not changed
- }
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName =~ m/^subDef.?|postmasterID/) {
- if (!defined $attrVal){
- } elsif ($attrVal eq "getNextID") {
- # actions see EnOcean_Notify, global ATTR
- } elsif ($attrVal !~ m/^[\dA-Fa-f]{8}$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "switchHysteresis") {
- if (!defined $attrVal) {
- } elsif ($attrVal =~ m/^\d+(\.\d+)?$/ && $attrVal >= 0.1) {
- } else {
- #RemoveInternalTimer($hash);
- $err = "attribute-value [$attrName] = $attrVal is not a valid number";
- }
- } elsif ($attrName eq "teachMethod") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^1BS|4BS|confirm|GP|RPS|smartAck|STE|UTE$$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "temperatureScale") {
- if (!defined $attrVal){
- } elsif ($attrVal =~ m/^(C|F|default|no_change)$/) {
- if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
- $waitingCmds |= 64;
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
- }
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "timeNotation") {
- if (!defined $attrVal){
- } elsif ($attrVal =~ m/^(12|24|default|no_change)$/) {
- if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
- $waitingCmds |= 64;
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
- }
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName =~ m/^timeProgram[1-4]$/) {
- if (!defined $attrVal){
- } 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)$/) {
- if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
- # delete remote and send new time program
- delete $hash->{helper}{4}{telegramWait};
- $hash->{helper}{4}{telegramWait}{substr($attrName,-1,1) + 0} = 1;
- for (my $messagePartCntr = 1; $messagePartCntr <= 4; $messagePartCntr ++) {
- if (defined AttrVal($name, "timeProgram" . $messagePartCntr, undef)) {
- $hash->{helper}{4}{telegramWait}{$messagePartCntr} = 1;
- }
- }
- $waitingCmds |= 528;
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
- }
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "trackerWakeUpCycle") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^10|20|30|40|60|120|180|240|3600|86400$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "updateState") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^default|yes|no$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "uteResponseRequest") {
- if (!defined $attrVal){
- } elsif ($attrVal !~ m/^(yes|no)$/) {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- } elsif ($attrName eq "wakeUpCycle") {
- my $wakeUpCycle = join("|", keys %wakeUpCycle);
- if (!defined $attrVal){
- } elsif ($attrVal =~ m/^$wakeUpCycle$/) {
- if ($attrVal >= 1500 && AttrVal($name, 'wakeUpCycle', 300) < 1500) {
- # switch to summer mode
- $attr{$name}{summerMode} = 'on';
- readingsSingleUpdate($hash, 'waitingCmds', 'summerMode', 0);
- } elsif ($attrVal < 1500 && AttrVal($name, 'wakeUpCycle', 300) >= 1500) {
- # runInit necessary before switch to operation mode
- $attr{$name}{summerMode} = 'off';
- readingsSingleUpdate($hash, 'waitingCmds', 'runInit', 0);
- }
- } else {
- $err = "attribute-value [$attrName] = $attrVal wrong";
- }
- }
- return $err;
- }
- sub EnOcean_Notify(@)
- {
- my ($hash, $dev) = @_;
- my $name = $hash->{NAME};
- my $devName = $dev->{NAME};
- return undef if (AttrVal($name ,"disable", 0) > 0);
- my $max = int(@{$dev->{CHANGED}});
- for (my $i = 0; $i < $max; $i++) {
- my $s = $dev->{CHANGED}[$i];
- $s = "" if(!defined($s));
- if ($devName eq $name) {
- my @parts = split(/: | /, $s);
- #####
- if (exists($hash->{helper}{stopped}) && !$hash->{helper}{stopped} && $parts[0] eq "temperature") {
- # PID regulator: calc gradient for delta as base for d-portion calculation
- my $setpointTemp = ReadingsVal($name, "setpointTemp", undef);
- my $temperature = $parts[1];
- # ---- build difference current - old value
- # calc difference of delta/deltaOld
- my $delta = $setpointTemp - $temperature if (defined($setpointTemp));
- my $deltaOld = ($hash->{helper}{deltaOld} + 0) if (defined($hash->{helper}{deltaOld}));
- my $deltaDiff = ($delta - $deltaOld) if (defined($delta) && defined($deltaOld));
- # ----- build difference of timestamps
- my $deltaOldTsStr = $hash->{helper}{deltaOldTS};
- my $deltaOldTsNum = time_str2num($deltaOldTsStr) if (defined($deltaOldTsStr));
- my $nowTsNum = gettimeofday();
- my $tsDiff = ($nowTsNum - $deltaOldTsNum)
- if (defined($deltaOldTsNum) && (($nowTsNum - $deltaOldTsNum) > 0));
- # ----- calculate gradient of delta
- my $deltaGradient = $deltaDiff / $tsDiff
- if (defined($deltaDiff) && defined($tsDiff) && ($tsDiff > 0 ));
- $deltaGradient = 0 if ( !defined($deltaGradient) );
- # ----- store results
- $hash->{helper}{deltaGradient} = $deltaGradient;
- $hash->{helper}{deltaOld} = $delta;
- $hash->{helper}{deltaOldTS} = TimeNow();
- Log3 $name, 5, "EnOcean $name <notify> $devName $s";
- }
- } elsif ($devName eq "global" && $s =~ m/^RENAMED ([^ ]*) ([^ ]*)$/) {
- if (defined AttrVal($name, "temperatureRefDev", undef)) {
- if (AttrVal($name, "temperatureRefDev", undef) eq $1) {
- CommandAttr(undef, "$name temperatureRefDev $2");
- }
- } elsif (defined AttrVal($name, "setpointRefDev", undef)) {
- if (AttrVal($name, "setpointRefDev", undef) eq $1) {
- CommandAttr(undef, "$name setpointRefDev $2");
- }
- } elsif (defined AttrVal($name, "setpointTempRefDev", undef)) {
- if (AttrVal($name, "setpointTempRefDev", undef) eq $1) {
- CommandAttr(undef, "$name setpointTempRefDev $2");
- }
- } elsif (defined AttrVal($name, "humidityRefDev", undef)) {
- if (AttrVal($name, "humidityRefDev", undef) eq $1) {
- CommandAttr(undef, "$name humidityRefDev $2");
- }
- } elsif (defined AttrVal($name, "observeRefDev", undef)) {
- if (AttrVal($name, "observeRefDev", undef) eq $1) {
- CommandAttr(undef, "$name observeRefDev $2");
- }
- } elsif (defined AttrVal($name, "demandRespRefDev", undef)) {
- if (AttrVal($name, "demandRespRefDev", undef) eq $1) {
- CommandAttr(undef, "$name demandRespRefDev $2");
- }
- }
- #Log3($name, 5, "EnOcean $name <notify> RENAMED old: $1 new: $2");
- } elsif ($devName eq "global" && $s =~ m/^DELETED ([^ ]*)$/) {
- # delete attribute *RefDev
- if (defined AttrVal($name, "temperatureRefDev", undef)) {
- if (AttrVal($name, "temperatureRefDev", undef) eq $1) {
- CommandDeleteAttr(undef, "$name temperatureRefDev");
- }
- } elsif (defined AttrVal($name, "setpointRefDev", undef)) {
- if (AttrVal($name, "setpointRefDev", undef) eq $1) {
- CommandDeleteAttr(undef, "$name setpointRefDev");
- }
- } elsif (defined AttrVal($name, "setpointTempRefDev", undef)) {
- if (AttrVal($name, "setpointTempRefDev", undef) eq $1) {
- CommandDeleteAttr(undef, "$name setpointTempRefDev");
- }
- } elsif (defined AttrVal($name, "humidityRefDev", undef)) {
- if (AttrVal($name, "humidityRefDev", undef) eq $1) {
- CommandDeleteAttr(undef, "$name humidityRefDev");
- }
- } elsif (defined AttrVal($name, "observeRefDev", undef)) {
- if (AttrVal($name, "observeRefDev", undef) eq $1) {
- CommandDeleteAttr(undef, "$name observeRefDev");
- }
- } elsif (defined AttrVal($name, "demandRespRefDev", undef)) {
- if (AttrVal($name, "demandRespRefDev", undef) eq $1) {
- CommandDeleteAttr(undef, "$name demandRespRefDev");
- }
- }
- #Log3($name, 5, "EnOcean $name <notify> DELETED $1");
- } elsif ($devName eq "global" && $s =~ m/^DEFINED ([^ ]*)$/) {
- my $definedName = $1;
- if ($name eq $definedName) {
- if (exists($attr{$name}{subType}) && $attr{$name}{subType} =~ m/^hvac\.0(1|4)$/) {
- # control PID regulatior
- if (AttrVal($name, 'pidCtrl', 'on') eq 'on' && ReadingsVal($name, 'maintenanceMode', 'off') eq 'off') {
- EnOcean_setPID(undef, $hash, 'start', ReadingsVal($name, "setpoint", ''));
- } else {
- EnOcean_setPID(undef, $hash, 'stop', '');
- }
- }
- }
- # teach-in response actions
- # delete temporary teach-in response device, see V9333_02
- #Log3($name, 2, "EnOcean $name <notify> DEFINED $definedName");
- } elsif ($devName eq "global" && $s =~ m/^INITIALIZED$/) {
- # assign remote management defptr
- if (exists $attr{$name}{remoteID}) {
- $modules{EnOcean}{defptr}{$attr{$name}{remoteID}} = $hash;
- }
- if (AttrVal($name ,"subType", "") eq "roomCtrlPanel.00") {
- CommandDeleteReading(undef, "$name waitingCmds");
- }
- if (exists($attr{$name}{subType}) && $attr{$name}{subType} =~ m/^hvac\.0(1|4)$/) {
- # control PID regulatior
- if (AttrVal($name, 'pidCtrl', 'on') eq 'on' && ReadingsVal($name, 'maintenanceMode', 'off') eq 'off') {
- EnOcean_setPID(undef, $hash, 'start', ReadingsVal($name, "setpoint", ''));
- } else {
- EnOcean_setPID(undef, $hash, 'stop', '');
- }
- }
- if (exists($attr{$name}{subType}) && $attr{$name}{subType} eq "switch.05") {
- my @getCmd = ($name, 'state');
- EnOcean_Get($hash, @getCmd);
- }
- #Log3($name, 2, "EnOcean $name <notify> INITIALIZED");
- } elsif ($devName eq "global" && $s =~ m/^REREADCFG$/) {
- # assign remote management defptr
- if (exists $attr{$name}{remoteID}) {
- $modules{EnOcean}{defptr}{$attr{$name}{remoteID}} = $hash;
- }
- if (AttrVal($name ,"subType", "") eq "roomCtrlPanel.00") {
- CommandDeleteReading(undef, "$name waitingCmds");
- }
- if (exists($attr{$name}{subType}) && $attr{$name}{subType} =~ m/^hvac\.0(1|4)$/) {
- # control PID regulatior
- if (AttrVal($name, 'pidCtrl', 'on') eq 'on' && ReadingsVal($name, 'maintenanceMode', 'off') eq 'off') {
- EnOcean_setPID(undef, $hash, 'start', ReadingsVal($name, "setpoint", ''));
- } else {
- EnOcean_setPID(undef, $hash, 'stop', '');
- }
- }
- #Log3($name, 2, "EnOcean $name <notify> REREADCFG");
- } elsif ($devName eq "global" && $s =~ m/^ATTR ([^ ]*) ([^ ]*) ([^ ]*)$/) {
- my ($sdev, $attrName, $attrVal) = ($1, $2, $3);
- #Log3 $name, 5, "EnOcean $name <notify> ATTR $1 $2 $3";
- if ($name eq $sdev && $attrName =~ m/^subDef.?/ && $attrVal eq "getNextID") {
- $attr{$name}{$attrName} = '0' x 8;
- $attr{$name}{$attrName} = EnOcean_CheckSenderID("getNextID", $defs{$name}{IODev}{NAME}, "00000000");
- Log3 $name, 2, "EnOcean set $name attribute $attrName to " . $attr{$name}{$attrName};
- } elsif ($attrName eq "devChannel" && $attrVal =~ m/^[\dA-Fa-f]{2}$/) {
- # convert old format
- $attr{$name}{$attrName} = hex $attrVal;
- }
- } elsif ($devName eq "global" && $s =~ m/^DELETEATTR ([^ ]*)$/) {
- #Log3($name, 5, "EnOcean $name <notify> DELETEATTR $1");
- } elsif ($devName eq "global" && $s =~ m/^MODIFIED ([^ ]*)$/) {
- #Log3($name, 5, "EnOcean $name <notify> MODIFIED");
- } elsif ($devName eq "global" && $s =~ m/^SAVE$/) {
- #Log3($name, 5, "EnOcean $name <notify> SAVE");
- } elsif ($devName eq "global" && $s =~ m/^SHUTDOWN$/) {
- #Log3($name, 5, "EnOcean $name <notify> SHUTDOWN");
- } else {
- my @parts = split(/: | /, $s);
- if (defined AttrVal($name, "observeRefDev", undef)) {
- my @observeRefDev = split("[ \t][ \t]*", AttrVal($name, "observeRefDev", undef));
- if (grep /^$devName$/, @observeRefDev) {
- my ($reading, $value) = ("", "");
- $reading = shift @parts;
- if (!defined($parts[0]) || @parts > 1) {
- $value = $s;
- $reading = "state";
- } else {
- $value = $parts[0];
- }
- my @cmdObserve = ($devName, $reading, $value);
- EnOcean_observeParse(2, $hash, @cmdObserve);
- #Log3($name, 5, "EnOcean $name <notify> observeRefDev: $devName $reading: $value");
- }
- }
- if (defined AttrVal($name, "demandRespRefDev", undef)) {
- my @demandRespRefDev = split("[ \t][ \t]*", AttrVal($name, "demandRespRefDev", undef));
- if (grep /^$devName$/, @demandRespRefDev) {
- my @cmdDemandResponse;
- my $actionCmd = AttrVal($name, "demandRespAction", undef);
- if (defined $actionCmd) {
- if ($parts[0] =~ m/^on|off$/) {
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set $parts[0]");
- my %specials = ("%TARGETNAME" => $name,
- "%NAME" => $devName,
- "%TARGETTYPE" => $hash->{TYPE},
- "%TYPE" => $dev->{TYPE},
- "%LEVEL" => ReadingsVal($devName, "level", 15),
- "%SETPOINT" => ReadingsVal($devName, "setpoint", 255),
- "%POWERUSAGE" => ReadingsVal($devName, "powerUsage", 100),
- "%POWERUSAGESCALE" => ReadingsVal($devName, "powerUsageScale", "max"),
- "%POWERUSAGELEVEL" => ReadingsVal($devName, "powerUsageLevel", "max"),
- "%TARGETSTATE" => ReadingsVal($name, "state", ""),
- "%STATE" => ReadingsVal($devName, "state", "off")
- );
- # action exec
- $actionCmd = EvalSpecials($actionCmd, %specials);
- my $ret = AnalyzeCommandChain(undef, $actionCmd);
- Log3 $name, 2, "EnOcean $name demandRespAction ERROR: $ret" if($ret);
- }
- } elsif (AttrVal($name, "subType", "") =~ m/^switch.*$/ || AttrVal($name, "subTypeSet", "") =~ m/^switch.*$/) {
- if ($parts[0] eq "powerUsageLevel" && $parts[1] eq "max") {
- @cmdDemandResponse = ($name, AttrVal($name, "demandRespMax", "B0"));
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- } elsif ($parts[0] eq "powerUsageLevel" && $parts[1] eq "min") {
- @cmdDemandResponse = ($name, AttrVal($name, "demandRespMin", "BI"));
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- }
- } elsif (AttrVal($name, "subType", "") eq "gateway" && AttrVal($name, "gwCmd", "") eq "switching") {
- if ($parts[0] eq "powerUsageLevel" && $parts[1] eq "max") {
- @cmdDemandResponse = ($name, "on");
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- } elsif ($parts[0] eq "powerUsageLevel" && $parts[1] eq "min") {
- @cmdDemandResponse = ($name, "off");
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- }
- } elsif ((AttrVal($name, "subType", "") eq "gateway" && AttrVal($name, "gwCmd", "") eq "dimming")
- || AttrVal($name, "subType", "") eq "actuator.01") {
- if ($parts[0] eq "powerUsage") {
- if (ReadingsVal($devName, "powerUsageScale", "max") eq "rel") {
- @cmdDemandResponse = ($name, "dim", $parts[1] * ReadingsVal($name, "dimValueLast", 100) / 100);
- } else {
- @cmdDemandResponse = ($name, "dim", $parts[1]);
- }
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- }
- } elsif ((AttrVal($name, "subType", "") eq "roomSensorControl.05" && AttrVal($name, "manufID", "") eq "00D")) {
- if ($parts[0] eq "level") {
- @cmdDemandResponse = ($name, "nightReduction", int(5 - 1/3 * $parts[1]));
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- }
- } elsif (AttrVal($name, "subType", "") eq "lightCtrl.01") {
- if ($parts[0] eq "setpoint") {
- @cmdDemandResponse = ($name, "dim", $parts[1]);
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- }
- } elsif (AttrVal($name, "subType", "") eq "roomSensorControl.01") {
- if ($parts[0] eq "setpoint") {
- @cmdDemandResponse = ($name, "setpoint", $parts[1]);
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- }
- } elsif (AttrVal($name, "subType", "") eq "roomSensorControl.05") {
- if ($parts[0] eq "setpoint") {
- @cmdDemandResponse = ($name, $parts[0], $parts[1]);
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- }
- } elsif (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") {
- if ($parts[0] eq "powerUsageLevel" && $parts[1] eq "max") {
- @cmdDemandResponse = ($name, "roomCtrlMode", "comfort");
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- } elsif ($parts[0] eq "powerUsageLevel" && $parts[1] eq "min") {
- @cmdDemandResponse = ($name, "roomCtrlMode", "economy");
- #Log3($name, 3, "EnOcean $name <notify> demandRespRefDev: $devName Cmd: set " . join(" ", @cmdDemandResponse));
- EnOcean_Set($hash, @cmdDemandResponse);
- }
- }
- }
- }
- if (defined(AttrVal($name, "humidityRefDev", undef)) && AttrVal($name, "setCmdTrigger", "man") eq "refDev") {
- if ($devName eq AttrVal($name, "humidityRefDev", "")) {
- if ($parts[0] eq "humidity") {
- if (AttrVal($name, "subType", "") eq "roomSensorControl.01") {
- my @setCmd = ($name, "setpoint");
- EnOcean_Set($hash, @setCmd);
- }
- }
- }
- #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
- }
- if (defined(AttrVal($name, "temperatureRefDev", undef)) && AttrVal($name, "setCmdTrigger", "man") eq "refDev") {
- # sent a setpoint or setpointTemp telegram
- if ($devName eq AttrVal($name, "temperatureRefDev", "")) {
- if ($parts[0] eq "temperature") {
- if (AttrVal($name, "subType", "") eq "roomSensorControl.05" && AttrVal($name, "manufID", "") eq "00D") {
- my @setCmd = ($name, "setpointTemp");
- EnOcean_Set($hash, @setCmd);
- } elsif (AttrVal($name, "subType", "") eq "fanCtrl.00") {
- my @setCmd = ($name, "setpointTemp");
- EnOcean_Set($hash, @setCmd);
- } elsif (AttrVal($name, "subType", "") eq "roomSensorControl.01") {
- my @setCmd = ($name, "setpoint");
- EnOcean_Set($hash, @setCmd);
- } elsif (AttrVal($name, "subType", "") eq "roomSensorControl.05") {
- my @setCmd = ($name, "setpoint");
- EnOcean_Set($hash, @setCmd);
- }
- }
- }
- #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
- }
- if (defined(AttrVal($name, "temperatureRefDev", undef)) &&
- $devName eq AttrVal($name, "temperatureRefDev", "") &&
- $parts[0] eq "temperature") {
- if (AttrVal($name, "subType", "") eq "hvac.01") {
- readingsSingleUpdate($hash, "temperature", $parts[1], 1);
- #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
- }
- if (AttrVal($name, "subType", "") eq "hvac.04" && AttrVal($name, "measurementCtrl", "enable") eq 'disable') {
- readingsSingleUpdate($hash, "temperature", $parts[1], 1);
- #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
- }
- }
- if (defined(AttrVal($name, "setpointRefDev", undef)) &&
- $devName eq AttrVal($name, "setpointRefDev", "") &&
- $parts[0] eq "setpoint") {
- if (AttrVal($name, "subType", '') =~ m/^hvac\.0(1|4)$/) {
- my @setCmd = ($name, "setpoint", $parts[1]);
- EnOcean_Set($hash, @setCmd);
- #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
- }
- }
- if (defined(AttrVal($name, "setpointTempRefDev", undef)) &&
- $devName eq AttrVal($name, "setpointTempRefDev", "") &&
- $parts[0] eq "setpointTemp") {
- if (AttrVal($name, "subType", '') =~ m/^hvac\.0(1|4)$/) {
- if (ReadingsVal($name, "setpointTemp", 0) != $parts[1]) {
- my @setCmd = ($name, "setpointTemp", $parts[1]);
- EnOcean_Set($hash, @setCmd);
- #Log3 $name, 2, "EnOcean $name <notify> $devName $s";
- }
- }
- }
- }
- }
- return undef;
- }
- # ADT encapsulation
- sub
- EnOcean_Encapsulation($$$$)
- {
- my ($packetType, $rorg, $data, $destinationID) = @_;
- if ($destinationID eq "FFFFFFFF") {
- return ($rorg, $data);
- } else {
- $data = $rorg . $data;
- return ("A6", $data);
- }
- }
- # set PID regulator
- sub EnOcean_setPID($$$) {
- my ($crtl, $hash, $cmd, $adjust) = @_;
- my $name = $hash->{NAME};
- my ($err, $response, $logLevel) = (undef, 'start', 5);
- @{$hash->{helper}{calcPID}} = (undef, $hash, $cmd);
- if ($cmd eq 'stop' || AttrVal($name, 'pidCtrl', 'on') eq 'off') {
- $hash->{helper}{stopped} = 1;
- readingsSingleUpdate($hash, "pidState", 'stopped', 0);
- RemoveInternalTimer($hash->{helper}{calcPID});
- $response = 'stopped';
- } elsif ($cmd eq 'start' || $cmd eq 'actuator') {
- $hash->{helper}{stopped} = 0;
- $hash->{helper}{adjust} = $adjust;
- RemoveInternalTimer($hash->{helper}{calcPID});
- ($err, $logLevel, $response) = EnOcean_calcPID($hash->{helper}{calcPID});
- }
- return ($err, $logLevel, $response);
- }
- # calc valve setpoint (PID regulator)
- sub EnOcean_calcPID($) {
- my ($pidParam) = @_;
- my ($crtl, $hash, $cmd) = @$pidParam;
- my $name = $hash->{NAME};
- my ($err, $response, $logLevel, $setpoint) = (undef, $cmd, 5, 0);
- #####
- my $reUINT = '^([\\+]?\\d+)$'; # uint without whitespaces
- my $re01 = '^([0,1])$'; # only 0,1
- my $reINT = '^([\\+,\\-]?\\d+$)'; # int
- my $reFloatpos = '^([\\+]?\\d+\\.?\d*$)'; # gleitpunkt positiv float
- my $reFloat = '^([\\+,\\-]?\\d+\\.?\d*$)'; # float
- my $sensor = $name;
- my $reading = 'temperature';
- my $regexp = $reFloat;
- my $DEBUG_Sensor = AttrVal( $name, 'pidDebugSensor', '0' ) eq '1';
- my $DEBUG_Actuation = AttrVal( $name, 'pidDebugActuation', '0' ) eq '1';
- my $DEBUG_Delta = AttrVal( $name, 'pidDebugDelta', '0' ) eq '1';
- my $DEBUG_Calc = AttrVal( $name, 'pidDebugCalc', '0' ) eq '1';
- my $DEBUG_Update = AttrVal( $name, 'pidDebugUpdate', '0' ) eq '1';
- my $DEBUG = $DEBUG_Sensor || $DEBUG_Actuation || $DEBUG_Calc || $DEBUG_Delta || $DEBUG_Update;
- my $actuation = "";
- my $actuationDone = ReadingsVal( $name, 'setpointSet', ReadingsVal( $name, 'setpoint', ""));
- my $actuationCalc = ReadingsVal( $name, 'setpointCalc', "" );
- my $actuationCalcOld = $actuationCalc;
- my $actorTimestamp =
- ( $hash->{helper}{actorTimestamp} )
- ? $hash->{helper}{actorTimestamp}
- : FmtDateTime( gettimeofday() - 3600 * 24 );
- my $desired = '';
- my $sensorStr = ReadingsVal($name, 'temperature',"");
- my $sensorValue = "";
- my $sensorTS = ReadingsTimestamp($name, 'temperature', undef);
- my $sensorIsAlive = 0;
- my $iPortion = ReadingsVal( $name, 'p_i', 0 );
- my $pPortion = ReadingsVal( $name, 'p_p', "" );
- my $dPortion = ReadingsVal( $name, 'p_d', "" );
- my $stateStr = "";
- CommandDeleteReading(undef, "$name pidAlarm");
- my $deltaOld = ReadingsVal( $name, 'delta', 0 );
- my $delta = "";
- my $deltaGradient = ( $hash->{helper}{deltaGradient} ) ? $hash->{helper}{deltaGradient} : 0;
- my $calcReq = 0;
- my $readingUpdateReq = '';
- # ---------------- check conditions
- while (1)
- {
- # --------------- retrive values from attributes
- my $wakeUpCycle = AttrVal($name, 'wakeUpCycle', ReadingsVal($name, 'wakeUpCycle', 300));
- my $pidCycle = $wakeUpCycle / 3;
- $pidCycle = 10 if ($pidCycle < 10);
- $hash->{helper}{actorInterval} = 10;
- $hash->{helper}{actorThreshold} = 0;
- $hash->{helper}{actorKeepAlive} = $pidCycle;
- $hash->{helper}{actorValueDecPlaces} = 0;
- $hash->{helper}{actorErrorAction} = AttrVal($name, 'pidActorErrorAction', 'freeze');
- $hash->{helper}{actorErrorPos} = AttrVal($name, 'pidActorErrorPos', 0);
- $hash->{helper}{calcInterval} = $pidCycle;
- $hash->{helper}{deltaTreshold} = AttrVal($name, 'pidDeltaTreshold', 0);
- if (AttrVal($name, 'measurementCtrl', 'enable') eq 'enable') {
- $hash->{helper}{sensorTimeout} = $wakeUpCycle * 4;
- } else {
- $hash->{helper}{sensorTimeout} = AttrVal($name, 'pidSensorTimeout', 3600);
- }
- $hash->{helper}{reverseAction} = 0;
- $hash->{helper}{updateInterval} = $pidCycle;
- $hash->{helper}{actorLimitLower} = AttrVal($name, 'pidActorLimitLower', 0);
- my $actorLimitLower = $hash->{helper}{actorLimitLower};
- $hash->{helper}{actorLimitUpper} = AttrVal($name, 'pidActorLimitUpper', 100);
- my $actorLimitUpper = $hash->{helper}{actorLimitUpper};
- $hash->{helper}{factor_P} = AttrVal($name, 'pidFactor_P', 25);
- $hash->{helper}{factor_I} = AttrVal($name, 'pidFactor_I', 0.25);
- $hash->{helper}{factor_D} = AttrVal($name, 'pidFactor_D', 0);
- if ($hash->{helper}{stopped}) {
- $stateStr = "stopped";
- last;
- }
- $desired = ReadingsVal( $name, 'setpointTempSet', ReadingsVal($name, 'setpointTemp', ""));
- #my $desired = ReadingsVal( $name, $hash->{helper}{desiredName}, "" );
- # sensor found
- #PID20_Log $hash, 2, "--------------------------" if ($DEBUG);
- #PID20_Log $hash, 2, "S1 sensorStr:$sensorStr sensorTS:$sensorTS" if ($DEBUG_Sensor);
- if ( !$sensorStr && !$stateStr ) {
- $stateStr = "alarm";
- $err = 'no_temperature_value';
- }
- # sensor alive
- if ( $sensorStr && $sensorTS )
- {
- my $timeDiff = EnOcean_TimeDiff($sensorTS);
- $sensorIsAlive = 1 if ( $timeDiff <= $hash->{helper}{sensorTimeout} );
- $sensorStr =~ m/$regexp/;
- $sensorValue = $1;
- $sensorValue = "" if ( !defined($sensorValue) );
- #PID20_Log $hash, 2,
- # "S2 timeOfDay:"
- # . gettimeofday()
- # . " timeDiff:$timeDiff sensorTimeout:"
- # . $hash->{helper}{sensorTimeout}
- # . " --> sensorIsAlive:$sensorIsAlive"
- # if ($DEBUG_Sensor);
- }
- # sensor dead
- if (!$sensorIsAlive && !$stateStr) {
- $stateStr = "alarm";
- $err = 'dead_sensor';
- }
- # missing desired
- if ($desired eq "" && !$stateStr) {
- $stateStr = "alarm";
- $err = 'setpoint_device_missing';
- }
- # check delta threshold
- $delta = ( $desired ne "" && $sensorValue ne "" ) ? $desired - $sensorValue : "";
- $calcReq = 1 if ( !$stateStr && $delta ne "" && ( abs($delta) >= abs( $hash->{helper}{deltaTreshold} ) ) );
- #PID20_Log $hash, 2,
- # "D1 desired[" . ( $desired ne "" ) ? sprintf( "%.1f", $desired )
- # : "" . "] - sensorValue: [" . ( $sensorValue ne "" ) ? sprintf( "%.1f", $sensorValue )
- # : "" . "] = delta[" . ( $delta ne "" ) ? sprintf( "%.2f", $delta )
- # : "" . "] calcReq:$calcReq"
- # if ($DEBUG_Delta);
- #request for calculation
- # ---------------- calculation request
- if ($calcReq)
- {
- # reverse action requested
- my $workDelta = ( $hash->{helper}{reverseAction} == 1 ) ? -$delta : $delta;
- my $deltaOld = -$deltaOld if ( $hash->{helper}{reverseAction} == 1 );
- # calc p-portion
- $pPortion = $workDelta * $hash->{helper}{factor_P};
- # calc d-Portion
- $dPortion = ($deltaGradient) * $hash->{helper}{calcInterval} * $hash->{helper}{factor_D};
- # calc i-portion respecting windUp
- # freeze i-portion if windUp is active
- my $isWindup = $actuationCalcOld
- && ( ( $workDelta > 0 && $actuationCalcOld > $actorLimitUpper )
- || ( $workDelta < 0 && $actuationCalcOld < $actorLimitLower ) );
- if ( $hash->{helper}{adjust} ne "" )
- {
- $iPortion = $hash->{helper}{adjust} - ( $pPortion + $dPortion );
- $iPortion = $actorLimitUpper if ( $iPortion > $actorLimitUpper );
- $iPortion = $actorLimitLower if ( $iPortion < $actorLimitLower );
- #PID20_Log $hash, 5, "adjust request with:" . $hash->{helper}{adjust} . " ==> p_i:$iPortion";
- $hash->{helper}{adjust} = "";
- } elsif ( !$isWindup ) # integrate only if no windUp
- {
- # normalize the intervall to minute=60 seconds
- $iPortion = $iPortion + $workDelta * $hash->{helper}{factor_I} * $hash->{helper}{calcInterval} / 60;
- $hash->{helper}{isWindUP} = 0;
- }
- $hash->{helper}{isWindUP} = $isWindup;
- # check callback for iPortion
- my $iportionCallBeforeSetting = AttrVal( $name, 'pidIPortionCallBeforeSetting', undef );
- if ( defined($iportionCallBeforeSetting) && exists &$iportionCallBeforeSetting )
- {
- #PID20_Log $hash, 5, 'start callback ' . $iportionCallBeforeSetting . ' with iPortion:' . $iPortion;
- no strict "refs";
- $iPortion = &$iportionCallBeforeSetting( $name, $iPortion );
- use strict "refs";
- #PID20_Log $hash, 5, 'return value of ' . $iportionCallBeforeSetting . ':' . $iPortion;
- }
- # calc actuation
- $actuationCalc = $pPortion + $iPortion + $dPortion;
- #PID20_Log $hash, 2, "P1 delta:" . sprintf( "%.2f", $delta ) . " isWindup:$isWindup" if ($DEBUG_Calc);
- #PID20_Log $hash, 2,
- # "P2 pPortion:"
- # . sprintf( "%.2f", $pPortion )
- # . " iPortion:"
- # . sprintf( "%.2f", $iPortion )
- # . " dPortion:"
- # . sprintf( "%.2f", $dPortion )
- # . " actuationCalc:"
- # . sprintf( "%.2f", $actuationCalc )
- # if ($DEBUG_Calc);
- }
- $readingUpdateReq = 1; # in each case update readings
- # ---------------- acutation request
- my $noTrouble = ( $desired ne "" && $sensorIsAlive );
- # check actor fallback in case of sensor fault
- if (!$sensorIsAlive && ($hash->{helper}{actorErrorAction} eq "errorPos")) {
- #$stateStr .= "- force pid-output to errorPos";
- $err .= ':actuator_in_errorPos';
- $actuationCalc = $hash->{helper}{actorErrorPos};
- $actuationCalc = "" if ( !defined($actuationCalc) );
- }
- # check acutation diff
- $actuation = $actuationCalc;
- # limit $actuation
- $actuation = $actorLimitUpper if ( $actuation ne "" && ( $actuation > $actorLimitUpper ) );
- $actuation = $actorLimitLower if ( $actuation ne "" && ( $actuation < $actorLimitLower ) );
- # check if round request
- my $fmt = "%." . $hash->{helper}{actorValueDecPlaces} . "f";
- $actuation = sprintf( $fmt, $actuation ) if ( $actuation ne "" );
- my $actuationDiff = abs( $actuation - $actuationDone )
- if ( $actuation ne "" && $actuationDone ne "" );
- #PID20_Log $hash, 2,
- # "A1 act:$actuation actDone:$actuationDone "
- # . " actThreshold:"
- # . $hash->{helper}{actorThreshold}
- # . " actDiff:$actuationDiff"
- # if ($DEBUG_Actuation);
- # check threshold-condition for actuation
- my $rsTS = $actuationDone ne "" && $actuationDiff >= $hash->{helper}{actorThreshold};
- # ...... special handling if acutation is in the black zone between actorLimit and (actorLimit - actorThreshold)
- # upper range
- my $rsUp =
- $actuationDone ne ""
- && $actuation > $actorLimitUpper - $hash->{helper}{actorThreshold}
- && $actuationDiff != 0
- && $actuation >= $actorLimitUpper;
- # low range
- my $rsDown =
- $actuationDone ne ""
- && $actuation < $actorLimitLower + $hash->{helper}{actorThreshold}
- && $actuationDiff != 0
- && $actuation <= $actorLimitLower;
- # upper or lower limit are exceeded
- my $rsLimit = $actuationDone ne "" && ( $actuationDone < $actorLimitLower || $actuationDone > $actorLimitUpper );
- my $actuationByThreshold = ( ( $rsTS || $rsUp || $rsDown ) && $noTrouble );
- #PID20_Log $hash, 2, "A2 rsTS:$rsTS rsUp:$rsUp rsDown:$rsDown noTrouble:$noTrouble"
- # if ($DEBUG_Actuation);
- # check time condition for actuation
- my $actTimeDiff = EnOcean_TimeDiff($actorTimestamp); # $actorTimestamp is valid in each case
- my $actuationByTime = ($noTrouble) && ( $actTimeDiff > $hash->{helper}{actorInterval} );
- #PID20_Log $hash, 2,
- # "A3 actTS:$actorTimestamp"
- # . " actTimeDiff:"
- # . sprintf( "%.2f", $actTimeDiff )
- # . " actInterval:"
- # . $hash->{helper}{actorInterval}
- # . "-->actByTime:$actuationByTime "
- # if ($DEBUG_Actuation);
- # check keep alive condition for actuation
- my $actuationKeepAliveReq = ( $actTimeDiff >= $hash->{helper}{actorKeepAlive} )
- if ( defined($actTimeDiff) && $actuation ne "" );
- # build total actuation request
- my $actuationReq = (
- ( $actuationByThreshold && $actuationByTime )
- || $actuationKeepAliveReq # request by keep alive
- || $rsLimit # upper or lower limit are exceeded
- || $actuationDone eq "" # startup condition
- ) && $actuation ne ""; # acutation is initialized
- #PID20_Log $hash, 2,
- # "A4 (actByTh:$actuationByThreshold && actByTime:$actuationByTime)"
- # . "||actKeepAlive:$actuationKeepAliveReq"
- # . "||rsLimit:$rsLimit=actnReq:$actuationReq"
- # if ($DEBUG_Actuation);
- # ................ perform output to actor
- if ($actuationReq)
- {
- $readingUpdateReq = 1; # update the readings
- # check calback for actuation
- my $actorCallBeforeSetting = AttrVal( $name, 'pidActorCallBeforeSetting', undef );
- if ( defined($actorCallBeforeSetting) && exists &$actorCallBeforeSetting )
- {
- #PID20_Log $hash, 5, 'start callback ' . $actorCallBeforeSetting . ' with actuation:' . $actuation;
- no strict "refs";
- $actuation = &$actorCallBeforeSetting( $name, $actuation );
- use strict "refs";
- #PID20_Log $hash, 5, 'return value of ' . $actorCallBeforeSetting . ':' . $actuation;
- }
- #build command for fhem
- #PID20_Log $hash, 5,
- # "actor:"
- # . $hash->{helper}{actor}
- # . " actorCommand:"
- # . $hash->{helper}{actorCommand}
- # . " actuation:"
- # . $actuation;
- #my $cmd = sprintf( "set %s %s %g", $hash->{helper}{actor}, $hash->{helper}{actorCommand}, $actuation );
- # execute command
- my $ret;
- #$ret = fhem $cmd;
- $setpoint = $actuation;
- $actuationDone = $actuation;
- # note timestamp
- $hash->{helper}{actorTimestamp} = TimeNow();
- my $retStr = "";
- $retStr = " with return-value:" . $ret if ( defined($ret) && ( $ret ne '' ) );
- #PID20_Log $hash, 3, "<$cmd> " . $retStr;
- }
- my $updateAlive = ( $actuation ne "" )
- && EnOcean_TimeDiff( ReadingsTimestamp( $name, 'setpointSet', gettimeofday() ) ) >= $hash->{helper}{updateInterval};
- # my $updateReq = ( ( $actuationReq || $updateAlive ) && $actuation ne "" );
- # PID20_Log $hash, 2, "U1 actReq:$actuationReq updateAlive:$updateAlive --> updateReq:$updateReq" if ($DEBUG_Update);
- # ---------------- update request
- if ($readingUpdateReq)
- {
- readingsBeginUpdate($hash);
- #readingsBulkUpdate( $hash, $hash->{helper}{desiredName}, $desired ) if ( $desired ne "" );
- #readingsBulkUpdate( $hash, $hash->{helper}{measuredName}, $sensorValue ) if ( $sensorValue ne "" );
- readingsBulkUpdate( $hash, 'p_p', $pPortion ) if ( $pPortion ne "" );
- readingsBulkUpdate( $hash, 'p_d', $dPortion ) if ( $dPortion ne "" );
- readingsBulkUpdate( $hash, 'p_i', $iPortion ) if ( $iPortion ne "" );
- readingsBulkUpdate( $hash, 'setpointSet', $actuationDone) if ($actuationDone ne "");
- readingsBulkUpdate( $hash, 'setpointCalc', $actuationCalc ) if ( $actuationCalc ne "" );
- readingsBulkUpdate( $hash, 'delta', $delta ) if ( $delta ne "" );
- readingsEndUpdate( $hash, 1 );
- #PID20_Log $hash, 5, "readings updated";
- }
- last;
- } # end while
- # ........ update statePID.
- $stateStr = 'idle' if ($stateStr eq '' && !$calcReq);
- $stateStr = 'processing' if ($stateStr eq '' && $calcReq);
- #PID20_Log $hash, 2, "C1 stateStr:$stateStr calcReq:$calcReq" if ($DEBUG_Calc);
- #......... timer setup
- #my $next = gettimeofday() + $hash->{helper}{calcInterval};
- #RemoveInternalTimer($name); # prevent multiple timers for same hash
- #InternalTimer( $next, "PID20_Calc", $name, 1 );
- #PID20_Log $hash, 2, "InternalTimer next:".FmtDateTime($next)." PID20_Calc name:$name DEBUG_Calc:$DEBUG_Calc";
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, 'pidState', $stateStr);
- readingsBulkUpdate($hash, 'pidAlarm', $err) if (defined $err);
- readingsEndUpdate($hash, 1);
- Log3($name, 5, "EnOcean $name EnOcean_calcPID Cmd: $cmd pidState: $stateStr T: $sensorValue SP: $setpoint SPT: $desired");
- @{$hash->{helper}{calcPID}} = (undef, $hash, 'periodic');
- RemoveInternalTimer($hash->{helper}{calcPID});
- InternalTimer(gettimeofday() + $hash->{helper}{calcInterval} * 1.02, "EnOcean_calcPID", $hash->{helper}{calcPID}, 0);
- return ($err, $logLevel, $response);
- }
- # sent message to Multisnesor Window Handle (EEP D2-06-01)
- sub
- EnOcean_multisensor_01Snd($$$)
- {
- my ($crtl, $hash, $packetType) = @_;
- my $name = $hash->{NAME};
- my ($data, $err, $response, $logLevel);
- EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- return ($err, $response);
- }
- # sent message to Room Control Panel (EEP D2-10-xx)
- sub
- EnOcean_roomCtrlPanel_00Snd($$$$$$$$)
- {
- my ($crtl, $hash, $packetType, $mid, $mcf, $irc, $fbc, $gmt) = @_;
- my $name = $hash->{NAME};
- my ($data, $err, $response, $logLevel);
- my $messagePart = 1;
- if ($mid == 0) {
- # general massage
- ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart);
- EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- if ($err) {
- Log3 $name, $logLevel, "EnOcean $name Error: $err";
- } else {
- Log3 $name, $logLevel, "EnOcean $name $response";
- }
- if (!defined($irc)) {
- } elsif ($irc == 0) {
- # acknowledge request
- } elsif ($irc == 1) {
- # data request
- } elsif ($irc == 2) {
- # configuration request
- } elsif ($irc == 3) {
- # room control request
- } elsif ($irc == 4) {
- # time program request
- }
- if (!defined($fbc)) {
- } elsif ($fbc == 0) {
- # acknowledge / heartbeat
- } elsif ($fbc == 1) {
- # telegram repetition request
- } elsif ($fbc == 2) {
- # message repetition request
- } elsif ($fbc == 3) {
- # reserved
- }
- if (!defined($gmt)) {
- } elsif ($gmt == 0) {
- # information request
- } elsif ($gmt == 1) {
- # feetback
- }
- } elsif ($mid == 1) {
- # data message
- ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart);
- EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- if ($err) {
- Log3 $name, $logLevel, "EnOcean $name Error: $err";
- } else {
- Log3 $name, $logLevel, "EnOcean $name $response";
- }
- } elsif ($mid == 2) {
- # configuration message
- ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart);
- EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- if ($err) {
- Log3 $name, $logLevel, "EnOcean $name Error: $err";
- } else {
- Log3 $name, $logLevel, "EnOcean $name $response";
- }
- } elsif ($mid == 3) {
- # room control setup
- ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart);
- EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- if ($err) {
- Log3 $name, $logLevel, "EnOcean $name Error: $err";
- } else {
- Log3 $name, $logLevel, "EnOcean $name $response";
- }
- } elsif ($mid == 4) {
- # time program setup
- ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart);
- EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
- if ($err) {
- Log3 $name, $logLevel, "EnOcean $name Error: $err";
- } else {
- Log3 $name, $logLevel, "EnOcean $name $response";
- }
- }
- return ($err, $response);
- }
- # generate command to Room Control Panel (EEP D2-10-xx)
- sub
- EnOcean_roomCtrlPanel_00Cmd($$$$)
- {
- my ($crtl, $hash, $mcf, $messagePart) = @_;
- my $name = $hash->{NAME};
- my $data = "0000";
- my $err;
- my $response = "acknowledge send";
- my $logLevel = 4;
- # Waitings Commands (waitingCmds)
- # 1 = sent data request
- # 2 = sent data message
- # 4 = sent configuration message (time)
- # 8 = sent room control setup
- # 16 = sent time program setup
- # 32 = sent configuration request
- # 64 = sent configuration message
- # 128 = sent room control setup request
- # 256 = sent time program request
- # 512 = sent delete time program
- my $waitingCmds = ReadingsVal($name, "waitingCmds", 0);
- if ($mcf == 0) {
- # message complete
- if ($waitingCmds & 8) {
- # room control setup waiting
- my ($db5, $db4, $db3, $db2, $db1, $db0) = (96, 0, 0, 0, 0, 0, 0);
- if (defined ReadingsVal($name, "setpointComfortTempSet", undef)) {
- $db1 = ReadingsVal($name, "setpointComfortTempSet", 0) * 255 / 40;
- $db0 = $db0 | 1;
- CommandDeleteReading(undef, "$name setpointComfortTempSet");
- }
- if (defined ReadingsVal($name, "setpointEconomyTempSet", undef)) {
- $db2 = ReadingsVal($name, "setpointEconomyTempSet", 0) * 255 / 40;
- $db0 = $db0 | 2;
- CommandDeleteReading(undef, "$name setpointEconomyTempSet");
- }
- if (defined ReadingsVal($name, "setpointPreComfortTempSet", undef)) {
- $db3 = ReadingsVal($name, "setpointPreComfortTempSet", 0) * 255 / 40;
- $db0 = $db0 | 4;
- CommandDeleteReading(undef, "$name setpointPreComfortTempSet");
- }
- if (defined ReadingsVal($name, "setpointBuildingProtectionTempSet", undef)) {
- $db4 = ReadingsVal($name, "setpointBuildingProtectionTempSet", 0) * 255 / 40;
- $db0 = $db0 | 8;
- CommandDeleteReading(undef, "$name setpointBuildingProtectionTempSet");
- }
- $data = sprintf "%02X%02X%02X%02X%02X%02X", $db5, $db4, $db3, $db2, $db1, $db0;
- # clear command
- $waitingCmds = $waitingCmds & 247 + 0xFF00;
- $response = "room control setup send $data";
- $logLevel = 2;
- } elsif ($waitingCmds & 64 || $waitingCmds & 4) {
- # configuration message waiting
- my ($sec, $min, $hour, $day, $month, $year) = localtime();
- my ($key, $val);
- $month += 1;
- $year += 1900;
- my ($db7, $db6, $db5, $db4, $db1, $db0) = (64, 0, 0, 0, $min << 2, $hour << 3);
- $db6 |= 1 if (AttrVal($name, "blockFanSpeed", "no") ne "yes" );
- $db6 |= 2 if (AttrVal($name, "blockSetpointTemp", "no") ne "yes" );
- $db6 |= 4 if (AttrVal($name, "blockOccupancy", "no") ne "yes" );
- $db6 |= 8 if (AttrVal($name, "blockTimeProgram", "no") ne "yes" );
- $db6 |= 16 if (AttrVal($name, "blockDateTime", "no") ne "yes" );
- $db6 |= 32 if (AttrVal($name, "blockDisplay", "no") ne "yes" );
- $db6 |= 64 if (AttrVal($name, "blockTemp", "no") ne "yes" );
- $db6 |= 128 if (AttrVal($name, "blockMotion", "no") ne "yes" );
- $db5 = AttrVal($name, "pollInterval", 10);
- if ($db5 > 60 && $db5 <= 180) {
- $db5 = 61;
- } elsif ($db5 > 180 && $db5 <= 720) {
- $db5 = 62;
- } elsif ($db5 > 720) {
- $db5 = 63;
- }
- $db5 = $db5 << 2;
- $db5 |= 2 if (AttrVal($name, "blockKey", "no") ne "yes" );
- my $displayContent = AttrVal($name, "displayContent", "no_change");
- my $displayContentVal = 0;
- my %displayContent = ("humidity" => 7,
- "off" => 6,
- "setpointTemp" => 5,
- "tempertureExtern" => 4,
- "temperatureIntern" => 3,
- "time" => 2,
- "default" => 1,
- "no_change" => 0
- );
- while (($key, $val) = each(%displayContent)) {
- $displayContentVal = $val if ($key eq $displayContent);
- }
- my $temperatureScale = AttrVal($name, "temperatureScale", "no_change");
- my $temperatureScaleVal = 0;
- my %temperatureScale = ("F" => 3,
- "C" => 2,
- "default" => 1,
- "no_change" => 0
- );
- while (($key, $val) = each(%temperatureScale)) {
- $temperatureScaleVal = $val if ($key eq $temperatureScale);
- }
- my $daylightSavingTimeVal = 0;
- $daylightSavingTimeVal = 1 if (AttrVal($name, "daylightSavingTime", "supported") eq "not_supported");
- my $timeNotation = AttrVal($name, "timeNotation", "no_change");
- my $timeNotationVal = 0;
- if ($timeNotation eq "no_change") {
- $timeNotationVal = 0;
- } elsif ($timeNotation eq "default") {
- $timeNotationVal = 1;
- } elsif ($timeNotation == 24) {
- $timeNotationVal = 2;
- } elsif ($timeNotation == 12) {
- $timeNotationVal = 3;
- }
- $db4 = (($displayContentVal << 2 | $temperatureScaleVal) << 1 | $daylightSavingTimeVal) << 2 | $timeNotationVal;
- my $db32 = ($day << 4 | $month) << 7 | $year - 2000;
- if ($waitingCmds & 4) {
- $db0 |= 1;
- # clear time command
- $waitingCmds &= 251 + 0xFF00;
- }
- if ($waitingCmds & 64) {
- # clear config command
- $waitingCmds &= 191 + 0xFF00;
- }
- $data = sprintf "%02X%02X%02X%02X%04X%02X%02X", $db7, $db6, $db5, $db4, $db32, $db1, $db0;
- $response = "configuration message send $data";
- $logLevel = 2;
- } elsif ($waitingCmds & 2) {
- # data message waiting
- my ($db7, $db6, $db5, $db4, $db3, $db2, $db1, $db0) = (32, 0, 0, 0, 0, 0, 0, 0, 0);
- if (defined ReadingsVal($name, "setpointTempSet", undef)) {
- $db1 = ReadingsVal($name, "setpointTempSet", 0) * 255 / 40;
- $db2 |= 2;
- CommandDeleteReading(undef, "$name setpointTempSet");
- }
- my $heatingSet;
- if (defined ReadingsVal($name, "heatingSet", undef)) {
- $heatingSet = ReadingsVal($name, "heatingSet", 0);
- } else {
- $heatingSet = ReadingsVal($name, "heating", 0);
- }
- if ($heatingSet eq "no_change") {
- $heatingSet = 0;
- } elsif ($heatingSet eq "on") {
- $heatingSet = 1;
- } elsif ($heatingSet eq "off") {
- $heatingSet = 2;
- } elsif ($heatingSet eq "auto") {
- $heatingSet = 3;
- }
- $db2 |= $heatingSet << 4;
- CommandDeleteReading(undef, "$name heatingSet");
- my $coolingSet;
- if (defined ReadingsVal($name, "coolingSet", undef)) {
- $coolingSet = ReadingsVal($name, "coolingSet", 0);
- } else {
- $coolingSet = ReadingsVal($name, "cooling", 0);
- }
- if ($coolingSet eq "no_change") {
- $coolingSet = 0;
- } elsif ($coolingSet eq "on") {
- $coolingSet = 1;
- } elsif ($coolingSet eq "off") {
- $coolingSet = 2;
- } elsif ($coolingSet eq "auto") {
- $coolingSet = 3;
- }
- $db2 |= $coolingSet << 6;
- CommandDeleteReading(undef, "$name coolingSet");
- my $roomCtrlModeSet;
- if (defined ReadingsVal($name, "roomCtrlModeSet", undef)) {
- $roomCtrlModeSet = ReadingsVal($name, "roomCtrlModeSet", 0);
- } else {
- $roomCtrlModeSet = ReadingsVal($name, "roomCtrlMode", 0);
- }
- if ($roomCtrlModeSet eq "comfort") {
- $roomCtrlModeSet = 0;
- } elsif ($roomCtrlModeSet eq "economy") {
- $roomCtrlModeSet = 1;
- } elsif ($roomCtrlModeSet eq "preComfort") {
- $roomCtrlModeSet = 2;
- } elsif ($roomCtrlModeSet eq "buildingProtection") {
- $roomCtrlModeSet = 3;
- }
- $db2 |= $roomCtrlModeSet << 2;
- CommandDeleteReading(undef, "$name roomCtrlModeSet");
- my $windowSet;
- if (defined ReadingsVal($name, "windowSet", undef)) {
- $windowSet = ReadingsVal($name, "windowSet", 0);
- } else {
- $windowSet = ReadingsVal($name, "window", 0);
- }
- if ($windowSet eq "no_change") {
- $windowSet = 0;
- } elsif ($windowSet eq "closed") {
- $windowSet = 1;
- } elsif ($windowSet eq "open") {
- $windowSet = 2;
- } elsif ($windowSet eq "reserved") {
- $windowSet = 3;
- }
- $db4 |= $windowSet;
- CommandDeleteReading(undef, "$name windowSet");
- my $fanSpeedModeSet;
- if (defined ReadingsVal($name, "fanSpeedModeSet", undef)) {
- $fanSpeedModeSet = ReadingsVal($name, "fanSpeedModeSet", 0);
- } else {
- $fanSpeedModeSet = ReadingsVal($name, "fanSpeedMode", 0);
- }
- $db4 |= 64 if ($fanSpeedModeSet eq "local");
- CommandDeleteReading(undef, "$name fanSpeedModeSet");
- if (defined ReadingsVal($name, "fanSpeedSet", undef)) {
- $db5 = ReadingsVal($name, "fanSpeedSet", 0);
- $db4 |= 128;
- CommandDeleteReading(undef, "$name fanSpeedSet");
- }
- $data = sprintf "%02X%02X%02X%02X%02X%02X%02X%02X", $db7, $db6, $db5, $db4, $db3, $db2, $db1, $db0;
- # clear command
- $waitingCmds = $waitingCmds & 253 + 0xFF00;
- $response = "data message send";
- $logLevel = 2;
- } elsif ($waitingCmds & 512) {
- # delete time program command waiting
- $data = "800000000001";
- # clear command
- $waitingCmds = $waitingCmds & 0xFDFF;
- $response = "delete time program send";
- $logLevel = 2;
- } elsif ($waitingCmds & 16) {
- # time program setup waiting
- my ($db5, $endMinute, $endHour, $startMinute, $startHour, $db0) = (128, 0, 0, 0, 0, 0, 0);
- my ($key, $val);
- my $messagePartCntr;
- for ($messagePartCntr = 4; $messagePartCntr >= 1; $messagePartCntr --) {
- if ($hash->{helper}{4}{telegramWait}{$messagePartCntr}) {
- $hash->{helper}{4}{telegramWait}{$messagePartCntr} = 0;
- my $timeProgram = AttrVal($name, "timeProgram" . $messagePartCntr, undef);
- my @timeProgram = split("[ \t][ \t]*", $timeProgram);
- my ($period, $roomCtrlMode) = ($timeProgram[0], $timeProgram[3]);
- ($startHour, $startMinute) = split(':', $timeProgram[1]);
- ($endHour, $endMinute) = split(':', $timeProgram[2]);
- my $periodVal = 0;
- my %period = ("FrMo" => 15,
- "FrSu" => 14,
- "ThFr" => 13,
- "WeFr" => 12,
- "TuTh" => 11,
- "MoWe" => 10,
- "Su" => 9,
- "Sa" => 8,
- "Fr" => 7,
- "Th" => 6,
- "We" => 5,
- "Tu" => 4,
- "Mo" => 3,
- "SaSu" => 2,
- "MoFr" => 1,
- "MoSu" => 0
- );
- while (($key, $val) = each(%period)) {
- $periodVal = $val if ($key eq $period);
- }
- if ($roomCtrlMode eq "buildingProtection") {
- $roomCtrlMode = 3;
- } elsif ($roomCtrlMode eq "preComfort") {
- $roomCtrlMode = 2;
- } elsif ($roomCtrlMode eq "economy") {
- $roomCtrlMode = 1;
- } else{
- $roomCtrlMode = 0;
- }
- if ($messagePartCntr > 1) {
- # set mcf flag
- $db5 |= 1;
- } else {
- # clear command
- $waitingCmds = $waitingCmds & 239 + 0xFF00;
- }
- $data = sprintf "%02X%02X%02X%02X%02X%02X", $db5, $endMinute, $endHour, $startMinute, $startHour, $periodVal << 4 | $roomCtrlMode << 2;
- $response = "time program setup send";
- $logLevel = 2;
- last;
- }
- }
- } elsif ($waitingCmds & 1) {
- # data request waiting
- $data = "0009";
- # clear command
- $waitingCmds = $waitingCmds & 254 + 0xFF00;
- $response = "data request send";
- $logLevel = 2;
- } elsif ($waitingCmds & 32) {
- # configuration request waiting
- $data = "0011";
- # clear command
- $waitingCmds = $waitingCmds & 223 + 0xFF00;
- $response = "configuration request send";
- $logLevel = 2;
- } elsif ($waitingCmds & 128) {
- # room control setup request waiting
- $data = "0019";
- # clear command
- $waitingCmds = $waitingCmds & 127 + 0xFF00;
- $response = "room control setup request send";
- $logLevel = 2;
- } elsif ($waitingCmds & 256) {
- # time program request waiting
- $data = "0021";
- # clear command
- $waitingCmds = $waitingCmds & 0xFEFF;
- $response = "time program request send";
- $logLevel = 2;
- }
- if ($waitingCmds == 0) {
- CommandDeleteReading(undef, "$name waitingCmds");
- } else {
- readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0);
- }
- } elsif ($mcf == 1) {
- # message incomplete
- $response = "acknowledge send, wating for next part of the message";
- $logLevel = 2;
- } elsif ($mcf == 2) {
- # automatic message control
- $response = "acknowledge send, automatic message control";
- $logLevel = 2;
- } elsif ($mcf == 3) {
- # reserved
- }
- return ($err, $response, $data, $logLevel);
- }
- # create SVG devices
- sub EnOcean_CreateSVG($$$)
- {
- my ($ctrl, $hash, $eepSVG) = @_;
- my $name = $hash->{NAME};
- my ($autocreateHash, $autocreateName, $autocreateDeviceRoom, $autocreateWeblinkRoom) =
- (undef, 'autocreate', 'EnOcean', 'Plots');
- my $filelogName = "FileLog_$name";
- my ($cmd, $eep, $weblinkName, $weblinkHash, $ret);
- if (defined($eepSVG) && $eepSVG =~ m/^([A-Za-z0-9]{2})-([A-Za-z0-9]{2})-([A-Za-z0-9]{2})$/i) {
- $eep = uc("$1.$2.$3");
- } 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) {
- $eep = uc("$1.$2.$3");
- } else {
- return undef;
- }
- # find autocreate device
- while (($autocreateName, $autocreateHash) = each(%defs)) {
- last if ($defs{$autocreateName}{TYPE} eq "autocreate");
- }
- # delete old SVG devices
- if (defined($ctrl) && $ctrl eq 'del' || !exists($defs{$filelogName})) {
- while (($weblinkName, $weblinkHash) = each(%defs)) {
- if ($weblinkName =~ /^SVG_$name.*/) {
- CommandDelete(undef, $weblinkName);
- Log3 $hash->{NAME}, 5, "EnOcean_CreateSVG: device $weblinkName deleted";
- }
- }
- }
- if (!defined(AttrVal($autocreateName, "disable", undef)) && exists($defs{$filelogName})) {
- if (exists $EnO_eepConfig{$eep}{GPLOT}) {
- # add GPLOT parameters
- $attr{$filelogName}{logtype} = $EnO_eepConfig{$eep}{GPLOT} . $attr{$filelogName}{logtype}
- if (!exists($attr{$filelogName}{logtype}) || $attr{$filelogName}{logtype} eq 'text');
- if (AttrVal($autocreateName, "weblink", 1)) {
- $autocreateWeblinkRoom = $attr{$autocreateName}{weblink_room} if (exists $attr{$autocreateName}{weblink_room});
- $autocreateWeblinkRoom = 'EnOcean' if ($autocreateWeblinkRoom eq '%TYPE');
- $autocreateWeblinkRoom = $name if ($autocreateWeblinkRoom eq '%NAME');
- $autocreateWeblinkRoom = $attr{$name}{room} if (exists $attr{$name}{room});
- my $wnr = 1;
- #create SVG devices
- foreach my $wdef (split(/,/, $EnO_eepConfig{$eep}{GPLOT})) {
- next if(!$wdef);
- my ($gplotfile, $stuff) = split(/:/, $wdef);
- next if(!$gplotfile);
- $weblinkName = "SVG_$name";
- $weblinkName .= "_$wnr" if($wnr > 1);
- $wnr++;
- next if (exists $defs{$weblinkName});
- $cmd = "$weblinkName SVG $filelogName:$gplotfile:CURRENT";
- Log3 $weblinkName, 2, "EnOcean define $cmd";
- $ret = CommandDefine(undef, $cmd);
- if($ret) {
- Log3 $weblinkName, 2, "EnOcean ERROR: define $cmd: $ret";
- last;
- }
- $attr{$weblinkName}{room} = $autocreateWeblinkRoom;
- $attr{$weblinkName}{title} = '"' . $name . ' Min $data{min1}, Max $data{max1}, Last $data{currval1}"';
- $ret = CommandSet(undef, "$weblinkName copyGplotFile");
- if($ret) {
- Log3 $weblinkName, 2, "EnOcean ERROR: set $weblinkName copyGplotFile: $ret";
- last;
- }
- }
- }
- }
- }
- return undef;
- }
- #CommandSave
- sub EnOcean_CommandSave($$)
- {
- my ($ctrl, $param) = @_;
- # find autocreate device
- my ($autocreateHash, $autocreateName);
- while (($autocreateName, $autocreateHash) = each(%defs)) {
- last if ($defs{$autocreateName}{TYPE} eq "autocreate");
- }
- my $autosave = AttrVal($autocreateName, "autosave", undef);
- if (!defined $autosave) {
- CommandSave($ctrl, $param) if (AttrVal("global", "autosave", 1));
- } elsif ($autosave) {
- CommandSave($ctrl, $param);
- }
- return;
- }
- sub EnOcean_readingsSingleUpdate($) {
- my ($readingParam) = @_;
- my ($hash, $readingName, $readingVal, $ctrl, $log) = @$readingParam;
- if (defined $hash) {
- readingsSingleUpdate($hash, $readingName, $readingVal, $ctrl) ;
- Log3 $hash->{NAME}, $log, "EnOcean " . $hash->{NAME} . " EVENT $readingName: $readingVal" if ($log);
- }
- return;
- }
- sub EnOcean_4BSRespWait($$$) {
- my ($ctrl, $hash, $subDef) = @_;
- my $IODev = $hash->{IODev}{NAME};
- my $IOHash = $defs{$IODev};
- $hash->{IODev}{helper}{"4BSRespWait"}{$subDef}{teachInReq} = "out";
- $hash->{IODev}{helper}{"4BSRespWait"}{$subDef}{hash} = $hash;
- if (!exists($hash->{IODev}{Teach})) {
- # enable teach-in receiving for 3 sec
- $hash->{IODev}{Teach} = 1;
- my %timeoutHash = (hash => $IOHash, function => "4BSRespTimeout", helper => "4BSRespWait");
- RemoveInternalTimer(\%timeoutHash);
- InternalTimer(gettimeofday() + 3, "EnOcean_RespTimeout", \%timeoutHash, 0);
- }
- return;
- }
- # Check SenderIDs
- sub EnOcean_CheckSenderID($$$)
- {
- my ($ctrl, $IODev, $senderID) = @_;
- if (!defined $IODev) {
- my (@listIODev, %listIODev);
- foreach my $dev (keys %defs) {
- next if ($defs{$dev}{TYPE} ne "EnOcean");
- push(@listIODev, $defs{$dev}{IODev}{NAME});
- }
- @listIODev = sort grep(!$listIODev{$_}++, @listIODev);
- if (@listIODev == 1) {
- $IODev = $listIODev[0];
- }
- }
- my $unusedID = 0;
- $unusedID = hex($defs{$IODev}{BaseID}) if ($defs{$IODev}{BaseID});
- my $IDCntr1;
- my $IDCntr2;
- if ($unusedID == 0) {
- $IDCntr1 = 0;
- $IDCntr2 = 0;
- } else {
- $IDCntr1 = $unusedID + 1;
- $IDCntr2 = $unusedID + 127;
- }
- if ($ctrl eq "getBaseID") {
- # get TCM BaseID of the EnOcean device
- if ($defs{$IODev}{BaseID}) {
- $senderID = $defs{$IODev}{BaseID}
- } else {
- $senderID = "0" x 8;
- }
- } elsif ($ctrl eq "getUsedID") {
- # find and sort used SenderIDs
- my @listID;
- my %listID;
- foreach my $dev (keys %defs) {
- next if ($defs{$dev}{TYPE} ne "EnOcean");
- push(@listID, grep(hex($_) >= $IDCntr1 && hex($_) <= $IDCntr2, $defs{$dev}{DEF}));
- push(@listID, $attr{$dev}{subDef}) if ($attr{$dev}{subDef});
- push(@listID, $attr{$dev}{subDefA}) if ($attr{$dev}{subDefA});
- push(@listID, $attr{$dev}{subDefB}) if ($attr{$dev}{subDefB});
- push(@listID, $attr{$dev}{subDefC}) if ($attr{$dev}{subDefC});
- push(@listID, $attr{$dev}{subDefD}) if ($attr{$dev}{subDefD});
- push(@listID, $attr{$dev}{subDefI}) if ($attr{$dev}{subDefI});
- push(@listID, $attr{$dev}{subDefH}) if ($attr{$dev}{subDefH});
- push(@listID, $attr{$dev}{subDefW}) if ($attr{$dev}{subDefW});
- push(@listID, $attr{$dev}{subDef0}) if ($attr{$dev}{subDef0});
- }
- $senderID = join(" ", sort grep(!$listID{$_}++, @listID));
- } elsif ($ctrl eq "getFreeID") {
- # find and sort free SenderIDs
- my (@freeID, @listID, %listID, @intersection, @difference, %count, $element);
- for (my $IDCntr = $IDCntr1; $IDCntr <= $IDCntr2; $IDCntr++) {
- push(@freeID, sprintf "%08X", $IDCntr);
- }
- foreach my $dev (keys %defs) {
- next if ($defs{$dev}{TYPE} ne "EnOcean");
- push(@listID, grep(hex($_) >= $IDCntr1 && hex($_) <= $IDCntr2, $defs{$dev}{DEF}));
- push(@listID, $attr{$dev}{subDef}) if ($attr{$dev}{subDef} && $attr{$dev}{subDef} ne "00000000");
- push(@listID, $attr{$dev}{subDefA}) if ($attr{$dev}{subDefA} && $attr{$dev}{subDefA} ne "00000000");
- push(@listID, $attr{$dev}{subDefB}) if ($attr{$dev}{subDefB} && $attr{$dev}{subDefB} ne "00000000");
- push(@listID, $attr{$dev}{subDefC}) if ($attr{$dev}{subDefC} && $attr{$dev}{subDefC} ne "00000000");
- push(@listID, $attr{$dev}{subDefD}) if ($attr{$dev}{subDefD} && $attr{$dev}{subDefD} ne "00000000");
- push(@listID, $attr{$dev}{subDefI}) if ($attr{$dev}{subDefI} && $attr{$dev}{subDefI} ne "00000000");
- push(@listID, $attr{$dev}{subDefH}) if ($attr{$dev}{subDefH} && $attr{$dev}{subDefH} ne "00000000");
- push(@listID, $attr{$dev}{subDefW}) if ($attr{$dev}{subDefW} && $attr{$dev}{subDefW} ne "00000000");
- push(@listID, $attr{$dev}{subDef0}) if ($attr{$dev}{subDef0} && $attr{$dev}{subDef0} ne "00000000");
- }
- @listID = sort grep(!$listID{$_}++, @listID);
- foreach $element (@listID, @freeID) {
- $count{$element}++
- }
- foreach $element (keys %count) {
- push @{$count{$element} > 1 ? \@intersection : \@difference }, $element;
- }
- $senderID = ':' . join(" ", sort @difference);
- } elsif ($ctrl eq "getNextID") {
- # get next free SenderID
- my (@freeID, @listID, %listID, @intersection, @difference, %count, $element);
- for (my $IDCntr = $IDCntr1; $IDCntr <= $IDCntr2; $IDCntr++) {
- push(@freeID, sprintf "%08X", $IDCntr);
- }
- foreach my $dev (keys %defs) {
- next if ($defs{$dev}{TYPE} ne "EnOcean");
- push(@listID, grep(hex($_) >= $IDCntr1 && hex($_) <= $IDCntr2, $defs{$dev}{DEF}));
- push(@listID, $attr{$dev}{subDef}) if ($attr{$dev}{subDef} && $attr{$dev}{subDef} ne "00000000");
- push(@listID, $attr{$dev}{subDefA}) if ($attr{$dev}{subDefA} && $attr{$dev}{subDefA} ne "00000000");
- push(@listID, $attr{$dev}{subDefB}) if ($attr{$dev}{subDefB} && $attr{$dev}{subDefB} ne "00000000");
- push(@listID, $attr{$dev}{subDefC}) if ($attr{$dev}{subDefC} && $attr{$dev}{subDefC} ne "00000000");
- push(@listID, $attr{$dev}{subDefD}) if ($attr{$dev}{subDefD} && $attr{$dev}{subDefD} ne "00000000");
- push(@listID, $attr{$dev}{subDefI}) if ($attr{$dev}{subDefI} && $attr{$dev}{subDefI} ne "00000000");
- push(@listID, $attr{$dev}{subDefH}) if ($attr{$dev}{subDefH} && $attr{$dev}{subDefH} ne "00000000");
- push(@listID, $attr{$dev}{subDefW}) if ($attr{$dev}{subDefW} && $attr{$dev}{subDefW} ne "00000000");
- push(@listID, $attr{$dev}{subDef0}) if ($attr{$dev}{subDef0} && $attr{$dev}{subDef0} ne "00000000");
- }
- @listID = sort grep(!$listID{$_}++, @listID);
- foreach $element (@listID, @freeID) {
- $count{$element}++
- }
- foreach $element (keys %count) {
- push @{$count{$element} > 1 ? \@intersection : \@difference }, $element;
- }
- @difference = sort @difference;
- if (defined $difference[0]) {
- $senderID = $difference[0];
- } else {
- $senderID = "0" x 8;
- Log3 $IODev, 2, "EnOcean $IODev no free senderIDs available";
- }
- } else {
- }
- return $senderID;
- }
- # assign next free SenderID
- sub EnOcean_AssignSenderID($$$$)
- {
- my ($ctrl, $hash, $attrName, $comMode) = @_;
- my $def = $hash->{DEF};
- my $err;
- my $name = $hash->{NAME};
- my $IODev = $hash->{IODev}{NAME};
- my $senderID = AttrVal($name, $attrName, "");
- # SenderID valid
- return ($err, $senderID) if ($senderID =~ m/^[\dA-Fa-f]{8}$/);
- return ("no IODev", $def) if (!defined $IODev);
- # DEF is SenderID
- if (hex($def) >= hex($defs{$IODev}{BaseID}) && hex($def) <= hex($defs{$IODev}{BaseID}) + 127) {
- if ($comMode eq "biDir") {
- $attr{$name}{comMode} = $comMode;
- } else {
- $attr{$name}{comMode} = "uniDir";
- }
- return ($err, $def);
- } else {
- if ($comMode eq "biDir") {
- $attr{$name}{comMode} = $comMode;
- } else {
- $attr{$name}{comMode} = "confirm";
- }
- $senderID = EnOcean_CheckSenderID("getNextID", $IODev, "00000000");
- }
- #Log3 $name, 2, "EnOcean $name SenderID: $senderID assigned";
- #CommandAttr(undef, "$name $attrName $senderID");
- $attr{$name}{$attrName} = $senderID;
- return ($err, $senderID);
- }
- # split chained data message
- sub EnOcean_SndCdm($$$$$$$$)
- {
- my ($ctrl, $hash, $packetType, $rorg, $data, $senderID, $status, $destinationID) = @_;
- if (!defined $data) {
- Log3 $hash->{NAME}, 5, "EnOcean $hash->{NAME} EnOcean_SndCDM SenderID: $senderID DestinationID: $destinationID " .
- "PacketType: $packetType RORG: $rorg DATA: undef STATUS: $status";
- return;
- }
- my ($seq, $idx, $len, $dataPart, $dataPartLen) = (0, 0, length($data) / 2, undef, 14);
- # split telelegram with optional data
- $dataPartLen = 9 if ($destinationID ne "FFFFFFFF");
- if ($packetType == 1 && $len > $dataPartLen) {
- # first CDM telegram
- if ($dataPartLen == 14) {
- $data =~ m/^(....................)(.*)$/;
- $dataPart = (sprintf "%02X", $seq << 6 | $idx) . (sprintf "%04X", $len) . $rorg . $1;
- $data = $2;
- } else {
- $data =~ m/^(..........)(.*)$/;
- $dataPart = (sprintf "%02X", $seq << 6 | $idx) . (sprintf "%04X", $len) . $rorg . $1;
- $data = $2;
- }
- $idx ++;
- $len -= $dataPartLen - 5;
- EnOcean_SndRadio($ctrl, $hash, $packetType, "40", $dataPart, $senderID, $status, $destinationID);
- while ($len > 0) {
- if ($len > $dataPartLen - 2) {
- if ($dataPartLen == 14) {
- $data =~ m/^(..........................)(.*)$/;
- $dataPart = (sprintf "%02X", $seq << 6 | $idx) . $1;
- $data = $2;
- } else {
- $data =~ m/^(................)(.*)$/;
- $dataPart = (sprintf "%02X", $seq << 6 | $idx) . $1;
- $data = $2;
- }
- $idx ++;
- $len -= $dataPartLen - 2;
- } else {
- $dataPart = (sprintf "%02X", $seq << 6 | $idx) . $data;
- $len = 0;
- }
- EnOcean_SndRadio($ctrl, $hash, $packetType, "40", $dataPart, $senderID, $status, $destinationID);
- }
- } else {
- # not necessary to split
- EnOcean_SndRadio($ctrl, $hash, $packetType, $rorg, $data, $senderID, $status, $destinationID);
- }
- return;
- }
- # send ESP3 Packet Type Radio
- sub EnOcean_SndRadio($$$$$$$$)
- {
- my ($ctrl, $hash, $packetType, $rorg, $data, $senderID, $status, $destinationID) = @_;
- if (!defined $data) {
- Log3 $hash->{NAME}, 5, "EnOcean $hash->{NAME} EnOcean_SndRadio SenderID: $senderID DestinationID: $destinationID " .
- "PacketType: $packetType RORG: $rorg DATA: undef STATUS: $status";
- return;
- }
- my ($err, $response, $loglevel);
- my $header;
- my $odata = "";
- my $odataLength = 0;
- if ($packetType == 1) {
- ($err, $rorg, $data, $response, $loglevel) = EnOcean_sec_convertToSecure($hash, $packetType, $rorg, $data);
- if (defined $err) {
- Log3 $hash->{NAME}, $loglevel, "EnOcean $hash->{NAME} Error: $err";
- return "";
- }
- if (AttrVal($hash->{NAME}, "repeatingAllowed", "yes") eq "no") {
- $status = substr($status, 0, 1) . "F";
- }
- if ($rorg eq "A6") {
- # ADT telegram
- $data .= $destinationID;
- } elsif ($destinationID ne "FFFFFFFF") {
- # SubTelNum = 03, DestinationID:8, RSSI = FF, secLevel = 00
- $odata = sprintf "03%sFF00", $destinationID;
- $odataLength = 7;
- }
- # Data Length:4 Optional Length:2 Packet Type:2
- $header = sprintf "%04X%02X%02X", (length($data) / 2 + 6), $odataLength, $packetType;
- Log3 $hash->{NAME}, 4, "EnOcean $hash->{NAME} sent PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status ODATA: $odata";
- $data = $rorg . $data . $senderID . $status . $odata;
- } elsif ($packetType == 2 || $packetType == 6) {
- # smart ack commands
- # Data Length:4 Optional Length:2 Packet Type:2
- $header = sprintf "%04X%02X%02X", length($data) / 2, 0, $packetType;
- Log3 $hash->{NAME}, 4, "EnOcean $hash->{NAME} sent PacketType: $packetType DATA: $data";
- } elsif ($packetType == 7) {
- my $delay = 0;
- $delay = 1 if ($destinationID eq "FFFFFFFF");
- $senderID = "00000000";
- #$senderID = "00000000" if ($destinationID eq "FFFFFFFF");
- $odata = sprintf "%s%sFF%02X", $destinationID, $senderID, $delay;
- $odataLength = 10;
- # Data Length:4 Optional Length:2 Packet Type:2
- $header = sprintf "%04X%02X%02X", (length($data) / 2), $odataLength, $packetType;
- Log3 $hash->{NAME}, 4, "EnOcean $hash->{NAME} sent PacketType: $packetType DATA: $data ODATA: $odata";
- $data .= $odata;
- }
- if (defined $ctrl) {
- # sent telegram delayed
- my @param = ($hash, $header, $data);
- InternalTimer(gettimeofday() + $ctrl, 'EnOcean_IOWriteTimer', \@param, 0);
- } else {
- IOWrite($hash, $header, $data);
- }
- return;
- }
- #
- sub EnOcean_IOWriteTimer($)
- {
- my ($ioParam) = @_;
- my ($hash, $header, $data) = @$ioParam;
- IOWrite($hash, $header, $data);
- return;
- }
- # Scale Readings
- sub EnOcean_ReadingScaled($$$$)
- {
- my ($hash, $readingVal, $readingMin, $readingMax) = @_;
- my $name = $hash->{NAME};
- my $valScaled;
- my $scaleDecimals = AttrVal($name, "scaleDecimals", undef);
- my $scaleMin = AttrVal($name, "scaleMin", undef);
- my $scaleMax = AttrVal($name, "scaleMax", undef);
- if (defined $scaleMax && defined $scaleMin &&
- $scaleMax =~ m/^[+-]?\d+(\.\d+)?$/ && $scaleMin =~ m/^[+-]?\d+(\.\d+)?$/) {
- $valScaled = ($readingMin*$scaleMax-$scaleMin*$readingMax)/
- ($readingMin-$readingMax)+
- ($scaleMin-$scaleMax)/($readingMin-$readingMax)*$readingVal;
- }
- if (defined $scaleDecimals && $scaleDecimals =~ m/^[0-9]?$/) {
- $scaleDecimals = "%0." . $scaleDecimals . "f";
- $valScaled = sprintf "$scaleDecimals", $valScaled;
- }
- return $valScaled;
- }
- # Reorganize Strings
- sub EnOcean_ReorgList($)
- {
- my ($list) = @_;
- my @list = split("[ \t][ \t]*", $list);
- my %list;
- @list = sort grep(!$list{$_}++, @list);
- $list = join(" ", @list) . " ";
- return $list;
- }
- # EnOcean_Set called from sub InternalTimer()
- sub EnOcean_TimerSet($)
- {
- my ($par) = @_;
- EnOcean_Set($par->{hash}, @{$par->{timerCmd}});
- }
- #
- sub EnOcean_InternalTimer($$$$$)
- {
- my ($modifier, $tim, $callback, $hash, $waitIfInitNotDone) = @_;
- my $mHash = {};
- my $timerName = "$hash->{NAME}_$modifier";
- if ($modifier eq "") {
- $mHash = $hash;
- } else {
- if (exists ($hash->{helper}{TIMER}{$timerName})) {
- $mHash = $hash->{helper}{TIMER}{$timerName};
- Log3 $hash->{NAME}, 5, "EnOcean_InternalTimer setting mHash with stored $timerName";
- } else {
- $mHash = {HASH => $hash, NAME => $timerName, MODIFIER => $modifier};
- $hash->{helper}{TIMER}{$timerName} = $mHash;
- Log3 $hash->{NAME}, 5, "EnOcean_InternalTimer setting mHash with $timerName";
- }
- }
- InternalTimer($tim, $callback, $mHash, $waitIfInitNotDone);
- Log3 $hash->{NAME}, 5, "EnOcean setting timer $timerName at " . strftime("%Y-%m-%d %H:%M:%S", localtime($tim));
- return;
- }
- #
- sub EnOcean_RemoveInternalTimer($$)
- {
- my ($modifier, $hash) = @_;
- my $mHash = {};
- my $timerName = "$hash->{NAME}_$modifier";
- if ($modifier eq "") {
- RemoveInternalTimer($hash);
- } else {
- $mHash = $hash->{helper}{TIMER}{$timerName};
- if (defined($mHash)) {
- delete $hash->{helper}{TIMER}{$timerName};
- RemoveInternalTimer($mHash);
- }
- }
- Log3 $hash->{NAME}, 5, "EnOcean removing timer $timerName";
- return;
- }
- #
- sub EnOcean_observeInit($$@)
- {
- #init observe
- my ($ctrl, $hash, @cmdValue) = @_;
- my ($err, $name) = (undef, $hash->{NAME});
- return (undef, $ctrl) if (lc(AttrVal($name, "observe", "off")) eq "off");
- return (undef, $ctrl) if (defined($hash->{helper}{observeCntr}) && $hash->{helper}{observeCntr} > 0);
- $hash->{helper}{observeCntr} = 1;
- #my @observeExeptions = split("[ \t][ \t]*", AttrVal($name, "observeExeptions", ""));
- my @observeRefDev = split("[ \t][ \t]*", AttrVal($name, "observeRefDev", $name));
- $hash->{helper}{observeRefDev} = \@observeRefDev;
- Log3 $name, 4, "EnOcean $name < observeRefDev " . join(" ", @{$hash->{helper}{observeRefDev}}) . " (init)";
- $hash->{helper}{lastCmdFunction} = "set";
- # @cmdValue = (<name>, <cmd>, <param1>, <param2>, ...)
- $hash->{helper}{lastCmdValue} = \@cmdValue;
- my $observeCmds = AttrVal($name, "observeCmds", undef);
- my @observeCmds;
- #my %observeCmds;
- my ($cmdPair, $cmdSent, $cmdReceived);
- if (defined $observeCmds) {
- @observeCmds = split("[ \t][ \t]*", $observeCmds);
- foreach my $cmdPair (@observeCmds) {
- ($cmdSent, $cmdReceived) = split(':', $cmdPair);
- $hash->{helper}{observeCmds}{$cmdSent} = $cmdReceived;
- }
- } else {
- $hash->{helper}{observeCmds}{$cmdValue[1]} = $cmdValue[1];
- }
- $hash->{helper}{observeCntr} = 1;
- #CommandDeleteReading(undef, "$name observeFailedDev");
- readingsSingleUpdate($hash, "observeFailedDev", "", 0);
- my %functionHash = (hash => $hash, function => "observe");
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + AttrVal($name, "observeInterval", 1), "EnOcean_observeRepeat", \%functionHash, 0);
- Log3 $name, 4, "EnOcean set " . join(" ", @cmdValue) . " observing started";
- return ($err, $ctrl);
- }
- #
- sub EnOcean_observeParse($$@)
- {
- # observe acknowledge
- my ($ctrl, $hash, @cmdValue) = @_;
- my ($err, $name) = (undef, $hash->{NAME});
- return (undef, $ctrl) if (lc(AttrVal($name, "observe", "off")) eq "off");
- # observing disabled or ignore second and following acknowledgment telegrams
- return (undef, $ctrl) if (!exists($hash->{helper}{observeRefDev}) || @{$hash->{helper}{observeRefDev}} == 0);
- my $devName = shift @cmdValue;
- my $cmd = shift @cmdValue;
- $cmd = shift @cmdValue if ($cmd eq "state");
- my ($observeRefDevIdx) = grep{$hash->{helper}{observeRefDev}[$_] eq $devName} 0..$#{$hash->{helper}{observeRefDev}};
- # device not observed
- return (undef, $ctrl) if (!defined $observeRefDevIdx);
- Log3 $name, 4, "EnOcean $name < observeRefDev " . join(" ", @{$hash->{helper}{observeRefDev}}) . " parsed";
- Log3 $name, 4, "EnOcean $name < $devName $cmd " . join(" ", @cmdValue) . " received";
- if (@{$hash->{helper}{observeRefDev}} == 1) {
- # last acknowledgment telegram stops observing
- delete $hash->{helper}{observeCmds};
- delete $hash->{helper}{observeCntr};
- delete $hash->{helper}{observeRefDev};
- delete $hash->{helper}{lastCmdFunction};
- delete $hash->{helper}{lastCmdValue};
- my %functionHash = (hash => $hash, function => "observe");
- RemoveInternalTimer(\%functionHash);
- Log3 $name, 4, "EnOcean $name < $devName $cmd " . join(" ", @cmdValue) . " observing stopped";
- } else {
- # remove the device that has sent a telegram
- Log3 $name, 4, "EnOcean $name < observeRefDev " . $hash->{helper}{observeRefDev}[$observeRefDevIdx] . " removed";
- splice(@{$hash->{helper}{observeRefDev}}, $observeRefDevIdx, 1);
- if (lc(AttrVal($name, "observeLogic", "or")) eq "and") {
- # AND logic: remove the device that has sent a telegram and await further telegrams
- Log3 $name, 4, "EnOcean $name < observeRefDev " . join(" ", @{$hash->{helper}{observeRefDev}}) . " observing continued";
- } else {
- # OR logic: last acknowledgment telegram stops observing
- #shift @{$hash->{helper}{observeRefDev}};
- delete $hash->{helper}{observeCmds};
- delete $hash->{helper}{observeCntr};
- delete $hash->{helper}{observeRefDev};
- delete $hash->{helper}{lastCmdFunction};
- delete $hash->{helper}{lastCmdValue};
- my %functionHash = (hash => $hash, function => "observe");
- RemoveInternalTimer(\%functionHash);
- Log3 $name, 4, "EnOcean $name < $devName $cmd " . join(" ", @cmdValue) . " observing stopped";
- }
- }
- # if (defined($hash->{helper}{lastCmdValue}[1]) && $hash->{helper}{lastCmdValue}[1] eq $cmd) {
- # acknowledgment telegram ok, cancel the repetition of the command
- # } else {
- # acknowledgment telegram does not match the telegram sent, cancel the repetition of the command
- # }
- return ($err, $ctrl);
- }
- #
- sub EnOcean_observeRepeat($)
- {
- #timer expires without acknowledgment telegram, repeat command
- my ($functionHash) = @_;
- my $hash = $functionHash->{hash};
- my $name = $hash->{NAME};
- return if (AttrVal($name, "observe", "off") eq "off" || !defined($hash->{helper}{observeCntr}));
- #my @observeExeptions = split("[ \t][ \t]*", AttrVal($name, "observeExeptions", ""));
- if ($hash->{helper}{observeCntr} <= AttrVal($name, "observeCmdRepetition", 2)) {
- #repeat last command
- $hash->{helper}{observeCntr} += 1;
- Log3 $name, 4, "EnOcean set " . join(" ", @{$hash->{helper}{lastCmdValue}}) . " repeated";
- EnOcean_Set($hash, @{$hash->{helper}{lastCmdValue}});
- RemoveInternalTimer($functionHash);
- InternalTimer(gettimeofday() + AttrVal($name, "observeInterval", 1), "EnOcean_observeRepeat", $functionHash, 0);
- } else {
- # reached the maximum number of retries, clear last command
- Log3 $name, 2, "EnOcean set " . join(" ", @{$hash->{helper}{lastCmdValue}}) .
- " observing " . join(" ", @{$hash->{helper}{observeRefDev}}) . " failed";
- #splice(@{$hash->{helper}{observeRefDev}}, 0);
- my $actionCmd = AttrVal($name, "observeErrorAction", undef);
- if (defined $actionCmd) {
- # error action exec
- my %specials = ("%NAME" => shift(@{$hash->{helper}{lastCmdValue}}),
- "%FAILEDDEV" => join(" ", @{$hash->{helper}{observeRefDev}}),
- "%TYPE" => $hash->{TYPE},
- "%EVENT" => ReplaceEventMap($name, join(" ", @{$hash->{helper}{lastCmdValue}}), 1)
- );
- $actionCmd = EvalSpecials($actionCmd, %specials);
- my $ret = AnalyzeCommandChain(undef, $actionCmd);
- Log3 $name, 2, "EnOcean $name observeErrorAction ERROR: $ret" if($ret);
- }
- readingsSingleUpdate($hash, "observeFailedDev", join(" ", @{$hash->{helper}{observeRefDev}}), 1);
- delete $hash->{helper}{observeCmds};
- delete $hash->{helper}{observeCntr};
- delete $hash->{helper}{observeRefDev};
- delete $hash->{helper}{lastCmdFunction};
- delete $hash->{helper}{lastCmdValue};
- RemoveInternalTimer($functionHash);
- }
- return;
- }
- sub EnOcean_RLT($) {
- # Radio Link Test
- my ($rltParam) = @_;
- my ($ctrl, $hash, $dataRx, $subDef, $rltMode, $rssiMaster) = @$rltParam;
- my $name = $hash->{NAME};
- my $def = $hash->{DEF};
- my ($err, $logLevel, $response, $dataTx, $rorg, $msgID) = (undef, 5, undef, undef, undef, 0);
- my $rltCntrMax = AttrVal($name, 'rltRepeat', 16);
- my $rltType = AttrVal($name, 'rltType', '4BS');
- if ($rltType eq '1BS') {
- $msgID = $hash->{helper}{rlt}{cntr} & 0x3F if (exists($hash->{helper}{rlt}{cntr}));
- $dataTx = sprintf("%02X", ($msgID & 0x3C) << 2 | 8 | ($msgID & 3) << 1);
- $rorg = 'D5';
- } else {
- $dataTx = '0000000C';
- $rorg = 'A5';
- }
- if ($ctrl eq 'start') {
- RemoveInternalTimer($hash->{helper}{rlt}{param});
- $hash->{helper}{rlt}{cntr} = 0;
- $hash->{helper}{rlt}{param}[0] = 'periodic';
- readingsSingleUpdate($hash, 'state', 'active', 1);
- EnOcean_SndRadio(undef, $hash, 1, $rorg, $dataTx, $subDef, "00", $def);
- InternalTimer(gettimeofday() + 0.15, "EnOcean_RLT", $hash->{helper}{rlt}{param}, 0);
- } elsif ($ctrl eq 'parse') {
- $hash->{helper}{rlt}{param}[0] = 'periodic';
- if ($hash->{helper}{rlt}{cntr} < $rltCntrMax) {
- # store received RLT data from slave
- $hash->{helper}{rlt}{dataRx}[$hash->{helper}{rlt}{cntr}] = $dataRx;
- $hash->{helper}{rlt}{rssiMaster}[$hash->{helper}{rlt}{cntr}] = $rssiMaster;
- if ($hash->{helper}{rlt}{cntr} < $rltCntrMax - 1) {
- RemoveInternalTimer($hash->{helper}{rlt}{param});
- $hash->{helper}{rlt}{cntr} ++;
- EnOcean_SndRadio(undef, $hash, 1, $rorg, $dataTx, $subDef, "00", $def);
- InternalTimer(gettimeofday() + 0.15, "EnOcean_RLT", $hash->{helper}{rlt}{param}, 0);
- }
- } else {
- RemoveInternalTimer($hash->{helper}{rlt}{param});
- readingsSingleUpdate($hash, 'state', 'stopped', 1);
- EnOcean_RLTResult(undef, $hash, $rltType, $rltCntrMax);
- if (exists $hash->{helper}{rlt}{oldDev}) {
- # activate old device subType
- my $oldHash = $hash->{helper}{rlt}{oldDev}[0];
- my $oldName = $hash->{helper}{rlt}{oldDev}[1];
- my $oldDef = $hash->{helper}{rlt}{oldDev}[2];
- CommandModify(undef, "$oldName $oldDef");
- $modules{EnOcean}{defptr}{$oldDef} = $oldHash;
- }
- delete $hash->{helper}{rlt};
- # delete deviceID
- CommandModify(undef, "$name 00000000");
- delete $modules{EnOcean}{defptr}{$def};
- }
- } elsif ($ctrl eq 'periodic') {
- RemoveInternalTimer($hash->{helper}{rlt}{param});
- if ($hash->{helper}{rlt}{cntr} < $rltCntrMax - 1) {
- # no RLT_SlaveTest telegram received from slave
- $hash->{helper}{rlt}{param}[0] = 'periodic';
- $hash->{helper}{rlt}{cntr} ++;
- EnOcean_SndRadio(undef, $hash, 1, $rorg, $dataTx, $subDef, "00", $def);
- InternalTimer(gettimeofday() + 0.11, "EnOcean_RLT", $hash->{helper}{rlt}{param}, 0);
- } else {
- # waiting for last RLT_SlaveTest telegram
- $hash->{helper}{rlt}{param}[0] = 'waiting';
- InternalTimer(gettimeofday() + 0.15, "EnOcean_RLT", $hash->{helper}{rlt}{param}, 0);
- }
- } elsif ($ctrl eq 'waiting') {
- readingsSingleUpdate($hash, 'state', 'stopped', 1);
- EnOcean_RLTResult(undef, $hash, $rltType, $rltCntrMax);
- if (exists $hash->{helper}{rlt}{oldDev}) {
- # activate old device subType
- my $oldHash = $hash->{helper}{rlt}{oldDev}[0];
- my $oldName = $hash->{helper}{rlt}{oldDev}[1];
- my $oldDef = $hash->{helper}{rlt}{oldDev}[2];
- CommandModify(undef, "$oldName $oldDef");
- $modules{EnOcean}{defptr}{$oldDef} = $oldHash;
- }
- delete $hash->{helper}{rlt};
- # delete deviceID
- CommandModify(undef, "$name 00000000");
- delete $modules{EnOcean}{defptr}{$def};
- } elsif ($ctrl eq 'standby') {
- RemoveInternalTimer($hash->{helper}{rlt}{param});
- delete $hash->{helper}{rlt};
- readingsSingleUpdate($hash, 'state', 'standby', 1);
- # delete deviceID
- CommandModify(undef, "$name 00000000");
- delete $modules{EnOcean}{defptr}{$def};
- } elsif ($ctrl eq 'stop') {
- RemoveInternalTimer($hash->{helper}{rlt}{param});
- if (exists $hash->{helper}{rlt}{oldDev}) {
- # activate old device subType
- my $oldHash = $hash->{helper}{rlt}{oldDev}[0];
- my $oldName = $hash->{helper}{rlt}{oldDev}[1];
- my $oldDef = $hash->{helper}{rlt}{oldDev}[2];
- CommandModify(undef, "$oldName $oldDef");
- $modules{EnOcean}{defptr}{$oldDef} = $oldHash;
- }
- delete $hash->{helper}{rlt};
- readingsSingleUpdate($hash, 'state', 'stopped', 1);
- # delete deviceID
- CommandModify(undef, "$name 00000000");
- delete $modules{EnOcean}{defptr}{$def};
- }
- return ($err, $logLevel, $response);
- }
- sub EnOcean_RLTResult($$$$) {
- # show RLT results
- my ($ctrl, $hash, $rltType, $rltCntrMax) = @_;
- my $name = $hash->{NAME};
- my ($err, $logLevel, $response, $data, $msgCntr, $msgID, $subTelNum, $rssiMaster, $rssiMasterAvg, $rssiSlave, $rssiLevel1, $rssiLevel2, $rssiNonEnOcean) =
- (undef, 5, undef, undef, 0, 0, 0, 0, 0, 0, 0, 0, 0);
- my %rssi = (0 => '-', 1 => '>= -31', 2 => '-32', 0x3F => '<= -93');
- my %rssiNonEnOcean =
- (0 => '-',
- 1 => '>= -31',
- 2 => '-32 ... -37',
- 3 => '-38 ... -43',
- 4 => '-44 ... -49',
- 5 => '-50 ... -55',
- 6 => '-56 ... -61',
- 7 => '-62 ... -67',
- 8 => '-68 ... -73',
- 9 => '-74 ... -79',
- 10 => '-80 ... -85',
- 11 => '<= -92');
- for (my $cntr = 0; $cntr < $rltCntrMax; $cntr++) {
- $data = $hash->{helper}{rlt}{dataRx}[$cntr];
- if (defined $data) {
- $msgCntr ++;
- $rssiMaster = -$hash->{helper}{rlt}{rssiMaster}[$cntr];
- $rssiMasterAvg += $rssiMaster;
- if ($rltType eq '1BS') {
- $msgID = (hex($data) & 0xF0) >> 6 | (hex($data) & 6) >> 1;
- Log3 $name, 2, "EnOcean RLT DeviceID: " . $hash->{DEF} . " msgCntr: ". ($cntr + 1) . " msgID: $msgID RSSI Master: $rssiMaster response received";
- } else {
- $subTelNum = (hex(substr($data, 0, 2)) & 0xC0) >> 6;
- $rssiLevel2 = hex(substr($data, 0, 2)) & 0x3F;
- $rssiLevel2 = $rssi{$rssiLevel2} if (exists $rssi{$rssiLevel2});
- $rssiLevel1 = hex(substr($data, 2, 2));
- $rssiLevel1 = $rssi{$rssiLevel1} if (exists $rssi{$rssiLevel1});
- $rssiSlave = hex(substr($data, 4, 2));
- $rssiSlave = $rssi{$rssiSlave} if (exists $rssi{$rssiSlave});
- $rssiNonEnOcean = (hex(substr($data, 6, 2)) & 0xF0) >> 4;
- $rssiNonEnOcean = $rssiNonEnOcean{$rssiNonEnOcean} if (exists $rssiNonEnOcean{$rssiNonEnOcean});
- $msgID = (hex(substr($data, 6, 2)) & 6) >> 1;
- Log3 $name, 2, "EnOcean RLT DeviceID: " . $hash->{DEF} . " msgCntr: ". ($cntr + 1) . " subTelNum: $subTelNum " .
- "RSSI Master: $rssiMaster RSSI Slave: $rssiSlave RSSI Level 1: $rssiLevel1 RSSI Level 2: $rssiLevel2 " .
- "RSSI non EnOcean: $rssiNonEnOcean";
- }
- } else {
- Log3 $name, 2, "EnOcean RLT DeviceID: " . $hash->{DEF} . " msgCntr: ". ($cntr + 1) . " no answer";
- }
- }
- if ($msgCntr > 0) {
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, 'msgLost', (sprintf "%0.1f", ($rltCntrMax - $msgCntr) / $rltCntrMax * 100));
- readingsBulkUpdate($hash, 'rssiMasterAvg', (sprintf "%0.1f", $rssiMasterAvg / $msgCntr));
- readingsEndUpdate($hash, 1);
- } else {
- CommandDeleteReading(undef, "$name msgLost");
- CommandDeleteReading(undef, "$name rssiMasterAvg");
- }
- return ($err, $logLevel, $response);
- }
- #
- sub EnOcean_energyManagement_01Parse($@)
- {
- my ($hash, @db) = @_;
- my $name = $hash->{NAME};
- # [drLevel] = 15 : no requests for reduction in power consumptions
- my $drLevel = ($db[0] & 0xF0) >> 4;
- my $powerUsage = $db[2] & 0x7F;
- my $powerUsageLevel = $db[0] & 1 ? "max" : "min";
- my $powerUsageScale = $db[2] & 0x80 ? "rel" : "max";
- my $randomStart = $db[0] & 4 ? "yes" : "no";
- my $randomEnd = $db[0] & 2 ? "yes" : "no";
- my $randomTime = rand(AttrVal($name, "demandRespRandomTime", 1));
- my $setpoint = $db[3];
- my $timeout = $db[1] * 15 * 60;
- $randomTime = $randomTime > $timeout && $timeout > 0 ? rand($timeout) : rand($randomTime);
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "randomEnd", $randomEnd);
- readingsBulkUpdate($hash, "randomStart", $randomStart);
- my %timeoutHash = (hash => $hash, function => "demandResponseTimeout");
- my %functionHash = (hash => $hash,
- function => "demandResponseExec",
- drLevel => $drLevel,
- powerUsage => $powerUsage,
- powerUsageLevel => $powerUsageLevel,
- powerUsageScale => $powerUsageScale,
- setpoint => $setpoint
- );
- RemoveInternalTimer(\%timeoutHash);
- RemoveInternalTimer(\%functionHash);
- if ($timeout > 0 && $drLevel < 15) {
- # timeout timer
- InternalTimer(gettimeofday() + $timeout, "EnOcean_demandResponseTimeout", \%timeoutHash, 0);
- my ($sec, $min, $hour, $day, $month, $year) = localtime(time + $timeout);
- $month += 1;
- $year += 1900;
- $min = $min < 10 ? $min = "0" . $min : $min;
- $hour = $hour < 10 ? $hour = "0" . $hour : $hour;
- $day = $day < 10 ? $day = "0" . $day : $day;
- $month = $month < 10 ? $ month = "0". $month : $month;
- readingsBulkUpdate($hash, "timeout", "$year-$month-$day $hour:$min:$sec");
- } else {
- CommandDeleteReading(undef, "$name timeout");
- }
- if ($randomStart eq "yes" && ReadingsVal($name, "level", 15) == 15) {
- readingsBulkUpdate($hash, "state", "waiting_for_start");
- Log3 $name, 3, "EnOcean set $name demand response waiting for start";
- InternalTimer(gettimeofday() + $randomTime, "EnOcean_demandResponseExec", \%functionHash, 0);
- } elsif ($randomEnd eq "yes" && ReadingsVal($name, "level", 15) < 15) {
- readingsBulkUpdate($hash, "state", "waiting_for_stop");
- Log3 $name, 3, "EnOcean set $name demand response waiting for stop";
- InternalTimer(gettimeofday() + $randomTime, "EnOcean_demandResponseExec", \%functionHash, 0);
- } else {
- EnOcean_demandResponseExec(\%functionHash);
- }
- readingsEndUpdate($hash, 1);
- return;
- }
- #
- sub EnOcean_demandResponseExec($)
- {
- my ($functionHash) = @_;
- my $function = $functionHash->{function};
- my $hash = $functionHash->{hash};
- my $drLevel = $functionHash->{drLevel};
- my $powerUsage = $functionHash->{powerUsage};
- my $powerUsageLevel = $functionHash->{powerUsageLevel};
- my $powerUsageScale = $functionHash->{powerUsageScale};
- my $setpoint = $functionHash->{setpoint};
- my $name = $hash->{NAME};
- my $actionCmd = AttrVal($name, "demandRespAction", undef);
- # save old values
- $hash->{helper}{drLevel} = ReadingsVal($name, "level", 15);
- $hash->{helper}{powerUsage} = ReadingsVal($name, "powerUsage", 100);
- $hash->{helper}{powerUsageLevel} = ReadingsVal($name, "powerUsageLevel", "max");
- $hash->{helper}{powerUsageScale} = ReadingsVal($name, "powerUsageScale", "max");
- $hash->{helper}{setpoint} = ReadingsVal($name, "setpoint", 255);
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "level", $drLevel);
- readingsBulkUpdate($hash, "powerUsage", $powerUsage);
- readingsBulkUpdate($hash, "powerUsageLevel", $powerUsageLevel);
- readingsBulkUpdate($hash, "powerUsageScale", $powerUsageScale);
- readingsBulkUpdate($hash, "setpoint", $setpoint);
- if ($drLevel == 15) {
- readingsBulkUpdate($hash, "state", "off");
- Log3 $name, 3, "EnOcean set $name demand response off";
- } else {
- readingsBulkUpdate($hash, "state", "on");
- Log3 $name, 3, "EnOcean set $name demand response on";
- }
- readingsEndUpdate($hash, 1);
- if (defined $actionCmd) {
- # action exec
- my %specials= ("%NAME" => $name,
- "%TYPE" => $hash->{TYPE},
- "%LEVEL" => $drLevel,
- "%SETPOINT" => $setpoint,
- "%POWERUSAGE" => $powerUsage,
- "%POWERUSAGESCALE" => $powerUsageScale,
- "%POWERUSAGELEVEL" => $powerUsageLevel,
- "%STATE" => ReadingsVal($name, "state", "off")
- );
- $actionCmd = EvalSpecials($actionCmd, %specials);
- my $ret = AnalyzeCommandChain(undef, $actionCmd);
- Log3 $name, 2, "EnOcean $name demandRespAction ERROR: $ret" if($ret);
- }
- return;
- }
- #
- sub EnOcean_demandResponseTimeout($)
- {
- my ($functionHash) = @_;
- my $function = $functionHash->{function};
- my $hash = $functionHash->{hash};
- my $name = $hash->{NAME};
- my $actionCmd = AttrVal($name, "demandRespAction", undef);
- my $data;
- my $timeoutLevel = AttrVal($name, "demandRespTimeoutLevel", "max");
- RemoveInternalTimer($functionHash);
- CommandDeleteReading(undef, "$name timeout");
- my $drLevel = 15;
- my $powerUsage = 100;
- my $powerUsageLevel = "max";
- my $powerUsageScale = "max";
- my $setpoint = 255;
- my $timeout = 0;
- if ($timeoutLevel eq "last" && defined($hash->{helper}{drLevel})) {
- # restore old values
- $drLevel = $hash->{helper}{drLevel};
- $powerUsage = $hash->{helper}{powerUsage};
- $powerUsageScale = $hash->{helper}{powerUsageLevel};
- $powerUsageLevel = $hash->{helper}{powerUsageScale};
- $setpoint = $hash->{helper}{setpoint};
- }
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "level", $drLevel);
- readingsBulkUpdate($hash, "powerUsage", $powerUsage);
- readingsBulkUpdate($hash, "powerUsageLevel", $powerUsageLevel);
- readingsBulkUpdate($hash, "powerUsageScale", $powerUsageScale);
- readingsBulkUpdate($hash, "setpoint", $setpoint);
- if ($drLevel == 15) {
- readingsBulkUpdate($hash, "state", "off");
- Log3 $name, 3, "EnOcean set $name demand response off";
- } else {
- readingsBulkUpdate($hash, "state", "on");
- Log3 $name, 3, "EnOcean set $name demand response on";
- }
- readingsEndUpdate($hash, 1);
- my %specials= ("%NAME" => $name,
- "%TYPE" => $hash->{TYPE},
- "%LEVEL" => $drLevel,
- "%SETPOINT" => $setpoint,
- "%POWERUSAGE" => $powerUsage,
- "%POWERUSAGESCALE" => $powerUsageScale,
- "%POWERUSAGELEVEL" => $powerUsageLevel,
- "%STATE" => ReadingsVal($name, "state", "off")
- );
- $powerUsageLevel = $powerUsageLevel eq "max" ? 1 : 0;
- $powerUsageScale = $powerUsageScale eq "rel" ? 0x80 : 0;
- my $randomStart = ReadingsVal($name, "randomStart", "no") eq "yes" ? 4 : 0;
- my $randomEnd = ReadingsVal($name, "randomEnd", "no") eq "yes" ? 2 : 0;
- $data = sprintf "%02X%02X%02X%02X", $setpoint, $powerUsageScale | $powerUsage, $timeout,
- $drLevel << 4 | $randomStart | $randomEnd | $powerUsageLevel | 8;
- EnOcean_SndRadio(undef, $hash, 1, "A5", $data, AttrVal($name, "subDef", $hash->{DEF}), "00", "FFFFFFFF");
- if (defined $actionCmd) {
- # action exec
- $actionCmd = EvalSpecials($actionCmd, %specials);
- my $ret = AnalyzeCommandChain(undef, $actionCmd);
- Log3 $name, 2, "EnOcean $name demandRespAction ERROR: $ret" if($ret);
- }
- return;
- }
- # Send UTE Teach-In Telegrams
- sub EnOcean_sndUTE($$$$$$$) {
- my ($ctrl, $hash, $comMode, $responseRequest, $teachInReq, $devChannel, $eep) = @_;
- my $name = $hash->{NAME};
- my ($err, $data) = (undef, "");
- my $IODev = $hash->{IODev}{NAME};
- my $IOHash = $defs{$IODev};
- my @db = (undef, undef, undef, "07", "FF", $devChannel);
- if ($eep =~ m/^(..)-(..)-(..)$/) {
- ($db[0], $db[1], $db[2]) = ($1, $2, $3);
- } else {
- return (1, undef, undef);
- }
- # set unidir/bidir operation
- $db[6] = $comMode eq "biDir" ? 0x80 : 0;
- $attr{$name}{comMode} = $comMode;
- # set teach mode
- if ($teachInReq eq "out") {
- $db[6] |= 0x10;
- } elsif ($teachInReq eq "inout") {
- $db[6] |= 0x20;
- }
- # set response message mode
- if ($responseRequest eq "no") {
- $db[6] |= 0x40;
- readingsSingleUpdate($hash, "teach", "EEP $eep UTE query sent", 1);
- } else {
- # set flag for response request,
- if ($teachInReq eq "in") {
- $hash->{IODev}{helper}{UTERespWait}{$hash->{DEF}}{teachInReq} = $teachInReq;
- $hash->{IODev}{helper}{UTERespWait}{$hash->{DEF}}{hash} = $hash;
- } elsif ($teachInReq eq "out") {
- $hash->{IODev}{helper}{UTERespWait}{AttrVal($name, "subDef", $hash->{DEF})}{teachInReq} = $teachInReq;
- $hash->{IODev}{helper}{UTERespWait}{AttrVal($name, "subDef", $hash->{DEF})}{hash} = $hash;
- } elsif ($teachInReq eq "inout") {
- $hash->{IODev}{helper}{UTERespWait}{$hash->{DEF}}{teachInReq} = $teachInReq;
- $hash->{IODev}{helper}{UTERespWait}{$hash->{DEF}}{hash} = $hash;
- }
- readingsSingleUpdate($hash, "teach", "EEP $eep UTE query sent, response requested", 1);
- if (!exists($hash->{IODev}{Teach})) {
- # enable teach-in receiving for 3 sec
- $hash->{IODev}{Teach} = 1;
- my %timeoutHash = (hash => $IOHash, function => "UTERespTimeout", helper => "UTERespWait");
- RemoveInternalTimer(\%timeoutHash);
- InternalTimer(gettimeofday() + 3, "EnOcean_RespTimeout", \%timeoutHash, 0);
- }
- }
- $attr{$name}{devChannel} = $devChannel;
- $attr{$name}{eep} = $eep;
- $attr{$name}{manufID} = "7FF";
- $data = sprintf "%02X%02X%s%s%s%s%s", $db[6], $db[5], $db[4], $db[3], $db[2], $db[1], $db[0];
- return ($err, "D4", $data);
- }
- #
- sub EnOcean_RespTimeout($) {
- my ($functionHash) = @_;
- my $function = $functionHash->{function};
- my $hash = $functionHash->{hash};
- my $helper = $functionHash->{helper};
- my $name = $hash->{NAME};
- delete $hash->{helper}{$helper};
- delete $hash->{Teach};
- return;
- }
- #
- sub EnOcean_setTeachConfirmWaitHash($) {
- my ($ctrl, $hash) = @_;
- if (AttrVal($hash->{NAME}, "teachMethod", "") eq 'confirm') {
- $hash->{IODev}{helper}{teachConfirmWaitHash} = $hash;
- my %functionHash = (hash => $hash->{IODev}, function => "teachConfirmWaitHash");
- RemoveInternalTimer(\%functionHash);
- InternalTimer(gettimeofday() + 5, "EnOcean_helperClear", \%functionHash, 0);
- }
- return;
- }
- #
- sub EnOcean_helperClear($) {
- my ($functionHash) = @_;
- my $function = $functionHash->{function};
- my $hash = $functionHash->{hash};
- delete $hash->{helper}{$function};
- return;
- }
- #
- sub EnOcean_cdmClearRemoteWait($) {
- my ($functionHash) = @_;
- my $hash = $functionHash->{hash};
- my $param = $functionHash->{param};
- delete $hash->{IODev}{helper}{remoteAnswerWait}{$param}{hash};
- #Log3 $hash->{NAME}, 3, "EnOcean $hash->{NAME} EnOcean_cdmClearRemoteWait executed.";
- return;
- }
- #
- sub EnOcean_cdmClearHashVal($) {
- my ($functionHash) = @_;
- my $hash = $functionHash->{hash};
- my $param = $functionHash->{param};
- delete $hash->{$param};
- #Log3 $hash->{NAME}, 3, "EnOcean $hash->{NAME} EnOcean_cdmClearHashVal executed.";
- return;
- }
- #
- sub EnOcean_CommandDelete($) {
- my ($functionHash) = @_;
- my $deleteDevice = $functionHash->{deleteDevice};
- my $function = $functionHash->{function};
- my $hash = $functionHash->{hash};
- my $name = $hash->{NAME};
- my $oldDevice = $functionHash->{oldDevice};
- CommandDelete(undef, $deleteDevice);
- if (defined $oldDevice) {
- Log3 $name, 2, "EnOcean $name: $oldDevice renamed to $deleteDevice";
- CommandRename(undef, "$oldDevice $deleteDevice");
- CommandSave(undef, undef);
- } else {
- Log3 $name, 2, "EnOcean $name: $deleteDevice deleted";
- CommandSave(undef, undef);
- }
- return;
- }
- #
- sub EnOcean_convBitToHex($) {
- # convert bit string to hex string
- my ($bitStr) = @_;
- my $hexStr = '';
- while(length($bitStr) > 0) {
- $bitStr =~ m/^(.*)(.{8})$/;
- $bitStr = $1;
- $hexStr = unpack('H2', pack('B8', $2)) . $hexStr;
- }
- return uc($hexStr);
- }
- #
- sub EnOcean_convHexToBit($) {
- # convert unsign hex string to bit string
- my ($hexstr) = @_;
- my $bitstr = '';
- while (length($hexstr) > 0) {
- $hexstr =~ m/^(.*)(..)$/;
- $hexstr = $1;
- $bitstr = unpack('B8', pack('H2', $2)) . $bitstr;
- }
- return $bitstr;
- }
- #
- sub EnOcean_convIntToBit($$) {
- # convert unsign number to bitstring
- my ($data, $resolution) = @_;
- Log3 undef, 3, "EnOcean EnOcean_convIntToBitstr input: $data";
- if ($data > 0xFFFF) {
- # unsigned long (32 bit)
- Log3 undef, 3, "EnOcean EnOcean_convIntToBitstr pack L: " . pack('L', $data);
- #$data = unpack('B32', pack('L', $data));
- $data = unpack('B32', $data);
- } elsif ($data > 0xFF) {
- # unsigned short (16 bit)
- Log3 undef, 3, "EnOcean EnOcean_convIntToBitstr pack S: " . pack('S', $data);
- #$data = '0' x 16 . unpack('B16', pack('S', $data));
- $data = '0' x 16 . unpack('B16', $data);
- } else {
- # unsigned char (8 bit)
- Log3 undef, 3, "EnOcean EnOcean_convIntToBitstr pack C: " . pack('C', $data);
- $data = '0' x 24 . unpack('B8', pack('C', $data));
- }
- Log3 undef, 3, "EnOcean EnOcean_convIntToBitstr pack B32: " . $data;
- $data = substr($data, 32 - $resolution);
- return $data;
- }
- sub EnOcean_gpConvSelDataToSndData($$$$) {
- # Generic Profiles, make selective data in hex
- my ($header, $channel, $resolution, $data) = @_;
- my $resolutionPattern = '%04B%06B%0' . $resolution . 'B';
- $data = sprintf "$resolutionPattern", $header, $channel, $data;
- if (($resolution + 10) % 8) {
- # fill with trailing zeroes to x bytes
- $data = $data . '0' x (8 - (10 + $resolution) % 8);
- }
- #Log3 undef, 3, "EnOcean EnOcean_gpConvSelDataToSndData header: $header channel: $channel data: $data";
- $data = EnOcean_convBitToHex($data);
- #Log3 undef, 3, "EnOcean EnOcean_gpConvSelDataToSndData header: $header channel: $channel data: $data";
- return $data;
- }
- #
- sub EnOcean_gpConvDataToValue($$$$$) {
- # Generic Profiles, convert data to value
- my ($ctrl, $hash, $channel, $data, $dataDescr) = @_;
- my $name = $hash->{NAME};
- my ($err, $logLevel, $msg, $readingFormat, $readingType, $readingUnit, $readingValue, $valueType) =
- (undef, 5, 'ok', '%d', 'data', 'N/A', $data, 'value');
- my @channelTypeList = ("teachIn", "data", "flag", "enum");
- my @signalTypeList;
- my @valueTypeList = ("res", "value", "setpointAbs", "setpointRel");
- # extract channel definition
- my ($channelName, $channelDir, $channelType, $signalType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax);
- ($channelName, $channelDir, $channelType, $signalType, $valueType, $resolution, $engMin, $scalingMin, $engMax, $scalingMax) =
- split(':', $dataDescr);
- my $readingName = sprintf('%02d', $channel) . '-' . $channelName;
- $readingType = $channelTypeList[$channelType];
- $valueType = $valueTypeList[$valueType];
- if ($channelType == 3) {
- # enumeration
- if (defined $EnO_gpValueEnum{$signalType}{enum}{$data}) {
- $readingValue = $EnO_gpValueEnum{$signalType}{enum}{$data};
- $readingFormat = '%s';
- }
- } elsif ($channelType == 2) {
- # flag
- if (defined $EnO_gpValueFlag{$signalType}{flag}{$data}) {
- $readingValue = $EnO_gpValueFlag{$signalType}{flag}{$data};
- $readingFormat = '%s';
- }
- } elsif ($channelType == 1) {
- # data
- if (defined $EnO_gpValueData{$signalType}{unit}) {
- $readingUnit = $EnO_gpValueData{$signalType}{unit};
- }
- my @decimalDigits = (0, 1, 1, 2, 2, 2, 3, 4 , 4, 5, 7, 8, 10);
- $readingValue = $data / 2**$EnO_resolution[$resolution] *
- ($engMax * $EnO_scaling[$scalingMax] - $engMin * $EnO_scaling[$scalingMin]) + $engMin * $EnO_scaling[$scalingMin];
- if ($readingValue =~ m/^[+-]?\d+$/) {
- } elsif ($readingValue < 1000) {
- $readingFormat = '%0.' . ($decimalDigits[$resolution] - 1) . 'f';
- } else {
- $readingFormat = '%.' . ($decimalDigits[$resolution] - 1) . 'g';
- }
- } else {
- # teach-in info ... not used
- my @valueList = ("res", "channelsDescription", "productID");
- if (defined $valueList[$signalType]) {
- $readingValue = $valueList[$signalType];
- $readingFormat = '%s';
- }
- }
- return ($err, $logLevel, $msg, $readingFormat, $readingName, $readingType, $readingUnit, $readingValue, $valueType);
- }
- # Parse Secure Teach-In Telegrams
- sub EnOcean_sec_parseTeachIn($$$$) {
- my ($hash, $telegram, $subDef, $destinationID) = @_;
- my $name = $hash->{NAME};
- my ($err, $response, $logLevel);
- my $rlc; # Rolling code
- my $key1; # First part of private key
- my $key2; # Second part of private key
- # Extract byte fields from telegram
- # TEACH_IN_INFO, SLF, RLC/KEY/variable
- $telegram =~ /^(..)(..)(.*)/; # TODO Parse error handling?
- my $teach_bin = unpack('B8',pack('H2', $1)); # Parse as ASCII HEX, unpack to bitstring
- my $slf_bin = unpack('B8',pack('H2', $2)); # Parse as ASCII HEX, unpack to bitstring
- my $crypt = $3;
- # Extract bit fields from teach-in info field
- # IDX, CNT, PSK, TYPE, INFO
- $teach_bin =~ /(..)(..)(.)(.)(..)/; # TODO Parse error handling?
- my $idx = unpack('C',pack('B8', '000000'.$1)); # Padd to byte, parse as unsigned char
- my $cnt = unpack('C',pack('B8', '000000'.$2)); # Padd to byte, parse as unsigned char
- my $psk = $3;
- my $type = $4;
- my $info = unpack('C',pack('B8', '000000'.$5)); # Padd to byte, parse as unsigned char
- # Extract bit fields from SLF field
- # RLC_ALGO, RLC_TX, MAC_ALGO, DATA_ENC
- $slf_bin =~ /(..)(.)(..)(...)/; # TODO Parse error handling?
- my $rlc_algo = unpack('C',pack('B8', '000000'.$1)); # Padd to byte, parse as unsigned char
- my $rlc_tx = $2;
- my $mac_algo = unpack('C',pack('B8', '000000'.$3)); # Padd to byte, parse as unsigned char
- my $data_enc = unpack('C',pack('B8', '00000'.$4)); # Padd to byte, parse as unsigned char
- # The teach-in information is split in two telegrams due to the ERP1 limitations on telegram length
- # So we should get a telegram with index 0 and count 2 with the first half of the infos needed
- if ($idx == 0 && $cnt == 2) {
- # First part of the teach in message
- # Decode teach in type
- if ($type == 0) {
- # 1BS, 4BS, UTE or GP teach-in expected
- if ($info == 0) {
- $attr{$name}{comMode} = "uniDir";
- $attr{$name}{secMode} = "rcv";
- } else {
- $attr{$name}{comMode} = "biDir";
- $attr{$name}{secMode} = "biDir";
- }
- $hash->{helper}{teachInWait} = "STE";
- } else {
- # switch teach-in
- $attr{$name}{teachMethod} = 'STE';
- if ($info == 0) {
- $attr{$name}{comMode} = "uniDir";
- $attr{$name}{eep} = "D2-03-00";
- $attr{$name}{manufID} = "7FF";
- $attr{$name}{secMode} = "rcv";
- foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) {
- $attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr};
- }
- readingsSingleUpdate($hash, "teach", "STE teach-in accepted EEP D2-03-00 Manufacturer: " . $EnO_manuf{"7FF"}, 1);
- Log3 $name, 2, "EnOcean $name STE teach-in accepted EEP D2-03-00 Rocker A Manufacturer: " . $EnO_manuf{"7FF"};
- } else {
- $attr{$name}{comMode} = "uniDir";
- $attr{$name}{eep} = "D2-03-00";
- $attr{$name}{manufID} = "7FF";
- $attr{$name}{secMode} = "rcv";
- foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) {
- $attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr};
- }
- readingsSingleUpdate($hash, "teach", "STE teach-in accepted EEP D2-03-00 Manufacturer: " . $EnO_manuf{"7FF"}, 1);
- Log3 $name, 2, "EnOcean $name STE teach-in accepted EEP D2-03-00 Rocker B Manufacturer: " . $EnO_manuf{"7FF"};
- }
- }
- # Decode RLC algorithm and extract RLC and private key (only first part most likely)
- if ($rlc_algo == 0) {
- # No RLC used in telegram or internally in memory, use case untested
- return ("Secure modes without RLC not tested or supported", undef);
- } elsif ($rlc_algo == 1) {
- # "RLC= 2-byte long. RLC algorithm consists on incrementing in +1 the previous RLC value
- # Extract RLC and KEY fields from data trailing SLF field
- # RLC, KEY, ID, STATUS
- $crypt =~ /^(....)(.*)$/;
- $rlc = $1;
- $key1 = $2;
- #print "RLC: $rlc\n";
- #print "Part 1 of KEY: $key1\n";
- # Store in device hash
- $attr{$name}{rlcAlgo} = '2++';
- readingsSingleUpdate($hash, ".rlcRcv", $rlc, 0);
- # storing backup copy
- $attr{$name}{rlcRcv} = $rlc;
- $attr{$name}{keyRcv} = $key1;
- } elsif ($rlc_algo == 2) {
- # RLC= 3-byte long. RLC algorithm consists on incrementing in +1 the previous RLC value
- # Extract RLC and KEY fields from data trailing SLF field
- # RLC, KEY, ID, STATUS
- $crypt =~ /^(......)(.*)$/;
- $rlc = $1;
- $key1 = $2;
- #print "RLC: $rlc\n";
- #print "Part 1 of KEY: $key1\n";
- # Store in device hash
- $attr{$name}{rlcAlgo} = '3++';
- readingsSingleUpdate($hash, ".rlcRcv", $rlc, 0);
- # storing backup copy
- $attr{$name}{rlcRcv} = $rlc;
- $attr{$name}{keyRcv} = $key1;
- } else {
- # Undefined RLC algorithm
- return ("Undefined RLC algorithm $rlc_algo", undef);
- }
- # RLC Transmission
- if ($rlc_tx == 0 ) {
- # Secure operation mode telegrams do not contain RLC, we store and track it ourself
- $attr{$name}{rlcTX} = 'false';
- } else {
- # Secure operation mode messages contain RLC, CAUTION untested
- $attr{$name}{rlcTX} = 'true';
- }
- # Decode MAC Algorithm
- if ($mac_algo == 0) {
- # No MAC included in the secure telegram
- # Doesn't make sense for RLC senders like the PTM215, as we can't verify the RLC then...
- #$attr{$name}{macAlgo} = 'no';
- return ("Secure mode without MAC algorithm unsupported", undef);
- } elsif ($mac_algo == 1) {
- # CMAC is a 3-byte-long code
- $attr{$name}{macAlgo} = '3';
- } elsif ($mac_algo == 2) {
- # MAC is a 4-byte-long code
- $attr{$name}{macAlgo} = '4';
- } else {
- # Undefined MAC algorith;
- # Nothing we can do either...
- #$attr{$name}{macAlgo} = 'no';
- return ("Undefined MAC algorithm $mac_algo", undef);
- }
- # Decode data encryption algorithm
- if ($data_enc == 0) {
- # Data not encrypted? Right now we will handle this like an error, concrete use case untested
- #$attr{$name}{secLevel} = 'encapsulation';
- return ("Secure mode message without data encryption unsupported", undef);
- } elsif ($data_enc == 1) {
- # Unspecified
- return ("Undefined data encryption algorithm $data_enc", undef);
- } elsif ($data_enc == 2) {
- # Unspecified
- return ("Undefined data encryption algorithm $data_enc", undef);
- } elsif ($data_enc == 3) {
- # Data will be encrypted/decrypted XORing with a string obtained from a AES128 encryption
- $attr{$name}{dataEnc} = 'VAES';
- $attr{$name}{secLevel} = 'encryption';
- } elsif ($data_enc == 4) {
- # Data will be encrypted/decrypted using the AES128 algorithm in CBC mode
- # Might be used in the future right now untested
- #$attr{$name}{dataEnc} = 'AES-CBC';
- #$attr{$name}{secLevel} = 'encryption';
- return ("Secure mode message with AES-CBC data encryption unsupported", undef);
- } else {
- # Something went horribly wrong
- return ("Could not parse data encryption information, $data_enc", undef);
- }
- $hash->{helper}{teachInSTE} = $cnt - 1;
- # Ok we got a lots of infos and the first part of the private key
- return (undef, "part1: $name");
- } elsif ($idx == 1 && exists($hash->{helper}{teachInSTE})) {
- # Second part of the teach-in telegrams
- # Extract byte fields from telegram
- # Don't care about info fields, KEY, ID, don't care about status
- $telegram =~ /^..(.*)$/; # TODO Parse error handling?
- $key2 = $1;
- # We already should have gathered the infos from the first teach-in telegram
- if (!defined($attr{$name}{keyRcv})) {
- # We have missed the first telegram
- return ("Missing first teach-in telegram", undef);
- }
- # Append second part of private key to first part of private key
- $attr{$name}{keyRcv} .= $key2;
- if ($attr{$name}{secMode} eq "biDir") {
- # bidirectional secure teach-in
- if (!defined $subDef) {
- $subDef = EnOcean_CheckSenderID("getNextID", $defs{$name}{IODev}{NAME}, "00000000");
- $attr{$name}{subDef} = $subDef;
- }
- ($err, $response, $logLevel) = EnOcean_sec_createTeachIn(undef, $hash, $attr{$name}{comMode},
- $attr{$name}{dataEnc}, $attr{$name}{eep},
- $attr{$name}{macAlgo}, $attr{$name}{rlcAlgo},
- $attr{$name}{rlcTX}, $attr{$name}{secLevel},
- $subDef, $destinationID);
- if ($err) {
- Log3 $name, $logLevel, "EnOcean $name Error: $err";
- return $err;
- } else {
- Log3 $name, $logLevel, "EnOcean $name $response";
- }
- }
- delete $hash->{helper}{teachInSTE};
- return (undef, "part2: $name");
- }
- # Sequence error?
- return ("teach-in sequence error IDC: $idx CNT: $cnt", undef);
- }
- # Do VAES decyrption
- # All parameters need to be passed as byte strings
- #
- # Parameter 1: Current rolling code, 16 bytes
- # Parameter 2: Private key, 16bytes
- # Paremeter 3: Encrypted data, 16bytes
- #
- # Returns: Decrypted data, 16 bytes
- #
- # Decryption of more than 16bytes of data is currently unsupported
- #
- sub EnOcean_sec_decodeVAES($$$) {
- my $rlc = $_[0];
- my $private_key = $_[1];
- my $data_enc = $_[2];
- # Public key according to EnOcean Security specification
- my $public_key = pack('H32', '3410de8f1aba3eff9f5a117172eacabd');
- # Input for VAES
- my $aes_in = $public_key ^ $rlc;
- #print "--\n";
- #print "Public Key ".unpack('H32', $public_key)."\n";
- #print "RLC ".unpack('H32', $rlc)."\n";
- #print "AES input ".unpack('H32', $aes_in)."\n";
- #print "--\n";
- #print "Private Key ".unpack('H32', $private_key)."\n";
- my $cipher = Crypt::Rijndael->new( $private_key );
- my $aes_out = $cipher->encrypt($aes_in);
- #print "AES output ".unpack('H32', $aes_out)."\n";
- #print "Data_enc: ".unpack('H32', $data_enc)."\n";
- my $data_dec = $data_enc ^ $aes_out;
- #print "Data_dec: ".unpack('H32', $data_dec)."\n";
- return $data_dec;
- }
- # Returns current RLC in hex format and increments the stored RLC
- # Checks the boundaries of the RLC for roll-over
- #
- # Parameter 1: Sender ID in hexadecimal format for lookup in receivers hash
- #
- # Affects: receivers hash
- #
- # Returns: RLC in hexadecimal format
- #
- sub EnOcean_sec_getRLC($$) {
- my $hash = $_[0];
- my $rlcVar = $_[1];
- my $name = $hash->{NAME};
- # Fetch newest RLC from receiver hash
- my $old_rlc = ReadingsVal($name, "." . $rlcVar, $attr{$name}{$rlcVar});
- if (hex($old_rlc) < hex($attr{$name}{$rlcVar})) {
- $old_rlc = $attr{$name}{$rlcVar};
- }
- #print "RLC old: $old_rlc\n";
- Log3 $name, 5, "EnOcean $name EnOcean_sec_getRLC RLC old: $old_rlc";
- # Advance RLC by one
- my $new_rlc = hex($old_rlc) + 1;
- # Boundary check
- if ($attr{$name}{rlcAlgo} eq '2++') {
- if ($new_rlc > 65535) {
- #print "RLC rollover\n";
- Log3 $name, 5, "EnOcean $name EnOcean_sec_getRLC RLC rollover";
- $new_rlc = 0;
- $attr{$name}{$rlcVar} = "0000";
- EnOcean_CommandSave(undef, undef);
- }
- readingsSingleUpdate($hash, "." . $rlcVar, uc(unpack('H4',pack('n', $new_rlc))), 0);
- $attr{$name}{$rlcVar} = uc(unpack('H4',pack('n', $new_rlc)));
- } elsif ($attr{$name}{rlcAlgo} eq '3++') {
- if ($new_rlc > 16777215) {
- #print "RLC rollover\n";
- Log3 $name, 5, "EnOcean $name EnOcean_sec_getRLC RLC rollover";
- $new_rlc = 0;
- $attr{$name}{$rlcVar} = "000000";
- EnOcean_CommandSave(undef, undef);
- }
- readingsSingleUpdate($hash, "." . $rlcVar, uc(unpack('H6',pack('N', $new_rlc))), 0);
- $attr{$name}{$rlcVar} = uc(unpack('H6',pack('N', $new_rlc)));
- }
- #print "RLC new: ".$attr{$name}{rlc}."\n";
- Log3 $name, 5, "EnOcean $name EnOcean_sec_getRLC RLC new: ". $attr{$name}{$rlcVar};
- return $old_rlc;
- }
- # Generate MAC of data
- #
- # Parameter 1: private key as byte string, 16bytes
- # Parameter 2: data fro which mac should be calculated in hexadecimal format, len variable
- # Parameter 3: length of MAC to be generated in bytes
- #
- # Returns: MAC in hexadecimal format
- #
- # This function currently supports data with lentgh of less then 16bytes,
- # MAC for longer data is untested but specified
- #
- sub EnOcean_sec_generateMAC($$$) {
- my $private_key = $_[0];
- my $data = $_[1];
- my $cmac_len = $_[2];
- #print "Calculating MAC for data $data\n";
- Log3 undef, 5, "EnOcean_sec_generateMAC Calculating MAC for data $data";
- Log3 undef, 5, "EnOcean_sec_generateMAC private key ".unpack('H32', $private_key);
- # Pack data to 16byte byte string, padd with 10..0 binary
- my $data_expanded = pack('H32', $data.'80');
- #print "Exp. data ".unpack('H32', $data_expanded)."\n";
- # Constants according to specification
- my $const_zero = pack('H32','00');
- my $const_rb = pack('H32', '00000000000000000000000000000087');
- # Encrypt zero data with private key to get L
- my $cipher = Crypt::Rijndael->new($private_key);
- my $l = $cipher->encrypt($const_zero);
- #print "L ".unpack('H32', $l)."\n";
- #print "L ".unpack('B128', $l)."\n";
- # Expand L to 128bit string
- my $l_bit = unpack('B128', $l);
- # K1 and K2 stored as 128bit string
- my $k1_bit;
- my $k2_bit;
- # K1 and K2 as binary
- my $k1;
- my $k2;
- # Store L << 1 in K1
- $l_bit =~ /^.(.{127})/;
- $k1_bit = $1.'0';
- $k1 = pack('B128', $k1_bit);
- # If MSB of L == 1, K1 = K1 XOR const_Rb
- if($l_bit =~ m/^1/) {
- #print "MSB of L is set\n";
- $k1 = $k1 ^ $const_rb;
- $k1_bit = unpack('B128', $k1);
- } else {
- #print "MSB of L is unset\n";
- }
- # Store K1 << 1 in K2
- $k1_bit =~ /^.(.{127})/;
- $k2_bit = $1.'0';
- $k2 = pack('B128', $k2_bit);
- # If MSB of K1 == 1, K2 = K2 XOR const_Rb
- if($k1_bit =~ m/^1/) {
- #print "MSB of K1 is set\n";
- $k2 = $k2 ^ $const_rb;
- } else {
- #print "MSB of K1 is unset\n";
- }
- # XOR data with K2
- $data_expanded ^= $k2;
- # Encrypt data
- my $cmac = $cipher->encrypt($data_expanded);
- #print "CMAC ".unpack('H32', $cmac)."\n";
- Log3 undef, 5, "EnOcean_sec_generateMAC CMAC ".unpack('H32', $cmac);
- # Extract specified len of MAC
- my $cmac_pattern = '^(.{'.($cmac_len * 2).'})';
- unpack('H32', $cmac) =~ /$cmac_pattern/;
- Log3 undef, 5, "EnOcean_sec_generateMAC cutted CMAC ".unpack('H32', $1);
- # Return MAC in hexadecimal format
- return uc($1);
- }
- # Verify (MAC) and decode/decrypt secure mode message
- #
- # Parameter 1: content of radio telegram in hexadecimal format
- #
- # Returns: "ERROR-" + error description, "OK-" + EEP D2-03-00 telegram in hexadecimal format
- #
- # Right now we only decode PTM215 telegrams which are transmitted as RORG 30 and without
- # encapsulation. Encapsulation of other telegrams is possible and specified but untested due to the
- # lack of hardware suporting this.
- #
- sub EnOcean_sec_convertToNonsecure($$$) {
- my ($hash, $rorg, $crypt_data) = @_;
- my $name = $hash->{NAME};
- if ($cryptFunc == 0) {
- return ("Cryptographic functions are not available", undef, undef);
- }
- my $private_key;
- # Prefix of pattern to extract the different cryptographic infos
- my $crypt_pattern = "^(.*)";;
- # Flags and infos for fields to expect
- my $expect_rlc = 0;
- my $expect_mac = 0;
- my $mac_len;
- my $expect_enc = 0;
- # Check if RLC is transmitted and when, which length to expect
- if($attr{$name}{rlcTX} eq 'true') {
- # Message should contain RLC
- if ($attr{$name}{rlcAlgo} eq '2++') {
- $crypt_pattern .= "(....)";
- $expect_rlc = 1;
- } elsif ($attr{$name}{rlcAlgo} eq '3++') {
- $crypt_pattern .= "(......)";
- $expect_rlc = 1;
- } else {
- # RLC_TX but no info on RLC length
- return ("RLC_TX and RLC_ALGO inconsistent", undef, undef);
- }
- }
- # Check what length of MAC to expect
- if($attr{$name}{macAlgo} eq '3') {
- $crypt_pattern .= "(......)";
- $mac_len = 3;
- $expect_mac = 1;
- } elsif ($attr{$name}{macAlgo} eq '4') {
- $crypt_pattern .= "(........)";
- $mac_len = 4;
- $expect_mac = 1;
- } else {
- # According to the specification it's possible to transmit no MAC, bt we don't implement this for now
- return ("Secure mode messages without MAC unsupported", undef, undef);
- }
- # Suffix for crypt pattern
- $crypt_pattern .= '$';
- # Extract byte fields from message payload
- $crypt_data =~ /$crypt_pattern/;
- my $data_enc = $1;
- my $dataLength = length($data_enc);
- return ("Telegrams with a length of more than 16 bytes are not supported", undef, undef) if ($dataLength > 32);
- my $rlc;
- my $mac;
- if ($expect_rlc == 1 && $expect_mac == 1) {
- $rlc = $2;
- $mac = $3;
- } elsif ($expect_rlc == 0 && $expect_mac == 1) {
- $mac = $2;
- }
- Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RORG: $rorg DATA_ENC: $data_enc";
- if ($expect_rlc == 1) {
- Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RLC: $rlc";
- };
- Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure MAC: $mac";
- # TODO RLC could be transmitted with data, could not test this
- #if(!defined($rlc)) {
- # print "No RLC in message, using stored value\n";
- # $rlc = getRLC($senderID);
- #}
- # Maximum RLC search window is 128
- foreach my $rlc_window (0..128) {
- #print "Trying RLC offset $rlc_window\n";
- # Fetch stored RLC
- $rlc = EnOcean_sec_getRLC($hash, "rlcRcv");
- # Fetch private Key for VAES
- if ($attr{$name}{keyRcv} =~ /[\dA-F]{32}/) {
- $private_key = pack('H32',$attr{$name}{keyRcv});
- } else {
- return ("private key wrong, please teach-in the device new", undef, undef);
- }
- # Generate and check MAC over RORG+DATA+RLC fields
- if ($mac eq EnOcean_sec_generateMAC($private_key, $rorg.$data_enc.$rlc, $mac_len)) {
- #print "RLC verfified\n";
- # Expand RLC to 16byte
- my $rlc_expanded = pack('H32',$rlc);
- # Expand data to 16byte
- my $data_expanded = pack('H32',$data_enc);
- # Decode data using VAES
- my $data_dec = EnOcean_sec_decodeVAES($rlc_expanded, $private_key, $data_expanded);
- my $data_end = unpack('H32', $data_dec);
- if ($rorg eq '30') {
- # Extract one nibble of data
- $data_end =~ /^.(.)/;
- return (undef, '32', "0" . uc($1));
- } else {
- $dataLength -= 2;
- $data_end =~ /^(..)(.{$dataLength})/;
- Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RORG: " . uc($1) . " DATA: " . uc($2);
- return (undef, uc($1), uc($2));
- }
- }
- }
- # Couldn't verify or decrypt message in RLC window
- return ("Can't verify or decrypt telegram", undef, undef);
- }
- #
- sub EnOcean_sec_createTeachIn($$$$$$$$$$$)
- {
- my ($ctrl, $hash, $comMode, $dataEnc, $eep, $macAlgo, $rlcAlgo, $rlcTX, $secLevel, $subDef, $destinationID) = @_;
- my $name = $hash->{NAME};
- my ($data, $err, $response, $loglevel);
- # THIS IS A BASIC IMPLEMENTATION WITH HARDCODED VALUES FOR
- # THE SECURITY PARAMETERS, WILL BE CUSTOMIZABLE IN FUTURE
- if ($cryptFunc == 0) {
- return ("Cryptographic functions are not available", undef, 2);
- }
- # generate random private key
- my $pKey;
- for (my $i = 1; $i < 5; $i++) {
- $pKey .= uc(unpack('H8', pack('L', makerandom(Size => 32, Strength => 1))));
- }
- $attr{$name}{keySnd} = AttrVal($name, "keySnd", $pKey);
- #generate random rlc, save to fhem.cfg and update readings
- my $rlc = ReadingsVal($name, ".rlcSnd", uc(unpack('H4', pack('n', makerandom(Size => 16, Strength => 1)))));
- readingsSingleUpdate($hash, ".rlcSnd", $rlc, 0);
- $attr{$name}{rlcSnd} = $rlc;
- $attr{$name}{comMode} = AttrVal($name, "comMode", $comMode);
- $attr{$name}{dataEnc} = AttrVal($name, "dataEnc", $dataEnc);
- $attr{$name}{eep} = $eep;
- $attr{$name}{macAlgo} = AttrVal($name, "macAlgo", $macAlgo);
- $attr{$name}{manufID} = "7FF";
- $attr{$name}{rlcAlgo} = AttrVal($name, "rlcAlgo", $rlcAlgo);
- $attr{$name}{rlcTX} = AttrVal($name, "rlcTX", $rlcTX);
- $attr{$name}{secLevel} = AttrVal($name, "secLevel", $secLevel);
- if (AttrVal($name, "secMode", "") =~ m/^rcv|bidir$/) {
- $attr{$name}{secMode} = "biDir";
- } else {
- $attr{$name}{secMode} = "snd";
- }
- # prepare 1st telegram
- #RORG = 35, TEACH_IN_INFO_0, SLF, RLC, KEY, ID, STATUS as defined in Security_of_EnOcean_Radio_Networks.pdf page 17
- #set TEACH_IN_INFO = 25 -> 0001.0101 -> IDX =0, CNT = 2, PSK = 0, TYPE = 1, INFO = 1
- #set SLF = 4B -> 0100.1011 -> RLC_ALGO=16bit, RLC-TX=0, MAC-ALGO = AES3BYTE, DATA_ENC = VAES128
- #save the fixed security parameters to fhem.cfg
- #get first 5 bytes of private key
- #data 1: 25 4B r1 r2 k1 k2 k3 k4 k5
- $data = "254B" . $rlc . substr($attr{$name}{keySnd}, 0, 5*2);
- EnOcean_SndRadio(undef, $hash, 1, "35", $data, $subDef, "00", $destinationID);
- # prepare 2nd telegram
- #RORG = 35, TEACH_IN_INFO_1, KEY, ID, STATUS as defined in Security_of_EnOcean_Radio_Networks.pdf page 17
- #set TEACH_IN_INFO = 40 -> 0100.0000 -> IDX =1, CNT = 0, PSK = 0, TYPE = 0, INFO = 0
- #get 2nd 11 bytes of private key
- #data 2: 40 k6 k7 k8 k9 k10 k11 k12 k13 k14 k15 k16
- $data = "40" . substr($attr{$name}{keySnd}, 10, 11*2);
- EnOcean_SndRadio(undef, $hash, 1, "35", $data, $subDef, "00", $destinationID);
- return (undef, "secure teach-in", 2);
- }
- #
- sub EnOcean_sec_convertToSecure($$$$)
- {
- my ($hash, $packetType, $rorg, $data) = @_;
- my ($err, $response, $loglevel);
- my $name = $hash->{NAME};
- my $secLevel = AttrVal($name, "secLevel", "off");
- # encryption needed?
- return ($err, $rorg, $data, $response, 5) if ($rorg =~ m/^F6|35$/ || $secLevel !~ m/^encapsulation|encryption$/);
- return ("Cryptographic functions are not available", undef, undef, $response, 2) if ($cryptFunc == 0);
- my $dataEnc = AttrVal($name, "dataEnc", undef);
- my $subType = AttrVal($name, "subType", "");
- # subType specific actions
- if ($subType eq "switch.00" || $subType eq "windowHandle.10") {
- # securemode for D2-03-00 and D2-03-10
- if (hex($data) > 15) {
- return("wrong data byte", $rorg, $data, $response, 2);
- }
- # set rorg to secure telegram
- $rorg = "30";
- } else {
- return("Cryptographic functions for $subType not available", $rorg, $data, $response, 2);
- }
- #Get and update RLC
- my $rlc = EnOcean_sec_getRLC($hash, "rlcSnd");
- #Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: Got actual RLC: $rlc";
- #Get key of device
- my $pKey = AttrVal($name, "keySnd", undef);
- return("private key not defined", $rorg, $data, $response, 2) if (!defined $pKey);
- $pKey = pack('H32', $pKey);
- #Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: key: " . AttrVal($name, "keySnd", "");
- #prepare data
- my $rlc_expanded = pack('H32', $rlc);
- my $data_expanded = pack('H32', $data);
- my $data_dec;
- if ($dataEnc eq "VAES") {
- $data_dec = EnOcean_sec_decodeVAES($rlc_expanded, $pKey, $data_expanded);
- } else {
- return("Cryptographic functions not available", $rorg, $data, $response, 2);
- }
- my $data_end = unpack('H32', $data_dec);
- #get the correct nibble
- $data_end =~ /^.(.)/;
- $data_end = uc("0$1");
- #Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: Crypted Data: $data_end";
- # calc MAC
- my $macAlgo = AttrVal($name, "macAlgo", undef);
- return("MAC Algorithm not defined", $rorg, $data, $response, 2) if (!defined $macAlgo);
- my $mac = EnOcean_sec_generateMAC($pKey, $rorg . $data_end . $rlc, $macAlgo);
- # combine message
- $data = $data_end . uc($mac);
- #Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: Crypted Payload: $data";
- return(undef, $rorg, $data, $response, 5);
- }
- #
- sub
- EnOcean_NumericSort
- {
- if ($a < $b) {
- return -1;
- } elsif ($a == $b) {
- return 0;
- } else {
- return 1;
- }
- }
- sub EnOcean_TimeDiff($)
- {
- my ($strTS) = @_;
- if (defined $strTS) {
- my $timeDiff = gettimeofday() - ($strTS eq "" ? gettimeofday() : time_str2num($strTS));
- $timeDiff = 0 if ($timeDiff < 0);
- return $timeDiff;
- } else {
- return 0;
- }
- }
- # Undef
- sub
- EnOcean_Undef($$)
- {
- my ($hash, $name) = @_;
- delete $hash->{helper};
- delete $modules{EnOcean}{defptr}{uc($hash->{DEF})};
- delete $modules{EnOcean}{defptr}{uc($attr{$name}{remoteID})} if (exists $attr{$name}{remoteID});
- if (AttrVal($name, "remoteManagement", "off") eq "client") {
- delete $hash->{RemoteClientUnlock};
- my %functionHash = (hash => $hash, param => 'RemoteClientUnlock');
- RemoveInternalTimer(\%functionHash);
- }
- return undef;
- }
- # Delete
- sub
- EnOcean_Delete($$)
- {
- my ($hash, $name) = @_;
- my $logName = "FileLog_$name";
- my ($count, $gplotFile, $logFile, $weblinkName, $weblinkHash);
- Log3 $name, 2, "EnOcean $name deleted";
- # delete FileLog device and log files
- if (exists $defs{$logName}) {
- $logFile = $defs{$logName}{logfile};
- $logFile =~ /^(.*)($name).*\.(.*)$/;
- $logFile = $1 . $2 . "*." . $3;
- CommandDelete(undef, "FileLog_$name");
- Log3 $name, 2, "EnOcean FileLog_$name deleted";
- $count = unlink glob $logFile;
- Log3 $name, 2, "EnOcean $logFile >> $count files deleted";
- }
- # delete SVG devices and gplot files
- while (($weblinkName, $weblinkHash) = each(%defs)) {
- if ($weblinkName =~ /^SVG_$name.*/) {
- $gplotFile = "./www/gplot/" . $defs{$weblinkName}{GPLOTFILE} . "*.gplot";
- CommandDelete(undef, $weblinkName);
- Log3 $name, 2, "EnOcean $weblinkName deleted";
- $count = unlink glob $gplotFile;
- Log3 $name, 2, "EnOcean $gplotFile >> $count files deleted";
- }
- }
- return undef;
- }
- 1;
- =pod
- =item device
- =item summary EnOcean Gateway and Actor
- =item summary_DE EnOcean Gateway und Aktor
- =begin html
- <a name="EnOcean"></a>
- <h3>EnOcean</h3>
- <ul><br>
- <b>Quick Links</b>
- <ul>
- <li><a href="#EnOceanget">Get Commands</a></li>
- <li><a href="#EnOceanset">Set Commands</a></li>
- <li><a href="#EnOceanattr">Attributes</a></li>
- <li><a href="#EnOceanevents">Generated Events</a></li>
- </ul><br><br>
- EnOcean devices are sold by numerous hardware vendors (e.g. Eltako, Peha, etc),
- using the RF Protocol provided by the EnOcean Alliance.<br><br>
- Depending on the function of the device an specific device profile is used, called
- EnOcean Equipment Profile (EEP). The specific definition of a device is referenced by
- the EEP (RORG-FUNC-TYPE). Basically four groups (RORG) will be differed, e. g.
- RPS (switches), 1BS (contacts), 4BS, VLD (sensors and controller). Some manufacturers use
- additional proprietary extensions. RORG MSC is not supported except for few exceptions.
- Further technical information can be found at the
- <a href="http://www.enocean-alliance.org/de/enocean_standard/">EnOcean Alliance</a>,
- see in particular the
- <a href="http://www.enocean-alliance.org/eep/">EnOcean Equipment Profiles (EEP)</a>
- <br><br>
- The supplementary Generic Profiles approach instead defines a language to communicate the
- transmitted data types and ranges. The devices becomes self describing on their data
- structures in communication. The Generic Profiles include a language definition with
- a parameter selection that covers every possible measured value to be transmitted.
- Therefore, the approach does not only define parameters for the value recalculation algorithm
- but also includes specific signal definition. (e.g. physical units). Further technical
- information can be found at the
- <a href="https://www.enocean-alliance.org/fileadmin/redaktion/enocean_alliance/pdf/GenericProfiles_V1_Extract.pdf">Generic Profiles 1.0 Abstract</a>
- <br><br>
- Smart Acknowledge (Smart Ack) enables a special bidirectional communication. The communication is managed by a
- Controller that responds to the devices telegrams with acknowledges. Smart Ack is a bidirectional communication
- protocol between two actors. At least one actor must be an energy autarkic Sensor, and at least one must be a line
- powered Controller (Fhem). A sensor sends its data and expects the answer telegram in a predefined very short
- time slot. In this time Sensors receiver is active. For this purpose we declare a Post Master with Mail Boxes.
- A Mail Box is like a letter box for a Sensor and it specific to a single sender. Telegrams from Fhem are collected
- into the Mail Box. A Sensor can reclaim telegrams that are in his Mail Box.
- <br><br>
- Fhem recognizes a number of devices automatically. In order to teach-in, for
- some devices the sending of confirmation telegrams has to be turned on.
- Some equipment types and/or device models must be manually specified.
- Do so using the <a href="#EnOceanattr">attributes</a>
- <a href="#subType">subType</a> and <a href="#model">model</a>, see chapter
- <a href="#EnOceanset">Set</a> and
- <a href="#EnOceanevents">Generated events</a>. With the help of additional
- <a href="#EnOceanattr">attributes</a>, the behavior of the devices can be
- changed separately.
- <br><br>
- Fhem and the EnOcean devices must be trained with each other. To this, Fhem
- must be in the learning mode, see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>,
- <a href="#EnOcean_smartAck">Smart Ack Learning</a> and <a href="#TCM_learningMode">learningMode</a>.
- The teach-in procedure depends on the type of the devices.
- <br><br>
- Switches (EEP RPS) and contacts (EEP 1BS) are recognized when receiving the first message.
- Contacts can also send a teach-in telegram. Fhem not need this telegram.
- Sensors (EEP 4BS) has to send a teach-in telegram. The profile-less
- 4BS teach-in procedure transfers no EEP profile identifier and no manufacturer
- ID. In this case Fhem does not recognize the device automatically. The proper
- device type must be set manually, use the <a href="#EnOceanattr">attributes</a>
- <a href="#subType">subType</a>, <a href="#manufID">manufID</a> and/or
- <a href="#model">model</a>. If the EEP profile identifier and the manufacturer
- ID are sent the device is clearly identifiable. Fhem automatically assigns
- these devices to the correct profile.
- <br><br>
- 4BS devices can also be taught in special cases by using of confirmation telegrams. This method
- is used for the EnOcean Tipp-Funk devices. The function is activated via the attribute [<a href="#EnOcean_teachMethod">teachMethod</a>] = confirm.<br>
- For example the remote device Eltako TF100D can be learned as follows
- <ul><br>
- <code>define <name> EnOcean H5-38-08</code><br>
- set TF100D in learning mode<br>
- <code>set <name> teach</code>
- </ul>
- <br>
- Some 4BS, VLD or MSC devices must be paired bidirectional,
- see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.
- <br><br>
- Devices that communicate encrypted, has to taught-in through specific procedures.
- <br><br>
- Smart Ack Learning is a futher process where devices exchange information about each
- other in order to create the logical links in the EnOcean network and a Post Master Mail Box.
- It can result in Learn In or Learn Out, see <a href="#EnOcean_smartAck">Smart Ack Learning</a>.
- <br><br>
- Fhem supports many of most common EnOcean profiles and manufacturer-specific
- devices. Additional profiles and devices can be added if required.
- <br><br>
- In order to enable communication with EnOcean remote stations a
- <a href="#TCM">TCM</a> module is necessary.
- <br><br>
- Please note that EnOcean repeaters also send Fhem data telegrams again.
- Use the TCM <code>attr <name> <a href="#blockSenderID">blockSenderID</a> own</code>
- to block receiving telegrams with a TCM SenderIDs.
- <br><br>
- <b>Observing Functions</b><br>
- <ul>
- Interference or overloading of the radio transmission can prevent the reception of Fhem
- commands at the receiver. With the help of the observing function Fhem checks the reception
- of the acknowledgment telegrams of the actuator. If within one second no acknowledgment
- telegram is received, the last set command is sent again.
- The set command is repeated a maximum of 5 times. The maximum number can be specified in the attribute
- <a href="#EnOcean_observeCmdRepetition">observeCmdRepetition</a>.<br>
- The function can only be used if the actuator immediately after the reception of
- the set command sends an acknowledgment message.<br>
- The observing function is turned on by the Attribute <a href="#EnOcean_observe">observe.</a>
- In addition, further devices can be monitored. The names of this devices can be entered in the
- <a href="#EnOcean_observeRefDev">observeRefDev</a> attribute. If additional device are specified,
- the monitoring is stopped as soon as the first acknowledgment telegram of one of the devices was received (OR logic).
- If the <a href="#EnOcean_observeLogic">observeLogic</a> attribute is set to "and", the monitoring is stopped when a telegram
- was received by all devices (AND logic). Please note that the name of the own device has also to be entered in the
- <a href="#EnOcean_observeRefDev">observeRefDev</a> if required.<br>
- If the maximum number of retries is reached and still no all acknowledgment telegrams has been received, the reading
- "observeFailedDev" shows the faulty devices and the command can be executed, that is stored in the
- <a href="#EnOcean_observeErrorAction">observeErrorAction</a> attribute.
- <br><br>
- </ul>
- <b>Energy Management</b><br>
- <ul>
- <li><a href="#demand_response">Demand Response</a> (EEP A5-37-01)</li>
- Demand Response (DR) is a standard to allow utility companies to send requests for reduction in power
- consumption during peak usage times. It is also used as a means to allow users to reduce overall power
- comsumption as energy prices increase. The EEP was designed with a very flexible setting for the level
- (0...15) as well as a default level whereby the transmitter can specify a specific level for all
- controllers to use (0...100 % of either maximum or current power output, depending on the load type).
- The profile also includes a timeout setting to indicate how long the DR event should last if the
- DR transmitting device does not send heartbeats or subsequent new DR levels.<br>
- The DR actor controls the target actuators such as switches, dimmers etc. The DR actor
- is linked to the FHEM target actors via the attribute <a href="#EnOcean_demandRespRefDev">demandRespRefDev</a>.<br>
- <ul>
- <li>Standard actions are available for the following profiles:</li>
- <ul>
- <li>switch (setting the switching command for min, max by the attribute <a href="#EnOcean_demandRespMin">demandRespMin</a>,
- <a href="#EnOcean_demandRespMax">demandRespMax</a>)</li>
- <li>gateway/switching (on, off)</li>
- <li>gateway/dimming (dim 0...100, relative to the max or current set value)</li>
- <li>lightCtrl.01 (dim 0...255)</li>
- <li>actuator.01 (dim 0...100)</li>
- <li>roomSensorControl.01 (setpoint 0...255)</li>
- <li>roomSensorControl.05 (setpoint 0...255 or nightReduction 0...5 for Eltako devices)</li>
- <li>roomCtrlPanel.00 (roomCtrlMode comfort|economy)</li>
- </ul>
- <li>On the target actuator can be specified alternatively a freely definable command.
- The command sequence is stored in the attribute <a href="#EnOcean_demandRespAction">demandRespAction</a>.
- The command sequence can be designed similar to "notify". For the command sequence predefined variables can be used,
- eg. $LEVEL. This actions can be executed very flexible depending on the given energy
- reduction levels.
- </li>
- <li>Alternatively or additionally, a custom command sequence in the DR profile itself
- can be stored.
- </li>
- </ul>
- The profile has a master and slave mode.
- <ul>
- <li>In slave mode, demand response data telegrams received eg a control unit of the power utility,
- evaluated and the corresponding commands triggered on the linked target actuators. The behavior in
- slave mode can be changed by multiple attributes.
- </li>
- <li>In master mode, the demand response level is set by set commands and thus sends a corresponding
- data telegram and the associated target actuators are controlled. The demand response control
- value are specified by "level", "power", "setpoint" "max" or "min". Each other settings are
- calculated proportionally. In normal operation, ie without power reduction, the control value (level)
- is 15. Through the optional parameters "powerUsageScale", "randomStart", "randomEnd" and "timeout"
- the control behavior can be customized. The threshold at which the reading "powerUsageLevel"
- between "min" and "max" switch is specified with the attribute
- <a href="#EnOcean_demandRespThreshold">demandRespThreshold</a>.
- </li>
- </ul>
- Additional information about the profile itself can be found in the EnOcean EEP documentation.
- <br><br>
- </ul>
- <b>Remote Management</b><br>
- <ul>
- Remote Management allows EnOcean devices to be configured and maintained over the air.
- Thanks to Remote Management, sensors or switches IDs, for instance, can be stored or deleted from
- already installed actuators or gateways which are hard to access. Remote Management also allows querying
- debug information from the Remote Device and calling some manufacturer implemented functions.<br>
- Remote Management is performed by the Remote Manager, operated by the actor, on the
- managed Remote Device (Sensor, Gateway). The management is done through a series of
- commands and responding answers. Actor sends the commands to the Remote Device. Remote
- Device sends answers to the actor. The commands indicate the Remote Device what to do.
- Remote Device answers if requested by the command. The commands belong to one of the
- main use case categories, which are:
- <ul>
- <li>Security</li>
- <li>Locate / indentify remote device</li>
- <li>Get status</li>
- <li>Extended function</li>
- </ul>
- The management is often done with a group of Remote Devices. Commands are sent as
- addressed unicast telegrams, usually. In special cases broadcast transmission is also available.
- To avoid telegram collisions the Remote Devices respond to broadcast commands with a
- random delay.<br>
- The Security, Locate, and Get Status options provide to the actor basic operability of Remote
- management. Their purpose is to ensure the proper work of Remote Management when
- operating with several Remote Devices. These functions behave in the same way on every
- Remote Device. Every product that supports Remote Management provides these options.<br>
- Extended functions provide the real benefit of Remote Management. They vary from Remote
- Device to Remote Device. They depend on how and where the Remote Device is used.
- Therefore, not every Remote Device provides every extended function. It depends on the
- programmer / customer what extended functions he wants to add. There is a list of specified
- commands, but the manufacturer can also add manufacturer specific extended functions. These
- functions are identified by the manufacturer ID.<br>
- More information can be found on the <a href="http://www.enocean.com">EnOcean websites</a>.<br><ber>
- Fhem operates primarily as a remote manager. For tests but also a client device can be created.
- <br><br>
- The remote manager function must be activated for the desired device by
- <ul><br>
- <code>attr <remote device name> remote manager</code><br>
- </ul>
- <br><br>
- The remote client device must be defined as follows<br>
- <ul><br>
- <code>define <client name> EnOcean C5-00-00</code><br>
- </ul><br>
- and has to by unlocked for t seconds
- <ul><br>
- <code>set <client name> unlock <t/s></code><br>
- </ul><br>
- Only one remote management client device should be defined.<br><br>
- For security reasons the remote management commands can only be accessed in the unlock
- period. The period can be entered in two cases:
- <ul>
- <li>Within 30min after device power-up if no CODE is set</li>
- <li>Within 30min after an unlock command with a correct 32bit security code is received</li>
- </ul>
- The unlock/lock period can be accessed only with the security code. The security code can be
- set whenever the Remote Device accepts remote management commands.<br>
- When the Remote Device is locked it does not respond to any command, but unlock and ping.
- When a wrong security code is received the Remote Device does not process unlock commands
- for a security period of 30 seconds.<br>
- Security code=0x000000 is the default value and has to be interpreted as: no CODE has been
- set. The actor can also set the security code to 0x000000 from a previously set value. If no
- security code is set, unlock after the unlock period is not processed. Only ping will be
- processed. Remote Management is not available until next power up. 0xFFFFFFFF is reserved
- and can not be used as security code.<br><br>
- To administrate a remote device whose Remote ID must be known. The Remote ID can be determined
- as follows:
- <ul><br>
- <code>attr <name> remote manager</code><br>
- power-up the remote device<br>
- <code>get <name> remoteID</code><br><br>
- </ul>
- All commands are described in the remote management chapters of the <a href="#EnOcean_remoteSet">set</a>-
- and <a href="#EnOcean_remoteGet">get</a>-commands.<br><br>
- The Remote Management Function is configured using the following attributes:<br>
- <ul>
- <li><a href="#EnOcean_remoteCode">remoteCode</a></li>
- <li><a href="#EnOcean_remoteEEP">remoteEEP</a></li>
- <li><a href="#EnOcean_remoteID">remoteID</a></li>
- <li><a href="#EnOcean_remoteManagement">remoteManagement</a></li>
- <li><a href="#EnOcean_remoteManufID">remoteManufID</a></li>
- </ul><br>
- The content of events is described in the chapter <a href="#EnOcean_remoteEvents">Remote Management Events</a><br><br>.
- The following extended functions are supported:
- <ul>
- <li>210:remoteLinkTableInfo</li>
- <li>211:remoteLinkTable</li>
- <li>212:remoteLinkTable</li>
- <li>213:remoteLinkTableGP</li>
- <li>214:remoteLinkTableGP</li>
- <li>220:remoteLearnMode</li>
- <li>221:remoteTeach</li>
- <li>224:remoteReset</li>
- <li>225:remoteRLT</li>
- <li>226:remoteApplyChanges</li>
- <li>227:remoteProductID</li>
- <li>230:remoteDevCfg</li>
- <li>231:remoteDevCfg</li>
- <li>232:remoteLinkCfg</li>
- <li>233:remoteLinkCfg</li>
- <li>240:remoteAck</li>
- <li>250:remoteRepeater</li>
- <li>251:remoteRepeater</li>
- <li>252:remoteRepeaterFilter</li>
- </ul>
- <br><br>
- </ul>
- <b>Radio Link Test</b><br>
- <ul>
- Units supporting the Radio Link Test (RLT) shall offer a functionality that allows for radio link testing between them
- (Position A to Position B, point-to-point only). Fhem support at least 1BS and 4BS test messages. When two units
- perform radio link testing one unit needs to act in a mode called RLT Master and the other unit needs to act in
- a mode called RLT Slave. Fhem acts as RLT Master (subType radioLinkTest).<br>
- The Radio Link Test device must be defined as follows<br>
- <ul><br>
- <code>define <name> EnOcean A5-3F-00</code><br>
- </ul><br>
- and has to by activated
- <ul><br>
- <code>set <name> standby</code><br>
- </ul><br>
- Alternatively, the device can also be created automatically by autocreate. Only one RLT device may be defined in FHEM.<br>
- After activation the RLT Master listens for RLT Query messages. On reception of at least one RLT Query messsage the
- RLT Master responds and starts transmission of RLT MasterTest messages. After that the RLT Master awaits the response
- from the RLT Slave.<br>
- A radio link test communication consits of a minimum of 16 and a maximum of 256 RLT MasterTest messages. When the
- radio link test communication is completed the RLT Master gets deactivated automatically. The test results can be
- found in the log file.
- <br><br>
- </ul>
- <b>Security features</b><br>
- <ul>
- The receiving and sending of encrypted messages is supported. This module currently allows the secure operating mode of PTM 215
- based switches.<br>
- To receive secured telegrams, you first have to start the teach in mode via<br><br>
- <code>set <IODev> teach <t/s></code><br><br>
- and then doing the following on the PTM 215 module:<br>
- <ul>
- <li>Remove the switch cover of the module</li>
- <li>Press both buttons of one rocker side (A0 & A1 or B0 & B1)</li>
- <li>While keeping the buttons pressed actuate the energy bow twice.</li><br>
- </ul>
- This generates two teach-in telegrams which create a Fhem device with the subType "switch.00" and synchronize the Fhem with
- the PTM 215. Both the Fhem and the PTM 215 now maintain a counter which is used to generate a rolling code encryption scheme.
- Also during teach-in, a private key is transmitted to the Fhem. The counter value is allowed to desynchronize for a maximum of
- 128 counts, to allow compensating for missed telegrams, if this value is crossed you need to teach-in the PTM 215 again. Also
- if your Fhem installation gets erased including the state information, you need to teach in the PTM 215 modules again (which
- you would need to do anyway).<br><br>
- To send secured telegrams, you first have send a secure teach-in to the remode device<br><br>
- <ul>
- <code>set <name> teachInSec</code><br><br>
- </ul>
- As for the security of this solution, if someone manages to capture the teach-in telegrams, he can extract the private key,
- so the added security isn't perfect but relies on the fact, that none listens to you setting up your installation.
- <br><br>
- The cryptographic functions need the additional Perl modules Crypt/Rijndael and Crypt/Random. The module must be installed manually.
- With the help of CPAN at the operating system level, for example,<br><br>
- <ul>
- <code>/usr/bin/perl -MCPAN -e 'install Crypt::Rijndael'</code><br>
- <code>/usr/bin/perl -MCPAN -e 'install Crypt::Random'</code>
- </ul>
- <br><br>
- </ul>
- <a name="EnOceandefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> EnOcean <DEF> [<EEP>]|getNextID|<EEP></code>
- <br><br>
- Define an EnOcean device, connected via a <a href="#TCM">TCM</a> modul. The
- <DEF> is the SenderID/DestinationID of the device (8 digit hex number), for example
- <ul><br>
- <code>define switch1 EnOcean FFC54500</code><br>
- </ul><br>
- In order to control devices, you cannot reuse the SenderIDs/
- DestinationID of other devices (like remotes), instead you have to create
- your own, which must be in the allowed SenderID range of the underlying Fhem
- IO device, see <a href="#TCM">TCM</a> BaseID, LastID. For this first query the
- <a href="#TCM">TCM</a> with the <code>get <tcm> baseID</code> command
- for the BaseID. You can use up to 128 IDs starting with the BaseID shown there.
- If you are using an Fhem SenderID outside of the allowed range, you will see an
- ERR_ID_RANGE message in the Fhem log.<br>
- FHEM can assign a free SenderID alternatively, for example
- <ul><br>
- <code>define switch1 EnOcean getNextID</code><br>
- </ul><br>
- If the EEP is known, the appropriate device can be created with the basic parameters, for example
- <ul><br>
- <code>define sensor1 EnOcean FFC54500 A5-02-05</code><br>
- </ul><br>
- or
- <ul><br>
- <code>define sensor1 EnOcean A5-02-05</code><br>
- </ul><br>
- Inofficial EEP for special devices
- <ul>
- <li>G5-07-01 PioTek-Tracker<br></li>
- <li>G5-10-12 Room Sensor and Control Unit [Eltako FUTH65D]<br></li>
- <li>G5-38-08 Gateway, Dimming [Eltako FSG, FUD]<br></li>
- <li>H5-38-08 Gateway, Dimming [Eltako TF61D, TF100D]<br></li>
- <li>M5-38-08 Gateway, Switching [Eltako FSR14]<br></li>
- <li>N5-38-08 Gateway, Switching [Eltako TF61L, TF61R, TF100A, TF100L]<br></li>
- <li>G5-3F-7F Shutter [Eltako FSB]<br></li>
- <li>H5-3F-7F Shutter [Eltako TF61J]<br></li>
- <li>L6-02-01 Smoke Detector [Eltako FRW]<br></li>
- <li>G5-ZZ-ZZ Light and Presence Sensor [Omnio Ratio eagle-PM101]<br></li>
- <li>ZZ-ZZ-ZZ EnOcean RAW profile<br></li>
- <br><br>
- </ul>
- The <a href="#autocreate">autocreate</a> module may help you if the actor or sensor send
- acknowledge messages or teach-in telegrams. In order to control this devices e. g. switches with
- additional SenderIDs you can use the attributes <a href="#subDef">subDef</a>,
- <a href="#subDef0">subDef0</a> and <a href="#subDefI">subDefI</a>.<br>
- Fhem communicates unicast, if bidirectional 4BS or UTE teach-in is used, see
- <a href="#EnOcean_teach-in"> Bidirectional Teach-In / Teach-Out</a>. In this case
- Fhem send unicast telegrams with its SenderID and the DestinationID of the device.
- <br><br>
- </ul>
- <a name="EnOceaninternals"></a>
- <b>Internals</b>
- <ul>
- <li>DEF: 0000000 ... FFFFFFFF|<EEP><br>
- EnOcean DestinationID or SenderID<br>
- If the attributes subDef* are set, this values are used as EnOcean SenderID.<br>
- For an existing device, the device can be re-parameterized by entering the EEP.<br>
- </li>
- <li><IODev>_DestinationID: 0000000 ... FFFFFFFF<br>
- Received destination address, Broadcast radio: FFFFFFFF<br>
- </li>
- <li><IODev>_RSSI: LP/dBm<br>
- Received signal strength indication (best value of all received subtelegrams)<br>
- </li>
- <li><IODev>_ReceivingQuality: excellent|good|bad<br>
- excellent: RSSI >= -76 dBm (internal standard antenna sufficiently)<br>
- good: RSSI < -76 dBm and RSSI >= -87 dBm (good antenna necessary)<br>
- bad: RSSI < -87 dBm (repeater required)<br>
- </li>
- <li><IODev>_RepeatingCounter: 0...2<br>
- Number of forwardings by repeaters<br>
- </li>
- <br><br>
- </ul>
- <a name="EnOceanset"></a>
- <b>Set</b>
- <ul>
- <li><a name="EnOcean_teach-in">Teach-In / Teach-Out</a>
- <ul>
- <li>Teach-in remote devices</li>
- <br>
- <code>set <IODev> teach <t/s></code>
- <br><br>
- Set Fhem in the learning mode.<br>
- A device, which is then also put in this state is to paired with
- Fhem. Bidirectional Teach-In / Teach-Out is used for some 4BS, VLD and MSC devices,
- e. g. EEP 4BS, RORG A5-20-01 (Battery Powered Actuator).<br>
- Bidirectional Teach-In for 4BS, UTE and Generic Profiles are supported.<br>
- <code>IODev</code> is the name of the TCM Module.<br>
- <code>t/s</code> is the time for the learning period.
- <br><br>
- Types of learning modes see <a href="#TCM_learningMode">learningMode</a>
- <br><br>
- Example:
- <ul><code>set TCM_0 teach 600</code></ul>
- <br>
- <li>RPS profiles Teach-In (switches)</li>
- <br>
- <code>set <name> A0|AI|B0|BI|C0|CI|D0|DI</code>
- <br><br>
- Send teach-in telegram to remote device.
- <br><br>
- <li>1BS profiles Teach-In (contact)</li>
- <br>
- <code>set <name> teach</code>
- <br><br>
- Send teach-in telegram to remote device.
- <br><br>
- <li>4BS profiles Teach-In (sensors, dimmer, room controller etc.)</li>
- <br>
- <code>set <name> teach</code>
- <br><br>
- Send teach-in telegram to remote device.<br>
- If no SenderID (attr subDef) was assigned before a learning telegram is sent for the first time, a free SenderID
- is assigned automatically.
- <br><br>
- <li>UTE - Universal Uni- and Bidirectional Teach-In</li>
- <br>
- <code>set <name> teachIn|teachOut</code>
- <br><br>
- Send teach-in telegram to remote device.<br>
- If no SenderID (attr subDef) was assigned before a learning telegram is sent for the first time, a free SenderID
- is assigned automatically.
- <br><br>
- <li>Generic Profiles Teach-In</li>
- <br>
- <code>set <name> teachIn|teachOut</code>
- <br><br>
- Send teach-in telegram to remote device.<br>
- If no SenderID (attr subDef) was assigned before a learning telegram is sent for the first time, a free SenderID
- is assigned automatically.
- <br><br>
- <li>Secure Devices Teach-In</li>
- <br>
- <code>set <name> teachInSec</code>
- <br><br>
- Secure teach-in to the remode device.<br>
- If no SenderID (attr subDef) was assigned before a learning telegram is sent for the first time, a free SenderID
- is assigned automatically.
- <br><br>
- </ul>
- </li>
- <li><a name="EnOcean_smartAck">Smart Ack Learning</a>
- <ul>
- <li>Teach-in remote Smart Ack devices</li>
- <br>
- <code>set <IODev> smartAckLearn <t/s></code>
- <br><br>
- Set Fhem in the Smart Ack learning mode.<br>
- The post master fuctionality must be activated using the command <code>smartAckMailboxMax</code> in advance.<br>
- The simple learnmode is supported, see <a href="#TCM_smartAckLearnMode">smartAckLearnMode</a><br>
- A device, which is then also put in this state is to paired with
- Fhem. Bidirectional learn in for 4BS, UTE and Generic Profiles are supported.<br>
- <code>IODev</code> is the name of the TCM Module.<br>
- <code>t/s</code> is the time for the learning period.
- <br><br>
- Example:
- <ul><code>set TCM_0 smartAckLearn 600</code></ul>
- <br>
- </ul>
- </li>
- <li><a name="EnOcean_remoteSet">Remote Management</a>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>remoteAction<br>
- sent action command to perfoms an action, depending on the functionality of the device</li>
- <li>remoteApplyChanges devCfg|linkTable|no_change<br>
- apply changes</li>
- <li>remoteDevCfg <index> <value><br>
- set configuration</li>
- <li>remoteLinkTable in|out <index> <ID> <EEP> <channel><br>
- set link table content</li>
- <li>remoteLinkCfg in|out <index> <data index> <value><br>
- set link based configuration</li>
- <li>remoteLinkTableGP in|out <index> <GP channel description><br>
- set link table content</li>
- <li>remoteLock<br>
- locks the remote device or local client</li>
- <li>remoteLearnMode in|out|off <index><br>
- initiate remote learn-in or learn-out of inbound index</li>
- <li>remoteReset devCfg|linkTableIn|linkTableOut|no_change<br>
- reset to defaults</li>
- <li>remoteRLT on|off <number of RLT slaves><br>
- reset to defaults</li>
- <li>remoteRepeater on|off|filter <level> <filter structure><br>
- set repeater functions</li>
- <li>remoteRepeaterFilter apply|block|delete|deleteAll destinationID|sourceID|rorg|rssi <filter value><br>
- set repeater functions</li>
- <li>remoteSetCode<br>
- set the remote security code</li>
- <li>remoteTeach <channel><br>
- request teach-in telegram from channel 00..FF</li>
- <li>remoteUnlock [1...1800]<br>
- unlocks the remote device or local client<br>
- The unlock period can be set in the client mode between 1s and 1800 s.</li>
- <br>
- [<channel>] = 00...FF<br>
- [<EEP>] = <RORG>-<function>-<type><br>
- [<filter structure>] = AND|OR<br>
- [<filter value>] = <destinationID>|<sourceID>|<RORG>|<LP/dBm><br>
- [<GP channel description>] = <name of channel 00>:<O|I>:<channel type>:<signal type>:<value type>[:<resolution>[:<engineering min>:<scaling min>:<engineering max>:<scaling max>]]<br>
- [<ID>] = 00000001...FFFFFFFE<br>
- [<index>] = 00...FF<br>
- [<number of RLT slaves>] = 01..7F<br>
- [<level>] = 1|2<br>
- [<data index>] = 0000...FFFF<br>
- [<value>] = n x 00...FF<br>
- </ul><br>
- </li><br>
- <li>Switch, Pushbutton Switch (EEP F6-02-01 ... F6-03-02)<br>
- RORG RPS [default subType]
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is one of A0, AI, B0, BI, C0, CI, D0, DI,
- combinations of these and released. First and second action can be sent
- simultaneously. Separate first and second action with a comma.<br>
- In fact we are trying to emulate a PT200 type remote.<br>
- If you define an <a href="#eventMap">eventMap</a> attribute with on/off,
- then you will be able to easily set the device from the <a
- href="#FHEMWEB">WEB</a> frontend.<br>
- <a href="#setExtensions">set extensions</a> are supported, if the corresponding
- <a href="#eventMap">eventMap</a> specifies the <code>on</code> and <code>off</code>
- mappings, for example <code>attr <name> eventMap on-till:on-till AI:on A0:off</code>.<br>
- With the help of additional <a href="#EnOceanattr">attributes</a>, the
- behavior of the devices can be adapt.<br>
- The attr subType must be switch. This is done if the device was created by autocreate.
- <br><br>
- Example:
- <ul><code>
- set switch1 BI<br>
- set switch1 B0,CI<br>
- attr eventMap BI:on B0:off<br>
- set switch1 on<br>
- </code></ul><br>
- </ul>
- </li>
- <li>Staircase off-delay timer (EEP F6-02-01 ... F6-02-02)<br>
- RORG RPS [Eltako FTN14, tested with Eltako FTN14 only]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>on<br>
- issue switch on command</li>
- <li>released<br>
- start timer</li>
- </ul><br>
- Set attr eventMap to B0:on BI:off, attr subType to switch, attr
- webCmd to on:released and if needed attr switchMode to pushbutton manually.<br>
- The attr subType must be switch. This is done if the device was created by autocreate.<br>
- Use the sensor type "Schalter" for Eltako devices. The Staircase
- off-delay timer is switched on when pressing "on" and the time will be started
- when pressing "released". "released" immediately after "on" is sent if
- the attr switchMode is set to "pushbutton".
- </li>
- <br><br>
- <li>Pushbutton Switch (EEP D2-03-00)<br>
- RORG VLD [EnOcean PTM 215 Modul]
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teachIn<br>
- initiate UTE teach-in</li>
- <li>teachInSec<br>
- initiate secure teach-in</li>
- <li>teachOut<br>
- initiate UTE teach-out</li>
- <li>A0|AI|B0|BI<br>
- issue switch command</li>
- <li>A0,B0|A0,AI|AI,B0|AI,BI<br>
- issue switch command</li>
- <li>pressed<br>
- energy bow pressed</li>
- <li>pressed34<br>
- 3 or 4 buttons and energy bow pressed</li>
- <li>released<br>
- energy bow released</li><br>
- </ul>
- First and second action can be sent simultaneously. Separate first and second action with a comma.<br>
- If you define an <a href="#eventMap">eventMap</a> attribute with on/off,
- then you will be able to easily set the device from the <a href="#FHEMWEB">WEB</a> frontend.<br>
- <a href="#setExtensions">set extensions</a> are supported, if the corresponding
- <a href="#eventMap">eventMap</a> specifies the <code>on</code> and <code>off</code>
- mappings, for example <code>attr <name> eventMap on-till:on-till AI:on A0:off</code>.<br>
- If <a href="#EnOcean_comMode">comMode</a> is set to biDir the device can be controlled bidirectionally.<br>
- With the help of additional <a href="#EnOceanattr">attributes</a>, the behavior of the devices can be adapt.<br>
- The attr subType must be switch.00. This is done if the device was created by autocreate.
- <br><br>
- <ul>
- Example:
- <ul><code>
- set switch1 BI<br>
- set switch1 B0,CI<br>
- attr eventMap BI:on B0:off<br>
- set switch1 on<br>
- </code></ul><br>
- </ul>
- </li>
- <li>Single Input Contact, Door/Window Contact (EEP D5-00-01)<br>
- RORG 1BS [tested with Eltako FSR14]
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>closed<br>
- issue closed command</li>
- <li>open<br>
- issue open command</li>
- <li>teach<br>
- initiate teach-in</li>
- </ul><br>
- The attr subType must be contact. The attribute must be set manually.
- </li><br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-02)<br>
- [Thermokon SR04 PTS]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate teach-in</li>
- <li>setpoint [0 ... 255]<br>
- Set the actuator to the specifed setpoint.</li>
- <li>setpointScaled [<floating-point number>]<br>
- Set the actuator to the scaled setpoint.</li>
- <li>fanStage [auto|0|1|2|3]<br>
- Set fan stage</li>
- <li>switch [on|off]<br>
- Set switch</li>
- </ul><br>
- The actual temperature will be taken from the temperature reported by
- a temperature reference device <a href="#temperatureRefDev">temperatureRefDev</a>
- primarily or from the attribute <a href="#actualTemp">actualTemp</a> if it is set.<br>
- If the attribute <a href="#EnOcean_setCmdTrigger">setCmdTrigger</a> is set to "refDev", a setpoint
- command is sent when the reference device is updated.<br>
- The scaling of the setpoint adjustment is device- and vendor-specific. Set the
- attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
- <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled setting
- setpointScaled.<br>
- The attr subType must be roomSensorControl.05. The attribute must be set manually.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-03)<br>
- [used for IntesisBox PA-AC-ENO-1i]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate teach-in</li>
- <li>setpoint [0 ... 255]<br>
- Set the actuator to the specifed setpoint.</li>
- <li>setpointScaled [<floating-point number>]<br>
- Set the actuator to the scaled setpoint.</li>
- <li>fanStage [auto|0|1|2|3]<br>
- Set fan stage</li>
- <li>switch [on|off]<br>
- Set switch</li>
- </ul><br>
- The actual temperature will be taken from the temperature reported by
- a temperature reference device <a href="#temperatureRefDev">temperatureRefDev</a>
- primarily or from the attribute <a href="#actualTemp">actualTemp</a> if it is set.<br>
- If the attribute <a href="#EnOcean_setCmdTrigger">setCmdTrigger</a> is set to "refDev", a setpoint
- command is sent when the reference device is updated.<br>
- The scaling of the setpoint adjustment is device- and vendor-specific. Set the
- attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
- <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled setting
- setpointScaled.<br>
- The attr subType must be roomSensorControl.05 and attr manufID must be 019. The attribute must be set manually.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (A5-10-06 plus night reduction)<br>
- [Eltako FTR65DS, FTR65HS]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate teach-in</li>
- <li>desired-temp [t/°C [lock|unlock]]<br>
- Set the desired temperature.</li>
- <li>nightReduction [t/K [lock|unlock]]<br>
- Set night reduction</li>
- <li>setpointTemp [t/°C [lock|unlock]]<br>
- Set the desired temperature</li>
- </ul><br>
- The actual temperature will be taken from the temperature reported by
- a temperature reference device <a href="#temperatureRefDev">temperatureRefDev</a>
- primarily or from the attribute <a href="#actualTemp">actualTemp</a> if it is set.<br>
- If the attribute <a href="#EnOcean_setCmdTrigger">setCmdTrigger</a> is set to "refDev", a setpointTemp
- command is sent when the reference device is updated.<br>
- This profil can be used with a further Room Sensor and Control Unit Eltako FTR55*
- to control a heating/cooling relay FHK12, FHK14 or FHK61. If Fhem and FTR55*
- is teached in, the temperature control of the FTR55* can be either blocked
- or to a setpoint deviation of +/- 3 K be limited. For this use the optional parameter
- [block] = lock|unlock, unlock is default.<br>
- The attr subType must be roomSensorControl.05 and attr manufID must be 00D.
- The attributes must be set manually.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-10)<br>
- [Thermokon SR04 * rH, Thanos SR *]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate teach-in</li>
- <li>setpoint [0 ... 255]<br>
- Set the actuator to the specifed setpoint.</li>
- <li>setpointScaled [<floating-point number>]<br>
- Set the actuator to the scaled setpoint.</li>
- <li>switch [on|off]<br>
- Set switch</li>
- </ul><br>
- The actual temperature will be taken from the temperature reported by
- a temperature reference device <a href="#temperatureRefDev">temperatureRefDev</a>
- primarily or from the attribute <a href="#actualTemp">actualTemp</a> if it is set.<br>
- The actual humidity will be taken from the humidity reported by
- a humidity reference device <a href="#EnOcean_humidityRefDev">humidityRefDev</a>
- primarily or from the attribute <a href="#EnOcean_humidity">humidity</a> if it is set.<br>
- If the attribute <a href="#EnOcean_setCmdTrigger">setCmdTrigger</a> is set to "refDev", a setpoint
- command is sent when the reference device is updated.<br>
- The scaling of the setpoint adjustment is device- and vendor-specific. Set the
- attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
- <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled setting
- setpointScaled.<br>
- The attr subType must be roomSensorControl.01. The attribute must be set manually.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-12)<br>
- [Eltako FUTH65D]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate teach-in</li>
- <li>setpoint [0 ... 255]<br>
- Set the actuator to the specifed setpoint.</li>
- <li>setpointScaled [<floating-point number>]<br>
- Set the actuator to the scaled setpoint.</li>
- <li>switch [on|off]<br>
- Set switch</li>
- </ul><br>
- The actual temperature will be taken from the temperature reported by
- a temperature reference device <a href="#temperatureRefDev">temperatureRefDev</a>
- primarily or from the attribute <a href="#actualTemp">actualTemp</a> if it is set.<br>
- If the attribute <a href="#EnOcean_setCmdTrigger">setCmdTrigger</a> is set to "refDev", a setpoint
- command is sent when the reference device is updated.<br>
- The attr subType must be roomSensorControl.01 and attr manufID must be 00D. The attribute must be set manually.
- </li>
- <br><br>
- <li>Battery Powered Actuator (EEP A5-20-01)<br>
- [Kieback&Peter MD15-FTL-xx]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>setpoint setpoint/%<br>
- Set the actuator to the specifed setpoint (0...100). The setpoint can also be set by the
- <a href="#EnOcean_setpointRefDev">setpointRefDev</a> device if it is set.</li>
- <li>setpointTemp t/°C<br>
- Set the actuator to the specifed temperature setpoint. The temperature setpoint can also be set by the
- <a href="#EnOcean_setpointTempRefDev">setpointTempRefDev</a> device if it is set.<br>
- The FHEM PID controller calculates the actuator setpoint based on the temperature setpoint. The controller's
- operation can be set via the PID parameters <a href="#EnOcean_pidFactor_P">pidFactor_P</a>,
- <a href="#EnOcean_pidFactor_I">pidFactor_I</a> and <a href="#EnOcean_pidFactor_D">pidFactor_D</a>.<br>
- If the attribute pidCtrl is set to off, the PI controller of the actuator is used (selfCtrl mode). Please
- read the instruction manual of the device, whether the device has an internal PI controller.<br></li>
- <li>runInit<br>
- Maintenance Mode: Run init sequence</li>
- <li>valveOpens<br>
- Maintenance Mode: Valve opens<br>
- After the valveOpens command, the valve remains open permanently and can no longer be controlled by Fhem.
- By pressing the button on the device itself, the actuator is returned to its normal operating state.</li>
- <li>valveCloses<br>
- Maintenance Mode: Valve closes</li>
- </ul><br>
- The Heating Radiator Actuating Drive is configured using the following attributes:<br>
- <ul>
- <li><a href="#EnOcean_pidActorCallBeforeSetting">pidActorCallBeforeSetting</a></li>
- <li><a href="#EnOcean_pidActorErrorAction">pidActorErrorAction</a></li>
- <li><a href="#EnOcean_pidActorErrorPos">pidActorErrorPos</a></li>
- <li><a href="#EnOcean_pidActorLimitLower">pidActorLimitLower</a></li>
- <li><a href="#EnOcean_pidActorLimitUpper">pidActorLimitUpper</a></li>
- <li><a href="#EnOcean_pidCtrl">pidCtrl</a></li>
- <li><a href="#EnOcean_pidDeltaTreshold">pidDeltaTreshold</a></li>
- <li><a href="#EnOcean_pidFactor_P">pidFactor_P</a></li>
- <li><a href="#EnOcean_pidFactor_I">pidFactor_I</a></li>
- <li><a href="#EnOcean_pidFactor_D">pidFactor_D</a></li>
- <li><a href="#EnOcean_pidIPortionCallBeforeSetting">pidIPortionCallBeforeSetting</a></li>
- <li><a href="#EnOcean_pidSensorTimeout">pidSensorTimeout</a></li>
- <li><a href="#EnOcean_rcvRespAction">rcvRespAction</a></li>
- <li><a href="#EnOcean_setpointRefDev">setpointRefDev</a></li>
- <li><a href="#EnOcean_setpointSummerMode">setpointSummerMode</a></li>
- <li><a href="#EnOcean_setpointTempRefDev">setpointTempRefDev</a></li>
- <li><a href="#temperatureRefDev">temperatureRefDev</a></li>
- <li><a href="#EnOcean_summerMode">summerMode</a></li>
- </ul>
- The actual temperature will be reported by the Heating Radiator Actuating Drive or by the
- <a href="#temperatureRefDev">temperatureRefDev</a> if it is set. The internal temperature sensor
- of the Micropelt iTRV is not suitable as an actual temperature value for the PID controller.
- An external room thermostat is required.<br>
- The attr event-on-change-reading .* shut not by set. The PID controller expects periodic events.
- If these are missing, a communication alarm is signaled.<br>
- The attr subType must be hvac.01. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.<br>
- The command is not sent until the device wakes up and sends a message, usually
- every 10 minutes.
- </li>
- <br><br>
- <li>Heating Radiator Actuating Drive (EEP A5-20-04)<br>
- [Holter SmartDrive MX]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>setpoint setpoint/%<br>
- Set the actuator to the specifed setpoint (0...100). The setpoint can also be set by the
- <a href="#EnOcean_setpointRefDev">setpointRefDev</a> device if it is set.</li>
- <li>setpointTemp t/°C<br>
- Set the actuator to the specifed temperature setpoint. The temperature setpoint can also be set by the
- <a href="#EnOcean_setpointTempRefDev">setpointTempRefDev</a> device if it is set.<br>
- The FHEM PID controller calculates the actuator setpoint based on the temperature setpoint. The controller's
- operation can be set via the PID parameters <a href="#EnOcean_pidFactor_P">pidFactor_P</a>,
- <a href="#EnOcean_pidFactor_I">pidFactor_I</a> and <a href="#EnOcean_pidFactor_D">pidFactor_D</a>.</li>
- <li>runInit<br>
- Maintenance Mode: Run init sequence</li>
- <li>valveOpens<br>
- Maintenance Mode: Valve opens<br>
- After the valveOpens command, the valve remains open permanently and can no longer be controlled by Fhem.
- By pressing the button on the device itself, the actuator is returned to its normal operating state.</li>
- <li>valveCloses<br>
- Maintenance Mode: Valve closes</li>
- </ul><br>
- The Heating Radiator Actuating Drive is configured using the following attributes:<br>
- <ul>
- <li><a href="#EnOcean_blockKey">blockKey</a></li>
- <li><a href="#EnOcean_displayOrientation">displayOrientation</a></li>
- <li><a href="#EnOcean_measurementCtrl">measurementCtrl</a></li>
- <li><a href="#model">model</a></li>
- <li><a href="#EnOcean_pidActorCallBeforeSetting">pidActorCallBeforeSetting</a></li>
- <li><a href="#EnOcean_pidActorErrorAction">pidActorErrorAction</a></li>
- <li><a href="#EnOcean_pidActorErrorPos">pidActorErrorPos</a></li>
- <li><a href="#EnOcean_pidActorLimitLower">pidActorLimitLower</a></li>
- <li><a href="#EnOcean_pidActorLimitUpper">pidActorLimitUpper</a></li>
- <li><a href="#EnOcean_pidCtrl">pidCtrl</a></li>
- <li><a href="#EnOcean_pidDeltaTreshold">pidDeltaTreshold</a></li>
- <li><a href="#EnOcean_pidFactor_P">pidFactor_P</a></li>
- <li><a href="#EnOcean_pidFactor_I">pidFactor_I</a></li>
- <li><a href="#EnOcean_pidFactor_D">pidFactor_D</a></li>
- <li><a href="#EnOcean_pidIPortionCallBeforeSetting">pidIPortionCallBeforeSetting</a></li>
- <li><a href="#EnOcean_pidSensorTimeout">pidSensorTimeout</a></li>
- <li><a href="#EnOcean_setpointRefDev">setpointRefDev</a></li>
- <li><a href="#EnOcean_setpointTempRefDev">setpointTempRefDev</a></li>
- <li><a href="#temperatureRefDev">temperatureRefDev</a></li>
- <li><a href="#EnOcean_summerMode">summerMode</a></li>
- <li><a href="#EnOcean_wakeUpCycle">wakeUpCycle</a></li>
- </ul>
- The actual temperature will be reported by the Heating Radiator Actuating Drive or by the
- <a href="#temperatureRefDev">temperatureRefDev</a> if it is set.<br>
- The attr event-on-change-reading .* shut not by set. The PID controller expects periodic events.
- If these are missing, a communication alarm is signaled.<br>
- The attr subType must be hvac.04. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.<br>
- The OEM version of the Holter SmartDrive MX has an internal PID controller. This function is activated by
- attr <device> model OEM and attr <device> pidCtrl off.<br>
- The command is not sent until the device wakes up and sends a message, usually
- every 5 minutes.
- </li>
- <br><br>
- <li>Generic HVAC Interface (EEP A5-20-10)<br>
- [IntesisBox PA-AC-ENO-1i]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>ctrl auto|0...100<br>
- Set control variable</li>
- <li>fanSpeed auto|1...14<br>
- Set fan speed</li>
- <li>occupancy occupied|off|standby|unoccupied<br>
- Set room occupancy</li>
- <li>on<br>
- Set on</li>
- <li>off<br>
- Set off</li>
- <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>
- Set mode</li>
- <li>teach<br>
- Teach-in</li>
- <li>vanePosition auto|horizontal|position_2|position_3|position_4|vertical|swing|vertical_swing|horizontal_swing|hor_vert_swing|stop_swing<br>
- Set vane position</li>
- </ul><br>
- The attr subType must be hvac.10. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Generic HVAC Interface - Error Control (EEP A5-20-11)<br>
- [IntesisBox PA-AC-ENO-1i]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>externalDisable disable|enable<br>
- Set external disablement</li>
- <li>remoteCtrl disable|enable<br>
- Dieable/enable remote controller</li>
- <li>teach<br>
- Teach-in</li>
- <li>window closed|opened<br>
- Set window state</li>
- </ul><br>
- The attr subType must be hvac.11. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Energy management, <a name="demand_response">demand response</a> (EEP A5-37-01)<br>
- demand response master commands<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>level 0...15 [<powerUsageScale> [<randomStart> [<randomEnd> [timeout]]]]<br>
- set demand response level</li>
- <li>max [<powerUsageScale> [<randomStart> [<randomEnd> [timeout]]]]<br>
- set power usage level to max</li>
- <li>min [<powerUsageScale> [<randomStart> [<randomEnd> [timeout]]]]<br>
- set power usage level to min</li>
- <li>power power/% [<powerUsageScale> [<randomStart> [<randomEnd> [timeout]]]]<br>
- set power</li>
- <li>setpoint 0...255 [<powerUsageScale> [<randomStart> [<randomEnd> [timeout]]]]<br>
- set setpoint</li>
- <li>teach<br>
- initiate teach-in</li>
- </ul><br>
- [<powerUsageScale>] = max|rel, [<powerUsageScale>] = max is default<br>
- [<randomStart>] = yes|no, [<randomStart>] = no is default<br>
- [<randomEnd>] = yes|no, [<randomEnd>] = no is default<br>
- [timeout] = 0/min | 15/min ... 3825/min, [timeout] = 0 is default<br>
- The attr subType must be energyManagement.01.<br>
- This is done if the device was created by autocreate.<br>
- </li>
- <br><br>
- <li><a name="Gateway">Gateway</a> (EEP A5-38-08)<br>
- The Gateway profile include 7 different commands (Switching, Dimming,
- Setpoint Shift, Basic Setpoint, Control variable, Fan stage, Blind Central Command).
- The commands can be selected by the attribute gwCmd or command line. The attribute
- entry has priority.<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li><gwCmd> <cmd> [subCmd]<br>
- initiate Gateway commands by command line</li>
- <li><cmd> [subCmd]<br>
- initiate Gateway commands if attribute gwCmd is set.</li>
- </ul><br>
- The attr subType must be gateway. Attribute gwCmd can also be set to
- switching|dimming|setpointShift|setpointBasic|controlVar|fanStage|blindCmd.<br>
- This is done if the device was created by autocreate.<br>
- For Eltako devices attributes must be set manually.
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Switching<br>
- [Eltako FLC61, FSA12, FSR14]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate teach-in mode</li>
- <li>on [lock|unlock]<br>
- issue switch on command</li>
- <li>off [lock|unlock]<br>
- issue switch off command</li>
- <li><a href="#setExtensions">set extensions</a> are supported.</li>
- </ul><br>
- The attr subType must be gateway and gwCmd must be switching. This is done if the device was
- created by autocreate.<br>
- For Eltako devices attributes must be set manually. For Eltako FSA12 attribute model must be set
- to FSA12.
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Dimming<br>
- [Eltako FUD12, FUD14, FUD61, FUD70, FSG14, ...]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>dim/% [rampTime/s [lock|unlock]]<br>
- issue dim command</li>
- <li>teach<br>
- initiate teach-in mode</li>
- <li>on [lock|unlock]<br>
- issue switch on command</li>
- <li>off [lock|unlock]<br>
- issue switch off command</li>
- <li>dim dim/% [rampTime/s [lock|unlock]]<br>
- issue dim command</li>
- <li>dimup dim/% [rampTime/s [lock|unlock]]<br>
- issue dim command</li>
- <li>dimdown dim/% [rampTime/s [lock|unlock]]<br>
- issue dim command</li>
- <li><a href="#setExtensions">set extensions</a> are supported.</li>
- </ul><br>
- rampTime Range: t = 1 s ... 255 s or 0 if no time specified,
- for Eltako: t = 1 = fast dimming ... 255 = slow dimming or 0 = dimming speed on the dimmer used<br>
- The attr subType must be gateway and gwCmd must be dimming. This is done if the device was
- created by autocreate.<br>
- For Eltako devices attributes must be set manually. Use the sensor type "PC/FVS" for Eltako devices.
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Dimming of fluorescent lamps<br>
- [Eltako FSG70, tested with Eltako FSG70 only]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>on<br>
- issue switch on command</li>
- <li>off<br>
- issue switch off command</li>
- <li><a href="#setExtensions">set extensions</a> are supported.</li>
- </ul><br>
- The attr subType must be gateway and gwCmd must be dimming. Set attr eventMap to B0:on BI:off,
- attr subTypeSet to switch and attr switchMode to pushbutton manually.<br>
- Use the sensor type "Richtungstaster" for Eltako devices.
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Setpoint shift<br>
- [untested]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate teach-in mode</li>
- <li>shift 1/K <br>
- issue Setpoint shift</li>
- </ul><br>
- Shift Range: T = -12.7 K ... 12.8 K<br>
- The attr subType must be gateway and gwCmd must be setpointShift.
- This is done if the device was created by autocreate.<br>
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Basic Setpoint<br>
- [untested]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate teach-in mode</li>
- <li>basic t/°C<br>
- issue Basic Setpoint</li>
- </ul><br>
- Setpoint Range: t = 0 °C ... 51.2 °C<br>
- The attr subType must be gateway and gwCmd must be setpointBasic.
- This is done if the device was created by autocreate.<br>
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Control variable<br>
- [untested]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate teach-in mode</li>
- <li>presence present|absent|standby<br>
- issue Room occupancy</li>
- <li>energyHoldOff normal|holdoff<br>
- issue Energy hold off</li>
- <li>controllerMode auto|heating|cooling|off<br>
- issue Controller mode</li>
- <li>controllerState auto|override <0 ... 100> <br>
- issue Control variable override</li>
- </ul><br>
- Override Range: cvov = 0 % ... 100 %<br>
- The attr subType must be gateway and gwCmd must be controlVar.
- This is done if the device was created by autocreate.<br>
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Fan stage<br>
- [untested]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate teach-in mode</li>
- <li>stage 0 ... 3|auto<br>
- issue Fan Stage override</li>
- </ul><br>
- The attr subType must be gateway and gwCmd must be fanStage.
- This is done if the device was created by autocreate.<br>
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- <a name="Blind Command Central">Blind Command Central</a><br>
- [not fully tested]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>position/% [α/°]<br>
- drive blinds to position with angle value</li>
- <li>teach<br>
- initiate teach-in mode</li>
- <li>status<br>
- Status request</li>
- <li>opens<br>
- issue blinds opens command</li>
- <li>up tu/s ta/s<br>
- issue roll up command</li>
- <li>closes<br>
- issue blinds closes command</li>
- <li>down td/s ta/s<br>
- issue roll down command</li>
- <li>position position/% [α/°]<br>
- drive blinds to position with angle value</li>
- <li>stop<br>
- issue blinds stops command</li>
- <li>runtimeSet tu/s td/s<br>
- set runtime parameter</li>
- <li>angleSet ta/s<br>
- set angle configuration</li>
- <li>positionMinMax positionMin/% positionMax/%<br>
- set min, max values for position</li>
- <li>angleMinMax αo/° αs/°<br>
- set slat angle for open and shut position</li>
- <li>positionLogic normal|inverse<br>
- set position logic</li>
- </ul><br>
- Runtime Range: tu|td = 0 s ... 255 s<br>
- Select a runtime up and a runtime down that is at least as long as the
- shading element or roller shutter needs to move from its end position to
- the other position.<br>
- Position Range: position = 0 % ... 100 %<br>
- Angle Time Range: ta = 0 s ... 25.5 s<br>
- Runtime value for the sunblind reversion time. Select the time to revolve
- the sunblind from one slat angle end position to the other end position.<br>
- Slat Angle: α|αo|αs = -180 ° ... 180 °<br>
- Position Logic, normal: Blinds fully opens corresponds to Position = 0 %<br>
- Position Logic, inverse: Blinds fully opens corresponds to Position = 100 %<br>
- The attr subType must be gateway and gwCmd must be blindCmd.<br>
- See also attributes <a href="#EnOcean_sendDevStatus">sendDevStatus and <a href="#EnOcean_serviceOn">serviceOn</a></a><br>
- The profile is linked with controller profile, see <a href="#Blind Status">Blind Status</a>.<br>
- </li>
- <br><br>
- <li>Extended Lighting Control (EEP A5-38-09)<br>
- [untested]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>teach<br>
- initiate remote teach-in</li>
- <li>on<br>
- issue switch on command</li>
- <li>off<br>
- issue switch off command</li>
- <li>dim dim [rampTime/s]<br>
- issue dim command</li>
- <li>dimup rampTime/s<br>
- issue dim command</li>
- <li>dimdown rampTime/s<br>
- issue dim command</li>
- <li>stop<br>
- stop dimming</li>
- <li>rgb <red color value><green color value><blue color value><br>
- issue color value command</li>
- <li>scene drive|store 0..15<br>
- store actual value in the scene or drive to scene value</li>
- <li>dimMinMax <min value> <max value><br>
- set minimal and maximal dimmer value</li>
- <li>lampOpHours 0..65535<br>
- set the operation hours of the lamp</li>
- <li>block unlock|on|off|local<br>
- locking local operations</li>
- <li>meteringValues 0..65535 mW|W|kW|MW|Wh|kWh|MWh|GWh|mA|mV<br>
- set a new value for the energy metering (overwrite the actual value with the selected unit)</li>
- <li>meteringValues 0..6553.5 A|V<br>
- set a new value for the energy metering (overwrite the actual value with the selected unit)</li>
- <li><a href="#setExtensions">set extensions</a> are supported.</li>
- </ul><br>
- color values: 00 ... FF hexadecimal<br>
- rampTime Range: t = 1 s ... 65535 s or 1 if no time specified, ramping time can be set by attribute
- <a href="#EnOcean_rampTime">rampTime</a><br>
- The attr subType or subTypSet must be lightCtrl.01. This is done if the device was created by autocreate.<br>
- The subType is associated with the subtype lightCtrlState.02.
- </li>
- <br><br>
- <li><a name="Manufacturer Specific Applications">Manufacturer Specific Applications</a> (EEP A5-3F-7F)<br>
- Shutter<br>
- [Eltako FSB12, FSB14, FSB61, FSB70, tested with Eltako devices only]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>position/% [α/°]<br>
- drive blinds to position with angle value</li>
- <li>anglePos α/°<br>
- drive blinds to angle value</li>
- <li>closes<br>
- issue blinds closes command</li>
- <li>down td/s<br>
- issue roll down command</li>
- <li>opens<br>
- issue blinds opens command</li>
- <li>position position/% [α/°]<br>
- drive blinds to position with angle value</li>
- <li>stop<br>
- issue stop command</li>
- <li>teach<br>
- initiate teach-in mode</li>
- <li>up tu/s<br>
- issue roll up command</li>
- </ul><br>
- Run-time Range: tu|td = 1 s ... 255 s<br>
- Position Range: position = 0 % ... 100 %<br>
- Slat Angle Range: α = -180 ° ... 180 °<br>
- Angle Time Range: ta = 0 s ... 6 s<br>
- The devive can only fully controlled if the attributes <a href="#angleMax">angleMax</a>,
- <a href="#angleMin">angleMin</a>, <a href="#angleTime">angleTime</a>,
- <a href="#shutTime">shutTime</a> and <a href="#shutTimeCloses">shutTimeCloses</a>,
- are set correctly.
- If <a href="#EnOcean_settingAccuracy">settingAccuracy</a> is set to high, the run-time is sent in 1/10 increments.<br>
- Set attr subType to manufProfile, manufID to 00D and attr model to FSB14|FSB61|FSB70|TF manually.<br>
- Use the sensor type "Szenentaster/PC" for Eltako devices.
- </li>
- <br><br>
- <li>Electronic switches and dimmers with Energy Measurement and Local Control (D2-01-00 - D2-01-12)<br>
- [Telefunken Funktionsstecker, PEHA Easyclick, AWAG Elektrotechnik AG Omnio UPS 230/xx,UPD 230/xx, NodOn in-wall module, smart plug]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>autoOffTime t/s [<channel>]<br>
- set auto Off timer</li>
- <li>delayOffTime t/s [<channel>]<br>
- set delay Off timer</li>
- <li>dim/% [<channel> [<rampTime>]]<br>
- issue dimming command</li>
- <li>extSwitchMode unavailable|switch|pushbutton|auto [<channel>]<br>
- set external interface mode</li>
- <li>extSwitchType toggle|direction [<channel>]<br>
- set external interface type</li>
- <li>on [<channel>]<br>
- issue switch on command</li>
- <li>off [<channel>]<br>
- issue switch off command</li>
- <li>dim dim/% [<channel> [<rampTime>]]<br>
- issue dimming command</li>
- <li>local dayNight day|night, day is default<br>
- set the user interface indication</li>
- <li>local defaultState on|off|last, off is default<br>
- set the default setting of the output channels when switch on</li>
- <li>local localControl enabled|disabled, disabled is default<br>
- enable the local control of the device</li>
- <li>local overCurrentShutdown off|restart, off is default<br>
- set the behavior after a shutdown due to an overcurrent</li>
- <li>local overCurrentShutdownReset not_active|trigger, not_active is default<br>
- trigger a reset after an overcurrent</li>
- <li>local rampTime<1...3> 0/s, 0.5/s ... 7/s, 7.5/s, 0 is default<br>
- set the dimming time of timer 1 ... 3</li>
- <li>local teachInDev enabled|disabled, disabled is default<br>
- enable the taught-in devices with different EEP</li>
- <li>measurement delta 0/s ... 4095/s, 0 is default<br>
- define the difference between two displayed measurements </li>
- <li>measurement mode energy|power, energy is default<br>
- define the measurand</li>
- <li>measurement report query|auto, query is default<br>
- specify the measurement method</li>
- <li>measurement reset not_active|trigger, not_active is default<br>
- resetting the measured values</li>
- <li>measurement responseTimeMax 10/s ... 2550/s, 10 is default<br>
- set the maximum time between two outputs of measured values</li>
- <li>measurement responseTimeMin 1/s ... 255/s, 1 is default<br>
- set the minimum time between two outputs of measured values</li>
- <li>measurement unit Ws|Wh|KWh|W|KW, Ws is default<br>
- specify the measurement unit</li>
- <li>roomCtrlMode off|comfort|comfort-1|comfort-2|economy|buildingProtection<br>
- set pilot wire mode</li>
- <li>special repeater off|1|2<br>
- set repeater level of device (additional NodOn command)
- </li>
- </ul><br>
- [autoOffTime] = 0 s ... 0.1 s ... 6553.4 s<br>
- [delayOffTime] = 0 s ... 0.1 s ... 6553.4 s<br>
- [channel] = 0...29|all|input, all is default<br>
- The default channel can be specified with the attr <a href="#EnOcean_defaultChannel">defaultChannel</a>.<br>
- [rampTime] = 1..3|switch|stop, switch is default<br>
- The attr subType must be actuator.01. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Blind Control for Position and Angle (D2-05-00 - D2-05-01)<br>
- [AWAG Elektrotechnik AG OMNIO UPJ 230/12, REGJ12/04M ]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>opens [<channel>]<br>
- issue blinds opens command</li>
- <li>closes [<channel>]<br>
- issue blinds closes command</li>
- <li>position position/% [[α/%] [[<channel>] [directly|opens|closes]]]<br>
- drive blinds to position with angle value</li>
- <li>anglePos α/% [<channel>]<br>
- drive blinds to angle value</li>
- <li>stop [<channel>]<br>
- issue stop command</li>
- <li>alarm [<channel>]<br>
- set actuator to the "alarm" mode. When the actuator ist set to the "alarm" mode neither local
- nor central positioning and configuration commands will be executed. Before entering the "alarm"
- mode, the actuator will execute the "alarm action" as configured by the attribute <a href="#EnOcean_alarmAction">alarmAction</a>
- </li>
- <li>lock [<channel>]<br>
- set actuator to the "blockade" mode. When the actuator ist set to the "blockade" mode neither local
- nor central positioning and configuration commands will be executed.
- </li>
- <li>unlock [<channel>]<br>
- issue unlock command</li>
- </ul><br>
- Channel Range: 1 ... 4|all, default is all<br>
- Position Range: position = 0 % ... 100 %<br>
- Slat Angle Range: α = 0 % ... 100 %<br>
- The devive can only fully controlled if the attributes <a href="#EnOcean_alarmAction">alarmAction</a>,
- <a href="#angleTime">angleTime</a>, <a href="#EnOcean_reposition">reposition</a> and <a href="#shutTime">shutTime</a>
- are set correctly.<br>
- With the attribute <a name="EnOcean_defaultChannel">defaultChannel</a> the default channel can be specified.<br>
- The attr subType must be blindsCtrl.00 or blindsCtrl.01. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Multisensor Windows Handle (D2-06-01)<br>
- [Soda GmbH]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>presence absent|present<br>
- set vacation mode</li>
- <li>handleClosedClick disable|enable<br>
- set handle closed click feature</li>
- <li>batteryLowClick disable|enable<br>
- set battery low click feature</li>
- <li>teachSlave contact|windowHandleClosed|windowHandleOpen|windowHandleTilted<br>
- sent teach-in to the slave devices (contact: EEP: D5-00-01, windowHandle: EEP F6-10-00)<br>
- The events window or handle will get forwarded once a slave-device contact or windowHandle is taught in.
- </li>
- <li>updateInterval t/s<br>
- set sensor update interval</li>
- <li>blinkInterval t/s<br>
- set vacation blink interval</li>
- </ul><br>
- sensor update interval Range: updateInterval = 5 ... 65535<br>
- vacation blick interval Range: blinkInterval = 3 ... 255<br>
- The multisensor window handle is configured using the following attributes:<br>
- <ul>
- <li><a href="#EnOcean_subDefH">subDefH</a></li>
- <li><a href="#EnOcean_subDefW">subDefW</a></li>
- </ul>
- The attr subType must be multisensor.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Control Panels (D2-10-00 - D2-10-02)<br>
- [Kieback & Peter RBW322-FTL]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>buildingProtectionTemp t/°C<br>
- set building protection temperature</li>
- <li>clearCmds [<channel>]<br>
- clear waiting commands</li>
- <li>comfortTemp t/°C<br>
- set comfort temperature</li>
- <li>config<br>
- Setting the configuration of the room controller, the configuration parameters are set using attributes.</li>
- <li>cooling auto|off|on|no_change<br>
- switch cooling</li>
- <li>deleteTimeProgram<br>
- delete time programs of the room controller</li>
- <li>desired-temp t/°C<br>
- set setpoint temperature</li>
- <li>economyTemp t/°C<br>
- set economy temperature</li>
- <li>fanSpeed fanspeed/%<br>
- set fan speed</li>
- <li>fanSpeedMode central|local<br>
- set fan speed mode</li>
- <li>heating auto|off|on|no_change<br>
- switch heating</li>
- <li>preComfortTemp t/°C<br>
- set pre comfort temperature</li>
- <li>roomCtrlMode buildingProtectionTemp|comfortTemp|economyTemp|preComfortTemp<br>
- select setpoint temperature</li>
- <li>setpointTemp t/°C<br>
- set current setpoint temperature</li>
- <li>time<br>
- set time and date of the room controller </li>
- <li>timeProgram<br>
- set time programms of the room contoller</li>
- <li>window closed|open<br>
- put the window state</li>
- </ul><br>
- Setpoint Range: t = 0 °C ... 40 °C<br>
- The room controller is configured using the following attributes:<br>
- <ul>
- <li><a href="#EnOcean_blockDateTime">blockDateTime</a></li>
- <li><a href="#EnOcean_blockDisplay">blockDisplay</a></li>
- <li><a href="#EnOcean_blockFanSpeed">blockFanSpeed</a></li>
- <li><a href="#EnOcean_blockMotion">blockMotion</a></li>
- <li><a href="#EnOcean_blockProgram">blockProgram</a></li>
- <li><a href="#EnOcean_blockOccupany">blockOccupancy</a></li>
- <li><a href="#EnOcean_blockTemp">blockTemp</a></li>
- <li><a href="#EnOcean_blockTimeProgram">blockTimeProgram</a></li>
- <li><a href="#EnOcean_blockSetpointTemp">blockSetpointTemp</a></li>
- <li><a href="#EnOcean_daylightSavingTime">daylightSavingTime</a></li>
- <li><a href="#EnOcean_displayContent">displayContent</a></li>
- <li><a href="#EnOcean_pollInterval">pollInterval</a></li>
- <li><a href="#EnOcean_temperatureScale">temperatureScale</a></li>
- <li><a href="#EnOcean_timeNotation">timeNotation</a></li>
- <li><a href="#EnOcean_timeProgram[1-4]">timeProgram[1-4]</a></li>
- </ul>
- The attr subType must be roomCtrlPanel.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Room Control Panels (D2-11-01 - D2-11-08)<br>
- [Thermokon EasySens SR06 LCD-2T/-2T rh -4T/-4T rh]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>cooling on|off, default [colling] = off<br>
- set cooling symbol at the display</li>
- <li>desired-temp t/°C<br>
- set setpoint temperature</li>
- <li>fanSpeed auto|off|1|2|3<br>
- set fan speed</li>
- <li>heating on|off, default [heating] = off<br>
- set heating symbol at the display</li>
- <li>occupancy occupied|unoccupied<br>
- set occupancy state</li>
- <li>setpointTemp t/°C<br>
- set current setpoint temperature</li>
- <li>setpointShiftMax t/K<br>
- set setpoint shift max</li>
- <li>setpointType setpointTemp|setpointShift<br>
- set setpoint type</li>
- <li>window closed|open, default [window] = closed<br>
- set window open symbol at the display</li>
- </ul><br>
- Setpoint Range: t = 5 °C ... 40 °C<br>
- Setpoint Shift Max Range: t = 0 K ... 10 K<br>
- The attr subType must be roomCtrlPanel.01. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired by Smart Ack,
- see <a href="#EnOcean_smartAck">SmartAck Learning</a>.
- </li>
- <br><br>
- <li>Fan Control (D2-20-00 - D2-20-02)<br>
- [Maico ECA x RC/RCH, ER 100 RC, untested]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>on<br>
- fan on</li>
- <li>off<br>
- fan off</li>
- <li>desired-temp [t/°C]<br>
- set setpoint temperature</li>
- <li>fanSpeed fanspeed/%|auto|default<br>
- set fan speed</li>
- <li>humidityThreshold rH/%<br>
- set humidity threshold</li>
- <li>roomSize 0...350/m<sup>2</sup>|default|not_used<br>
- set room size</li>
- <li>setpointTemp [t/°C]<br>
- set current setpoint temperature</li>
- </ul><br>
- Setpoint Range: t = 0 °C ... 40 °C<br>
- The fan controller is configured using the following attributes:<br>
- <ul>
- <li><a href="#EnOcean_setCmdTrigger">setCmdTrigger</a></li>
- <li><a href="#EnOcean_switchHysteresis">switchHysteresis</a></li>
- <li><a href="#temperatureRefDev">temperatureRefDev</a></li>
- </ul>
- The attr subType must be fanCtrl.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>. The profile
- behaves like a master. Only one fan can be taught as a slave.
- </li>
- <br><br>
- <li>Heat Recovery Ventilation (D2-50-00 - D2-50-11)<br>
- [untested]<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>ventilation off|1...4|auto|demand|supplyAir|exhaustAir<br>
- select ventilation mode/level</li>
- <li>heatExchangerBypass opens|closes<br>
- override of automatic heat exchanger bypass control</li>
- <li>startTimerMode<br>
- enable timer operation mode</li>
- <li>CO2Threshold default|1/%<br>
- override CO2 threshold for CO2 control in automatic mode</li>
- <li>humidityThreshold default|rH/%<br>
- override humidity threshold for humidity control in automatic mode</li>
- <li>airQuatityThreshold default|1/%<br>
- override air qualidity threshold for air qualidity control in automatic mode</li>
- <li>roomTemp default|t/°C<br>
- override room temperature threshold for room temperature control mode</li>
- </ul><br>
- roomTemp Range: t = -63 °C ... 63 °C<br>
- xThreshold Range: 0 % ... 100 %<br>
- The attr subType must be heatRecovery.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Valve Control (EEP D2-A0-01)<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>closes<br>
- issue closes command (master)</li>
- <li>opens<br>
- issue opens command (master)</li>
- <li>closed<br>
- issue closed command (slave)</li>
- <li>open<br>
- issue open command (slave)</li>
- <li>teachIn<br>
- initiate UTE teach-in (slave)</li>
- <li>teachOut<br>
- initiate UTE teach-out (slave)</li>
- </ul><br>
- The valve controller is configured using the following attributes:<br>
- <ul>
- <li><a href="#EnOcean_devMode">devMode</a></li>
- </ul>
- The attr subType must be valveCtrl.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>. The profile
- behaves like a master or slave, see <a href="#EnOcean_devMode">devMode</a>.
- </li>
- <br><br>
- <li>Generic Profiles<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li><00 ... 64>-<channel name> <value><br>
- set channel value</li>
- <li>channelName <channel number>-<channel name><br>
- rename channel</li>
- <li>teachIn<br>
- sent teach-in telegram</li>
- <li>teachOut<br>
- sent teach-out telegram</li>
- </ul><br>
- The generic profile device is configured using the following attributes:<br>
- <ul>
- <li><a href="#EnOcean_comMode">comMode</a></li>
- <li><a href="#EnOcean_devMode">devMode</a></li>
- <li><a href="#EnOcean_gpDef">gpDef</a></li>
- <li><a href="#EnOcean_manufID">manufID</a></li>
- </ul>
- The attr subType must be genericProfile. This is done if the device was
- created by autocreate. If the profile in slave mode is operated, especially the channel
- definition in the gpDef attributes must be entered manually.
- </li>
- <br><br>
- <li>RAW Command<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>1BS|4BS|GPCD|GPSD|GPTI|GPTR|MSC|RPS|UTE|VLD data [status]<br>
- sent data telegram</li>
- </ul><br>
- [data] = <1-byte hex ... 512-byte hex><br>
- [status] = 0x00 ... 0xFF<br>
- With the help of this command data messages in hexadecimal format can be sent.
- Telegram types (RORG) 1BS, 4BS, RPS, MSC, UTE, VLD, GPCD, GPSD, GPTI and GPTR are supported.
- For further information, see <a href="http://www.enocean-alliance.org/eep/">EnOcean Equipment Profiles (EEP)</a> and
- Generic Profiles.
- </li>
- <br><br>
- <li>Radio Link Test<br>
- <ul>
- <code>set <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>standby|stop<br>
- set RLT Master state
- </li>
- </ul><br>
- The Radio Link Test device is configured using the following attributes:<br>
- <ul>
- <li><a href="#EnOcean_rltRepeat">rltRepeat</a></li>
- <li><a href="#EnOcean_rltType">rltType</a></li>
- </ul>
- The attr subType must be readioLinkTest. This is done if the device was
- created by autocreate or manually by <code>define <name> EnOcean A5-3F-00</code><br>.
- </li>
- <br><br>
- </ul></ul>
- <a name="EnOceanget"></a>
- <b>Get</b>
- <ul>
- <li><a name="EnOcean_remoteGet">Remote Management</a>
- <ul>
- <code>get <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>remoteDevCfg <start data index> <end data index><br>
- get device configuration between start index and end index</li>
- <li>remoteFunctions<br>
- get a list of the supported extended functions</li>
- <li>remoteID<br>
- get the remote device ID</li>
- <li>remoteLinkTableInfo<br>
- query supported link table info</li>
- <li>remoteLinkCfg in|out <index> <start data index> <end data index> <length><br>
- get link table between start index and end index</li>
- <li>remoteLinkTable in|out <start index> <end index><br>
- get link table between start index and end index</li>
- <li>remoteLinkTableGP in|out <index><br>
- get link table GP entry with index</li>
- <li>remotePing<br>
- get a ping response from the remote device</li>
- <li>remoteProductID<br>
- query product ID</li>
- <li>remoteRepeater<br>
- asks for the repeater status of the remote device</li>
- <li>remoteStatus<br>
- asks for the status info of the remote device</li>
- <br>
- [<data index>] = 0000...FFFF<br>
- [<index>] = 00...FF<br>
- [<length>] = n x 00...FF<br>
- </ul>
- </li><br><br>
- <li>Dual Channel Switch Actuator (EEP A5-11-059)<br>
- [untested]<br>
- <ul>
- <code>get <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>state<br>
- status request</li>
- </ul><br>
- The attr subType or subTypSet must be switch.05. This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Extended Lighting Control (EEP A5-38-09)<br>
- [untested]<br>
- <ul>
- <code>get <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>state<br>
- status request</li>
- </ul><br>
- The attr subType or subTypSet must be lightCtrl.01. This is done if the device was created by autocreate.<br>
- The subType is associated with the subtype lightCtrlState.02.
- </li>
- <br><br>
- <li>Electronic switches and dimmers with Energy Measurement and Local Control (D2-01-00 - D2-01-12)<br>
- [Telefunken Funktionsstecker, PEHA Easyclick, AWAG Elektrotechnik AG Omnio UPS 230/xx,UPD 230/xx, NodOn in-wall module, smart plug]<br>
- <ul>
- <code>get <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>roomCtrlMode<br>
- get pilot wire mode</li>
- <li>settings [<channel>]<br>
- get external interface settings</li>
- <li>state [<channel>]<br>
- </li>
- <li>measurement <channel> energy|power<br>
- </li>
- <li>special <channel> health|load|voltage|serialNumber<br>
- additional Permondo SmartPlug PSC234 commands
- </li>
- <li>special <channel> firmwareVersion|reset|taughtInDevID|taughtInDevNum<br>
- additional NodOn commands
- </li>
- </ul><br>
- The default channel can be specified with the attr <a href="#EnOcean_defaultChannel">defaultChannel</a>.<br>
- The attr subType must be actuator.01. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Blind Control for Position and Angle (D2-05-00)<br>
- [AWAG Elektrotechnik AG OMNIO UPJ 230/12, REGJ12/04M]<br>
- <ul>
- <code>get <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>position [<channel>]<br>
- query position and angle value</li>
- </ul><br>
- Channel Range: 1 ... 4|all, default is all<br>
- The devive can only fully controlled if the attributes <a href="#EnOcean_alarmAction">alarmAction</a>,
- <a href="#angleTime">angleTime</a>, <a href="#EnOcean_reposition">reposition</a> and <a href="#shutTime">shutTime</a>
- are set correctly.<br>
- With the attribute <a name="EnOcean_defaultChannel">defaultChannel</a> the default channel can be specified.<br>
- The attr subType must be blindsCtrl.00 or blindsCrtl.01. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Multisensor Windows Handle (D2-06-01)<br>
- [Soda GmbH]<br>
- <ul>
- <code>get <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>config<br>
- get configuration settings</li>
- <li>log<br>
- get log data</li>
- </ul><br>
- The multisensor window handle is configured using the following attributes:<br>
- <ul>
- <li><a href="#EnOcean_subDefH">subDefH</a></li>
- <li><a href="#EnOcean_subDefW">subDefW</a></li>
- </ul>
- The attr subType must be multisensor.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Control Panels (D2-10-00 - D2-10-02)<br>
- [Kieback & Peter RBW322-FTL]<br>
- <ul>
- <code>get <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>config<br>
- get the configuration of the room controler</li>
- <li>data<br>
- get data</li>
- <li>roomCtrl<br>
- get the parameter of the room controler</li>
- <li>timeProgram<br>
- get the time program</li>
- </ul><br>
- The attr subType must be roomCtrlPanel.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Fan Control (D2-20-00 - D2-20-02)<br>
- [Maico ECA x RC/RCH, ER 100 RC, untested]<br>
- <ul>
- <code>get <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>state<br>
- get the state of the room controler</li>
- </ul><br>
- The attr subType must be fanCtrl.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Heat Recovery Ventilation (D2-50-00 - D2-50-11)<br>
- [untested]<br>
- <ul>
- <code>get <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>basicState<br>
- get the basic state</li>
- <li>extendedState<br>
- get the extended state</li>
- </ul><br>
- The attr subType must be heatRecovery.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Valve Control (EEP D2-A0-01)<br>
- <ul>
- <code>get <name> <value></code>
- <br><br>
- where <code>value</code> is
- <li>state<br>
- get the state of the valve controler (master)</li>
- </ul><br>
- The attr subType must be valveCtrl.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>. The profile
- behaves like a master or slave, see <a href="#EnOcean_devMode">devMode</a>.
- </li>
- <br><br>
- </ul><br>
- <a name="EnOceanattr"></a>
- <b>Attributes</b>
- <ul>
- <ul>
- <li><a name="actualTemp">actualTemp</a> t/°C<br>
- The value of the actual temperature, used by a Room Sensor and Control Unit
- or when controlling HVAC components e. g. Battery Powered Actuators (MD15 devices). Should by
- filled via a notify from a distinct temperature sensor.<br>
- If absent, the reported temperature from the HVAC components is used.
- </li>
- <li><a name="EnOcean_alarmAction">alarmAction</a> <channel1>[:<channel2>[:<channel3>[:<channel4>]]]<br>
- [alarmAction] = no|stop|opens|closes, default is no<br>
- Action that is executed before the actuator is entering the "alarm" mode.<br>
- Notice subType blindsCrtl.00, blindsCrtl.01: The attribute can only be set while the actuator is online.
- </li>
- <li><a name="angleMax">angleMax</a> αs/°, [αs] = -180 ... 180, 90 is default.<br>
- Slat angle end position maximum.<br>
- angleMax is supported for shutter.
- </li>
- <li><a name="angleMin">angleMin</a> αo/°, [αo] = -180 ... 180, -90 is default.<br>
- Slat angle end position minimum.<br>
- angleMin is supported for shutter.
- </li>
- <li><a name="angleTime">angleTime</a> <channel1>[:<channel2>[:<channel3>[:<channel4>]]]<br>
- subType blindsCtrl.00, blindsCtrl.01: [angleTime] = 0|0.01 .. 2.54, 0 is default.<br>
- subType manufProfile: [angleTime] = 0 ... 6, 0 is default.<br>
- Runtime value for the sunblind reversion time. Select the time to revolve
- the sunblind from one slat angle end position to the other end position.<br>
- Notice subType blindsCrtl.00: The attribute can only be set while the actuator is online.
- </li>
- <li><a name="EnOcean_blockDateTime">blockDateTime</a> yes|no, [blockDateTime] = no is default.<br>
- blockDateTime is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_blockDisplay">blockDisplay</a> yes|no, [blockDisplay] = no is default.<br>
- blockDisplay is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_blockFanSpeed">blockFanSpeed</a> yes|no, [blockFanSpeed] = no is default.<br>
- blockFanSpeed is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_blockKey">blockKey</a> yes|no, [blockKey] = no is default.<br>
- blockKey is supported for roomCtrlPanel.00 and hvac.04.
- </li>
- <li><a name="EnOcean_blockMotion">blockMotion</a> yes|no, [blockMotion] = no is default.<br>
- blockMotion is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_blockOccupany">blockOccupancy</a> yes|no, [blockOccupancy] = no is default.<br>
- blockOccupancy is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_blockTemp">blockTemp</a> yes|no, [blockTemp] = no is default.<br>
- blockTemp is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_blockTimeProgram">blockTimeProgram</a> yes|no, [blockTimeProgram] = no is default.<br>
- blockTimeProgram is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_blockSetpointTemp">blockSetpointTemp</a> yes|no, [blockSetpointTemp] = no is default.<br>
- blockSetPointTemp is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_blockUnknownMSC">blockUnknownMSC</a> yes|no,
- [blockUnknownMSC] = no is default.<br>
- If the structure of the MSC telegrams can not interpret the raw data to be output. Setting this attribute to yes,
- the output can be suppressed.
- </li>
- <li><a name="EnOcean_comMode">comMode</a> biDir|confirm|uniDir, [comMode] = uniDir is default.<br>
- Communication Mode between an enabled EnOcean device and Fhem.<br>
- Unidirectional communication means a point-to-multipoint communication
- relationship. The EnOcean device e. g. sensors does not know the unique
- Fhem SenderID.<br>
- If the attribute is set to confirm Fhem awaits confirmation telegrams from the remote device.<br>
- Bidirectional communication means a point-to-point communication
- relationship between an enabled EnOcean device and Fhem. It requires all parties
- involved to know the unique Sender ID of their partners. Bidirectional communication
- needs a teach-in / teach-out process, see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <li><a name="EnOcean_dataEnc">dataEnc</a> VAES|AES-CBC, [dataEnc] = VAES is default<br>
- Data encryption algorithm
- </li>
- <li><a name="EnOcean_defaultChannel">defaultChannel</a> <channel>
- subType actuator.01: [defaultChannel] = all|input|0 ... 29, all is default.<br>
- subType blindsCtrl.00, blindsCtrl.01: [defaultChannel] = all|1 ... 4, all is default.<br>
- Default device channel
- </li>
- <li><a name="EnOcean_daylightSavingTime">daylightSavingTime</a> supported|not_supported, [daylightSavingTime] = supported is default.<br>
- daylightSavingTime is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_demandRespAction">demandRespAction</a> <command><br>
- Command being executed after an demand response command is set. If <command> is enclosed in {},
- then it is a perl expression, if it is enclosed in "", then it is a shell command,
- else it is a "plain" fhem.pl command (chain). In the <command> you can access the demand response
- readings $TYPE, $NAME, $LEVEL, $SETPOINT, $POWERUSAGE, $POWERUSAGESCALE, $POWERUSAGELEVEL, $STATE. In addition,
- the variables $TARGETNAME, $TARGETTYPE, $TARGETSTATE can be used if the action is executed
- on the target device. This data is available as a local variable in perl, as environment variable for shell
- scripts, and will be textually replaced for Fhem commands.
- </li>
- <li><a name="EnOcean_demandRespMax">demandRespMax</a> A0|AI|B0|BI|C0|CI|D0|DI, [demandRespMax] = B0 is default<br>
- Switch command which is executed if the demand response switches to a maximum.
- </li>
- <li><a name="EnOcean_demandRespMin">demandRespMin</a> A0|AI|B0|BI|C0|CI|D0|DI, [demandRespMax] = BI is default<br>
- Switch command which is executed if the demand response switches to a minimum.
- </li>
- <li><a name="EnOcean_demandRespRefDev">demandRespRefDev</a> <name><br>
- </li>
- <li><a name="EnOcean_demandRespRandomTime">demandRespRandomTime</a> t/s [demandRespRandomTime] = 1 is default<br>
- Maximum length of the random delay at the start or end of a demand respose event in slave mode.
- </li>
- <li><a name="EnOcean_demandRespThreshold">demandRespThreshold</a> 0...15 [demandRespTheshold] = 8 is default<br>
- Threshold for switching the power usage level between minimum and maximum in the master mode.
- </li>
- <li><a name="EnOcean_demandRespTimeoutLevel">demandRespTimeoutLevel</a> max|last [demandRespTimeoutLevel] = max is default<br>
- Demand response timeout level in slave mode.
- </li>
- <li><a name="devChannel">devChannel</a> 00 ... FF, [devChannel] = FF is default<br>
- Number of the individual device channel, FF = all channels supported by the device
- </li>
- <li><a name="destinationID">destinationID</a> multicast|unicast|00000001 ... FFFFFFFF,
- [destinationID] = multicast is default<br>
- Destination ID, special values: multicast = FFFFFFFF, unicast = [DEF]
- </li>
- <li><a name="EnOcean_devMode">devMode</a> master|slave, [devMode] = master is default.<br>
- device operation mode.
- </li>
- <li><a href="#devStateIcon">devStateIcon</a></li>
- <li><a name="EnOcean_dimMax">dimMax</a> dim/%|off, [dimMax] = 255 is default.<br>
- maximum brightness value<br>
- dimMax is supported for the profile gateway/dimming.
- </li>
- <li><a name="EnOcean_dimMin">dimMin</a> dim/%|off, [dimMax] = off is default.<br>
- minimum brightness value<br>
- If [dimMax] = off, then the actuator takes down the ramp time set there.
- dimMin is supported for the profile gateway/dimming.
- </li>
- <li><a name="dimValueOn">dimValueOn</a> dim/%|last|stored,
- [dimValueOn] = 100 is default.<br>
- Dim value for the command "on".<br>
- The dimmer switched on with the value 1 % ... 100 % if [dimValueOn] =
- 1 ... 100.<br>
- The dimmer switched to the last dim value received from the
- bidirectional dimmer if [dimValueOn] = last.<br>
- The dimmer switched to the last Fhem dim value if [dimValueOn] =
- stored.<br>
- dimValueOn is supported for the profile gateway/dimming.
- </li>
- <li><a href="#EnOcean_disable">disable</a> 0|1<br>
- If applied set commands will not be executed.
- </li>
- <li><a href="#EnOcean_disabledForIntervals">disabledForIntervals</a> HH:MM-HH:MM HH:MM-HH-MM...<br>
- Space separated list of HH:MM tupels. If the current time is between
- the two time specifications, set commands will not be executed. Instead of
- HH:MM you can also specify HH or HH:MM:SS. To specify an interval
- spawning midnight, you have to specify two intervals, e.g.:
- <ul>
- 23:00-24:00 00:00-01:00
- </ul>
- </li>
- <li><a name="EnOcean_displayContent">displayContent</a>
- humidity|off|setpointTemp|temperatureExtern|temperatureIntern|time|default|no_change, [displayContent] = no_change is default.<br>
- displayContent is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_displayOrientation">displayOrientation</a> rad/°, [displayOrientation] = 0|90|180|270, 0 is default.<br>
- Display orientation of the actuator
- </li>
- <li><a href="#do_not_notify">do_not_notify</a></li>
- <li><a name="EnOcean_eep">eep</a> <00...FF>-<00...3F>-<00...7F><br>
- EnOcean Equipment Profile (EEP)
- <li><a href="#eventMap">eventMap</a></li>
- </li>
- <li><a name="EnOcean_gpDef">gpDef</a> <name of channel 00>:<O|I>:<channel type>:<signal type>:<value type>[:<resolution>[:<engineering min>:<scaling min>:<engineering max>:<scaling max>]] ...
- <name of channel 64>:<O|I>:<channel type>:<signal type>:<value type>[:<resolution>[:<engineering min>:<scaling min>:<engineering max>:<scaling max>]]
- <br>
- Generic Profiles channel definitions are set automatically in master mode. If the profile in slave mode is operated, the channel
- definition must be entered manually. For each channel, the channel definitions are to be given in ascending order. The channel
- parameters to be specified in decimal. First, the outgoing channels (direction = O) are to be defined, then the incoming channels
- (direction = I) should be described. The channel numbers are assigned automatically starting with 00th.
- </li>
- <li><a name="gwCmd">gwCmd</a> switching|dimming|setpointShift|setpointBasic|controlVar|fanStage|blindCmd<br>
- Gateway Command Type, see <a href="#Gateway">Gateway</a> profile
- </li>
- <li><a name="EnOcean_humidity">humidity</a> rH/%<br>
- The value of the actual humidity, used by a Room Sensor and Control Unit. Should by
- filled via a notify from a distinct humidity sensor.
- </li>
- <li><a name="EnOcean_humidityRefDev">humidityRefDev</a> <name><br>
- Name of the device whose reference value is read. The reference values is
- the reading humidity.
- </li>
- <li><a href="#ignore">ignore</a></li>
- <li><a href="#IODev">IODev</a></li>
- <li><a name="EnOcean_keyRcv">keyRcv</a> <private key 16 byte hex><br>
- Private Key for receive direction
- </li>
- <li><a name="EnOcean_keySnd">keySnd</a> <private key 16 byte hex><br>
- Private Key for send direction
- </li>
- <li><a name="EnOcean_macAlgo">macAlgo</a> no|3|4<br>
- MAC Algorithm
- </li>
- <li><a name="EnOcean_manufID">manufID</a> <000 ... 7FF><br>
- Manufacturer ID number
- </li>
- <li><a name="EnOcean_measurementCtrl">measurementCtrl</a> enable|disable<br>
- Enable or disable the temperature measurements (room and feed temperature) of the actuator. If the temperature
- measurements are turned off, an external temperature sensor must be exists, see attribute
- <a href="#temperatureRefDev">temperatureRefDev</a>.
- </li>
- <li><a href="#model">model</a></li>
- <li><a name="EnOcean_observe">observe</a> off|on, [observe] = off is default.<br>
- Observing and repeating the execution of set commands
- </li>
- <li><a name="EnOcean_observeCmdRepetition">observeCmdRepetition</a> 1..5, [observeCmdRepetition] = 2 is default.<br>
- Maximum number of command retries
- </li>
- <li><a name="EnOcean_observeErrorAction">observeErrorAction</a> <command><br>
- Command being executed after an error. If <command> is enclosed in {},
- then it is a perl expression, if it is enclosed in "", then it is a shell command,
- else it is a "plain" fhem.pl command (chain). In the <command> you can access the set
- command. $TYPE, $NAME, $FAILEDDEV, $EVENT, $EVTPART0, $EVTPART1, $EVTPART2, etc. contains the space separated
- set parts. The <a href="#eventMap">eventMap</a> replacements are taken into account. This data
- is available as a local variable in perl, as environment variable for shell
- scripts, and will be textually replaced for Fhem commands.
- </li>
- <li><a name="EnOcean_observeInterval">observeInterval</a> 1/s ... 255/s, [observeInterval] = 1 is default.<br>
- Interval between two observations
- </li>
- <li><a name="EnOcean_observeLogic">observeLogic</a> and|or, [observeLogic] = or is default.<br>
- Observe logic
- </li>
- <li><a name="EnOcean_observeRefDev">observeRefDev</a> <name> [<name> [<name>]],
- [observeRefDev] = <name of the own device> is default<br>
- Names of the devices to be observed. The list must be separated by spaces.
- </li>
- <li><a name="EnOcean_pidActorCallBeforeSetting">pidActorCallBeforeSetting</a>,
- [pidActorCallBeforeSetting] = not defined is default<br>
- Callback-function, which can manipulate the actorValue. Further information see modul PID20.
- </li>
- <li><a name="EnOcean_pidActorErrorAction">pidActorErrorAction</a> freeze|errorPos,
- [pidActorErrorAction] = freeze is default<br>
- required action on error
- </li>
- <li><a name="EnOcean_pidActorErrorPos">pidActorErrorPos</a> valvePos/%,
- [pidActorErrorPos] = 0...100, 0 is default<br>
- actor's position to be used in case of error
- </li>
- <li><a name="EnOcean_pidActorLimitLower">pidActorLimitLower</a> valvePos/%,
- [pidActorLimitLower] = 0...100, 0 is default<br>
- lower limit for actor
- </li>
- <li><a name="EnOcean_pidActorLimitUpper">pidActorLimitUpper</a> valvePos/%,
- [pidActorLimitUpper] = 0...100, 100 is default<br>
- upper limit for actor
- </li>
- <li><a name="EnOcean_pidCtrl">pidCtrl</a> on|off,
- [pidCtrl] = on is default<br>
- Activate the Fhem PID regulator
- </li>
- <li><a name="EnOcean_pidDeltaTreshold">pidDeltaTreshold</a> <floating-point number>,
- [pidDeltaTreshold] = 0 is default<br>
- if delta < delta-threshold the pid will enter idle state
- </li>
- <li><a name="EnOcean_pidFactor_P">pidFactor_P</a> <floating-point number>,
- [pidFactor_P] = 25 is default<br>
- P value for PID
- </li>
- <li><a name="EnOcean_pidFactor_I">pidFactor_I</a> <floating-point number>,
- [pidFactor_I] = 0.25 is default<br>
- I value for PID
- </li>
- <li><a name="EnOcean_pidFactor_D">pidFactor_D</a> <floating-point number>,
- [pidFactor_D] = 0 is default<br>
- D value for PID
- </li>
- <li><a name="EnOcean_pidIPortionCallBeforeSetting">pidIPortionCallBeforeSetting</a>
- [pidIPortionCallBeforeSetting] = not defined is default<br>
- Callback-function, which can manipulate the value of I-Portion. Further information see modul PID20.
- </li>
- <li><a name="EnOcean_pidSensorTimeout">pidSensorTimeout t/s</a>
- [pidSensorTimeout] = 3600 is default<br>
- number of seconds to wait before sensor <a href="#temperatureRefDev">temperatureRefDev</a> will be recognized n/a
- </li>
- <li><a name="EnOcean_pollInterval">pollInterval</a> t/s, [pollInterval] = 10 is default.<br>
- [pollInterval] = 1 ... 1440.<br>
- pollInterval is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_rampTime">rampTime</a> t/s or relative, [rampTime] = 1 is default.<br>
- No ramping or for Eltako dimming speed set on the dimmer if [rampTime] = 0.<br>
- Gateway/dimmung: Ramping time 1 s to 255 s or relative fast to low dimming speed if [rampTime] = 1 ... 255.<br>
- lightCtrl.01: Ramping time 1 s to 65535 s<br>
- rampTime is supported for gateway, command dimming and lightCtrl.01.
- </li>
- <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
- <li><a name="EnOcean_rcvRespAction">rcvRespAction</a> <command><br>
- Command being executed after an message from the aktor is received and before an response message is sent.
- If <command> is enclosed in {}, then it is a perl expression, if it is enclosed in "", then it is a shell command,
- else it is a "plain" fhem.pl command (chain). In the <command> you can access the name of the device by using $NAME
- and the current readings $ACTUATORSTATE, $BATTERY, $COVER, $ENERGYINPUT, $ENERGYSTORAGE, $MAINTENANCEMODE, $OPERATIONMODE,
- $ROOMTEMP, $SELFCTRL, $SETPOINT, $SETPOINTTEMP, $SUMMERMODE, $TEMPERATURE, $WINDOW for the subType hvac.01 and $NAME,
- $BATTERY, $FEEDTEMP, $MAINTENANCEMODE, $OPERATIONMODE, $ROOMTEMP, $SETPOINT, $SETPOINTTEMP, $SUMMERMODE, $TEMPERATURE
- for the subType hvac.04.
- This data is available as a local variable in perl, as environment variable for shell
- scripts, and will be textually replaced for Fhem commands.
- </li>
- <li><a name="EnOcean_remoteCode">remoteCode</a> <00000000...FFFFFFFE><br>
- Remote Management Security Code, 00000000 is interpreted as on code has been set.
- </li>
- <li><a name="EnOcean_remoteEEP">remoteEEP</a> <00...FF>-<00...3F>-<00...7F><br>
- Remote Management EnOcean Equipment Profile (EEP)
- </li>
- <li><a name="EnOcean_remoteID">remoteID</a> <00000001...FFFFFFFE><br>
- Remote Management Remote Device ID
- </li>
- <li><a name="EnOcean_remoteManagement">remoteManagement</a> client|manager|off,
- [remoteManagement] = off is default.<br>
- Enable Remote Management for the device.
- </li>
- <li><a name="EnOcean_remoteManufID">remoteManufID</a> <000...7FF><br>
- Remote Management Manufacturer ID
- </li>
- <li><a name="repeatingAllowed">repeatingAllowed</a> yes|no,
- [repeatingAllowed] = yes is default.<br>
- EnOcean Repeater in the transmission range of Fhem may forward data messages
- of the device, if the attribute is set to yes.
- </li>
- <li><a name="EnOcean_releasedChannel">releasedChannel</a> A|B|C|D|I|0|auto, [releasedChannel] = auto is default.<br>
- Attribute releasedChannel determines via which SenderID (subDefA ... subDef0) the command released is sent.
- If [releasedChannel] = auto, the SenderID the last command A0, AI, B0, BI, C0, CI, D0 or DI is used.
- Attribute releasedChannel is supported for attr switchType = central and attr switchType = channel.
- </li>
- <li><a name="EnOcean_reposition">reposition</a> directly|opens|closes, [reposition] = directly is default.<br>
- Attribute reposition specifies how to adjust the internal positioning tracker before going to the new position.
- </li>
- <li><a name="EnOcean_rlcAlgo">rlcAlgo</a> 2++|3++<br>
- RLC Algorithm
- </li>
- <li><a name="EnOcean_rlcRcv">rlcRcv</a> <rolling code 2 or 3 byte hex><br>
- Rolling Code for receive direction
- </li>
- <li><a name="EnOcean_rlcSnd">rlcSnd</a> <rolling code 2 or 3 byte hex><br>
- Rolling Code for send direction
- </li>
- <li><a name="EnOcean_rlcTX">rlcTX</a> false|true<br>
- Rolling Code is expected in the received telegram
- </li>
- <li><a name="EnOcean_rltRepeat">rltRepeat</a> 16|32|64|128|256,
- [rltRepeat] = 16 is default.<br>
- Number of RLT MasterTest messages sent
- </li>
- <li><a name="EnOcean_rltType">rltType</a> 1BS|4BS,
- [rltType] = 4BS is default.<br>
- Type of RLT MasterTest message
- </li>
- <li><a name="scaleDecimals">scaleDecimals</a> 0 ... 9<br>
- Decimal rounding with x digits of the scaled reading setpoint
- </li>
- <li><a name="EnOcean_teachMethod">teachMethod</a> 1BS|4B|confirm|GP|RPS|smartAck|STE|UTE<br>
- teach-in method
- </li>
- <li><a name="scaleMax">scaleMax</a> <floating-point number><br>
- Scaled maximum value of the reading setpoint
- </li>
- <li><a name="scaleMin">scaleMin</a> <floating-point number><br>
- Scaled minimum value of the reading setpoint
- </li>
- <li><a name="EnOcean_secLevel">secLevel</a> encapsulation|encryption|off, [secLevel] = off is default<br>
- Security level of the data
- </li>
- <li><a name="EnOcean_secMode">secMode</a> rcv|snd|bidir<br>
- Telegram direction, which is secured
- </li>
- <li><a name="EnOcean_sendDevStatus">sendDevStatus</a> no|yes, [sendDevStatus] = no is default.<br>
- Send new status of the device.
- </li>
- <li><a name="sensorMode">sensorMode</a> switch|pushbutton,
- [sensorMode] = switch is default.<br>
- The status "released" will be shown in the reading state if the
- attribute is set to "pushbutton".
- </li>
- <li><a name="EnOcean_serviceOn">serviceOn</a> no|yes,
- [serviceOn] = no is default.<br>
- Device in Service Mode.
- </li>
- <li><a name="EnOcean_setCmdTrigger">setCmdTrigger</a> man|refDev, [setCmdTrigger] = man is default.<br>
- Operation mode to send set commands<br>
- If the attribute is set to "refDev", a device-specific set command is sent when the reference device is updated.
- For the subType "roomSensorControl.05" and "fanCrtl.00" the reference "temperatureRefDev" is supported.<br>
- For the subType "roomSensorControl.01" the references "humidityRefDev" and "temperatureRefDev" are supported.<br>
- </li>
- <li><a name="EnOcean_setpointRefDev">setpointRefDev</a> <name><br>
- Name of the device whose reference value is read. The reference values is
- the reading setpoint.
- </li>
- <li><a name="EnOcean_setpointSummerMode">setpointSummerMode</a> valvePos/%,
- [setpointSummerMode] = 0...100, 0 is default<br>
- Valve position in summer operation
- </li>
- <li><a name="EnOcean_setpointTempRefDev">setpointTempRefDev</a> <name><br>
- Name of the device whose reference value is read. The reference values is
- the reading setpointTemp.
- </li>
- <li><a name="EnOcean_settingAccuracy">settingAccuracy</a> high|low,
- [settingAccuracy] = low is default.<br>
- set setting accurancy.
- </li>
- <li><a href="#showtime">showtime</a></li>
- <li><a name="shutTime">shutTime</a> <channel1>[:<channel2>[:<channel3>[:<channel4>]]]<br>
- subType blindsCtrl.00, blindsCtrl.01: [shutTime] = 5 ... 300, 300 is default.<br>
- subType manufProfile: [shutTime] = 1 ... 255, 255 is default.<br>
- Use the attr shutTime to set the time delay to the position "Halt" in
- seconds. Select a delay time that is at least as long as the shading element
- or roller shutter needs to move from its end position to the other position.<br>
- Notice subType blindsCrtl.00: The attribute can only be set while the actuator is online.
- </li>
- <li><a name="shutTimeCloses">shutTimeCloses</a> t/s, [shutTimeCloses] = 1 ... 255,
- [shutTimeCloses] = [shutTime] is default.<br>
- Set the attr shutTimeCloses to define the runtime used by the commands opens and closes.
- Select a runtime that is at least as long as the value set by the delay switch of the actuator.
- <br>
- shutTimeCloses is supported for shutter.
- </li>
- <li><a name="subDef">subDef</a> <EnOcean SenderID>,
- [subDef] = [DEF] is default.<br>
- SenderID (<a href="#TCM">TCM</a> BaseID + offset) to control a bidirectional switch or actor.<br>
- In order to control devices that send acknowledge telegrams, you cannot reuse the ID of this
- devices, instead you have to create your own, which must be in the
- allowed ID-Range of the underlying IO device. For this first query the
- <a href="#TCM">TCM</a> with the "<code>get <tcm> idbase</code>" command. You can use
- up to 128 IDs starting with the base shown there.<br>
- If [subDef] = getNextID FHEM can assign a free SenderID alternatively. The system configuration
- needs to be reloaded. The assigned SenderID will only displayed after the system configuration
- has been reloaded, e.g. Fhem command rereadcfg.
- </li>
- <li><a name="subDefA">subDefA</a> <EnOcean SenderID>,
- [subDefA] = [subDef] is default.<br>
- SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = A0|AI|released<br>
- Used with switch type "channel". Set attr switchType to channel.<br>
- subDefA is supported for switches.<br>
- Second action is not sent.<br>
- If [subDefA] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
- displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
- </li>
- <li><a name="subDefB">subDefB</a> <EnOcean SenderID>,
- [subDefB] = [subDef] is default.<br>
- SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = B0|BI|released<br>
- Used with switch type "channel". Set attr switchType to channel.<br>
- subDefB is supported for switches.<br>
- Second action is not sent.<br>
- If [subDefB] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
- displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
- </li>
- <li><a name="subDefC">subDefC</a> <EnOcean SenderID>,
- [subDefC] = [subDef] is default.<br>
- SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = C0|CI|released<br>
- Used with switch type "channel". Set attr switchType to channel.<br>
- subDefC is supported for switches.<br>
- Second action is not sent.<br>
- If [subDefC] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
- displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
- </li>
- <li><a name="subDefD">subDefD</a> <EnOcean SenderID>,
- [subDefD] = [subDef] is default.<br>
- SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = D0|DI|released<br>
- Used with switch type "channel". Set attr switchType to channel.<br>
- subDefD is supported for switches.<br>
- Second action is not sent.<br>
- If [subDefD] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
- displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
- </li>
- <li><a name="subDef0">subDef0</a> <EnOcean SenderID>,
- [subDef0] = [subDef] is default.<br>
- SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = A0|B0|C0|D0|released<br>
- Used with switch type "central". Set attr switchType to central.<br>
- Use the sensor type "zentral aus/ein" for Eltako devices.<br>
- subDef0 is supported for switches.<br>
- Second action is not sent.<br>
- If [subDef0] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
- displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
- </li>
- <li><a name="subDefI">subDefI</a> <EnOcean SenderID>,
- [subDefI] = [subDef] is default.<br>
- SenderID (<a href="#TCM">TCM</a> BaseID + offset) for [value] = AI|BI|CI|DI<br>
- Used with switch type "central". Set attr switchType to central.<br>
- Use the sensor type "zentral aus/ein" for Eltako devices.<br>
- subDefI is supported for switches.<br>
- Second action is not sent.<br>
- If [subDefI] = getNextID FHEM can assign a free SenderID alternatively. The assigned SenderID will only
- displayed after the system configuration has been reloaded, e.g. Fhem command rereadcfg.
- </li>
- <li><a name="EnOcean_subDefH">subDefH</a> <EnOcean SenderID>,
- [subDefH] = undef is default.<br>
- SenderID (<a href="#TCM">TCM</a> BaseID + offset)<br>
- Used with subType "multisensor.00". If the attribute subDefH is set, the position of the window handle as EEP F6-10-00
- (windowHandle) telegram is forwarded.<br>
- If [subDefH] = getNextID FHEM can assign a free SenderID alternatively.
- </li>
- <li><a name="EnOcean_subDefW">subDefW</a> <EnOcean SenderID>,
- [subDefW] = undef is default.<br>
- SenderID (<a href="#TCM">TCM</a> BaseID + offset)<br>
- Used with subType "multisensor.00". If the attribute subDefW is set, the window state as EEP D5-00-01
- (contact) telegram is forwarded.<br>
- If [subDefW] = getNextID FHEM can assign a free SenderID alternatively.
- </li>
- <li><a href="#subType">subType</a></li>
- <li><a name="subTypeSet">subTypeSet</a> <type of device>, [subTypeSet] = [subType] is default.<br>
- Type of device (EEP Profile) used for sending commands. Set the Attribute manually.
- The profile has to fit their basic profile. More information can be found in the basic profiles.
- </li>
- <li><a name="EnOcean_summerMode">summerMode</a> off|on,
- [summerMode] = off is default.<br>
- Put Battery Powered Actuator (hvac.01) or Heating Radiator Actuating Drive (hvac.04) in summer operation
- to reduce energy consumption. If [summerMode] = on, the set commands are not executed.
- </li>
- <li><a name="EnOcean_switchHysteresis">switchHysteresis</a> <value>,
- [switchHysteresis] = 1 is default.<br>
- Switch Hysteresis
- </li>
- <li><a name="switchMode">switchMode</a> switch|pushbutton,
- [switchMode] = switch is default.<br>
- The set command "released" immediately after <value> is sent if the
- attribute is set to "pushbutton".
- </li>
- <li><a name="switchType">switchType</a> direction|universal|central|channel,
- [switchType] = direction is default.<br>
- EnOcean Devices support different types of sensors, e. g. direction
- switch, universal switch or pushbutton, central on/off.<br>
- For Eltako devices these are the sensor types "Richtungstaster",
- "Universalschalter" or "Universaltaster", "Zentral aus/ein".<br>
- With the sensor type <code>direction</code> switch on/off commands are
- accepted, e. g. B0, BI, released. Fhem can control an device with this
- sensor type unique. This is the default function and should be
- preferred.<br>
- Some devices only support the <code>universal switch
- </code> or <code>pushbutton</code>. With a Fhem command, for example,
- B0 or BI is switched between two states. In this case Fhem cannot
- control this device unique. But if the Attribute <code>switchType
- </code> is set to <code>universal</code> Fhem synchronized with
- a bidirectional device and normal on/off commands can be used.
- If the bidirectional device response with the channel B
- confirmation telegrams also B0 and BI commands are to be sent,
- e g. channel A with A0 and AI. Also note that confirmation telegrams
- needs to be sent.<br>
- Partly for the switchType <code>central</code> two different SenderID
- are required. In this case set the Attribute <code>switchType</code> to
- <code>central</code> and define the Attributes
- <a href="#subDef0">subDef0</a> and <a href="#subDefI">subDefI</a>.<br>
- Furthermore, SenderIDs can be used depending on the channel A, B, C or D.
- In this case set the Attribute switchType to <code>channel</code> and define
- the Attributes <a href="#subDefA">subDefA</a>, <a href="#subDefB">subDefB</a>,
- <a href="#subDefC">subDefC</a>, or <a href="#subDefD">subDefD</a>.
- </li>
- <li><a name="temperatureRefDev">temperatureRefDev</a> <name><br>
- Name of the device whose reference value is read. The reference values is
- the reading temperature.
- </li>
- <li><a name="EnOcean_temperatureScale">temperatureScale</a> F|C|default|no_change, [temperatureScale] = no_change is default.<br>
- temperatureScale is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_timeNotation">timeNotation</a> 12|24|default|no_change, [timeNotation] = no_change is default.<br>
- timeNotation is supported for roomCtrlPanel.00.
- </li>
- <li><a name="EnOcean_timeProgram[1-4]">timeProgram[1-4]</a> <period> <starttime> <endtime> <roomCtrlMode>, [timeProgam[1-4]] = <none> is default.<br>
- [period] = FrMo|FrSu|ThFr|WeFr|TuTh|MoWe|SaSu|MoFr|MoSu|Su|Sa|Fr|Th|We|Tu|Mo<br>
- [starttime] = [00..23]:[00|15|30|45]<br>
- [endtime] = [00..23]:[00|15|30|45]<br>
- [roomCtrlMode] = buildingProtection|comfort|economy|preComfort<br>
- The Room Control Panel Kieback & Peter RBW322-FTL supports only [roomCtrlMode] = comfort.<br>
- timeProgram is supported for roomCtrlPanel.00.
- </li>
- <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>
- Transmission cycle of the tracker.
- </li>
- <li><a name="EnOcean_updateState">updateState</a> default|yes|no, [updateState] = default is default.<br>
- update reading state after set commands
- </li>
- <li><a name="EnOcean_uteResponseRequest">uteResponseRequest</a> yes|no<br>
- request UTE teach-in/teach-out response message, the standard value depends on the EEP profil
- </li>
- <li><a href="#verbose">verbose</a></li>
- <li><a name="EnOcean_wakeUpCycle">wakeUpCycle</a> t/s, [wakeUpCycle] = 10 s ... 151200 s, 300 s is default.<br>
- Transmission cycle of the actuator.
- </li>
- <li><a href="#webCmd">webCmd</a></li>
- </ul>
- </ul>
- <br>
- <a name="EnOceanevents"></a>
- <b>Generated events</b>
- <ul>
- <ul>
- <li><a name="EnOcean_remoteEvents">Remote Management</a><br>
- <ul>
- <li>remoteDevCfg<0000...FFFF>: <device config></li>
- <li>remoteFunction<01...99>: <remote function number>:<remote manufacturer ID>:<explanation></li>
- <li>remoteLastFunctionNumber: 001...FFF</li>
- <li>remoteLastStatusReturnCode: 00...FF</li>
- <li>remoteLearn: not_supported|supported</li>
- <li>remoteLinkCfg<in|out><00...FF>: <data index>:<device config></li>
- <li>remoteLinkTableDesc<in|out><00...FF>: <DeviceID>:<EEP>:<channel></li>
- <li>remoteLinkTableGPDesc<in|out><00...FF>: <name of channel 00>:<O|I>:<channel type>:<signal type>:<value type>[:<resolution>[:<engineering min>:<scaling min>:<engineering max>:<scaling max>]]</li>
- <li>remoteProductID: 00000000...FFFFFFFF</li>
- <li>remoteRepeaterFilter: AND|OR</li>
- <li>remoteRepeaterFunction: on|off|filter</li>
- <li>remoteRepeaterLevel: 1|2</li>
- <li>remoteTeach: not_supported|supported</li>
- <li>remoteRSSI: LP/dBm</li>
- <li>teach: <result of teach procedure></li>
- </ul>
- </li>
- <br><br>
- <li>Switch (EEP F6-02-01 ... F6-03-02)<br>
- <ul>
- <li>A0</li>
- <li>AI</li>
- <li>B0</li>
- <li>BI</li>
- <li>C0</li>
- <li>CI</li>
- <li>D0</li>
- <li>DI</li>
- <li><BtnX,BtnY> First and second action where BtnX and BtnY is
- one of the above, e.g. A0 BI or D0 CI</li>
- <li>buttons: pressed|released</li>
- <li>state: <BtnX>[,<BtnY>]</li>
- </ul><br>
- Switches (remote controls) or actors with more than one
- (pair) keys may have multiple channels e. g. B0/BI, A0/AI with one
- SenderID or with separate addresses.
- </li>
- <br><br>
- <li>Pushbutton Switch, Pushbutton Input Module (EEP F6-02-01 ... F6-02-02, F6-01-01)<br>
- [Eltako FT55, FSM12, FSM61, FTS12]<br>
- <ul>
- <li>A0</li>
- <li>AI</li>
- <li>B0</li>
- <li>BI</li>
- <li>C0</li>
- <li>CI</li>
- <li>D0</li>
- <li>DI</li>
- <li><BtnX,BtnY> First and second action where BtnX and BtnY is
- one of the above, e.g. A0,BI or D0,CI</li>
- <li>released</li>
- <li>buttons: pressed|released</li>
- <li>state: <BtnX>[,<BtnY>] [released]</li>
- </ul><br>
- The status of the device may become "released", this is not the case for a normal switch.<br>
- Set attr model to FT55|FSM12|FSM61|FTS12 or attr sensorMode to pushbutton manually.
- </li>
- <br><br>
- <li>Pushbutton Switch (EEP F6-3F-7F)<br>
- [Eltako FGW14/FAM14 with internal decryption and RS-485 communication]<br>
- <ul>
- <li>A0</li>
- <li>AI</li>
- <li>B0</li>
- <li>BI</li>
- <li>C0</li>
- <li>CI</li>
- <li>D0</li>
- <li>DI</li>
- <li><BtnX,BtnY> First and second action where BtnX and BtnY is
- one of the above, e.g. A0,BI or D0,CI</li>
- <li>released</li>
- <li>buttons: pressed|released</li>
- <li>state: <BtnX>[,<BtnY>] [released]</li>
- </ul><br>
- Set attr subType to switch.7F and manufID to 00D.<br>
- The status of the device may become "released", this is not the case for
- a normal switch. Set attr sensorMode to pushbutton manually.
- </li>
- <br><br>
- <li>Pushbutton Switch (EEP D2-03-00)<br>
- [EnOcean PTM 215 Modul]<br>
- <ul>
- <li>A0</li>
- <li>AI</li>
- <li>B0</li>
- <li>BI</li>
- <li><BtnX,BtnY> First and second action where BtnX and BtnY is
- one of the above, e.g. A0,BI</li>
- <li>pressed</li>
- <li>released</li>
- <li>teach: <result of teach procedure></li>
- <li>energyBow: pressed|released</li>
- <li>state: <BtnX>|<BtnX>,<BtnY>|released|pressed|teachIn|teachOut</li>
- </ul><br>
- The attr subType must be switch.00. This is done if the device was
- created by autocreate. Set attr sensorMode to pushbutton manually if needed.
- </li>
- <br><br>
- <li>Heating/Cooling Relay (EEP F6-02-01 ... F6-02-02)<br>
- [Eltako FAE14, FHK14, untested]<br>
- <ul>
- <li>controllerMode: auto|off</li>
- <li>energyHoldOff: normal|holdoff</li>
- <li>buttons: pressed|released</li>
- </ul><br>
- Set attr subType to switch and model to FAE14|FHK14 manually. In addition
- every telegram received from a teached-in temperature sensor (e.g. FTR55H)
- is repeated as a confirmation telegram from the Heating/Cooling Relay
- FAE14, FHK14. In this case set attr subType to e. g. roomSensorControl.05
- and attr manufID to 00D.
- </li>
- <br><br>
- <li>Key Card Activated Switch (EEP F6-04-01)<br>
- [Eltako FKC, FKF, FZS, untested]<br>
- <ul>
- <li>keycard_inserted</li>
- <li>keycard_removed</li>
- <li>state: keycard_inserted|keycard_removed</li>
- </ul><br>
- Set attr subType to keycard manually.
- </li>
- <br><br>
- <li>Wind Speed Threshold Detector (EEP F6-05-00)<br>
- <ul>
- <li>on</li>
- <li>off</li>
- <li>alarm: dead_sensor|off</li>
- <li>windSpeed: dead_sensor|on|off</li>
- <li>battery: low|ok</li>
- <li>state: on|off</li>
- </ul><br>
- Set attr subType to windSpeed.00 manually.
- </li>
- <br><br>
- <li>Liquid Leakage Sensor (EEP F6-05-01)<br>
- [untested]<br>
- <ul>
- <li>dry</li>
- <li>wet</li>
- <li>state: dry|wet</li>
- </ul><br>
- Set attr subType to liquidLeakage manually.
- </li>
- <br><br>
- <li>Smoke Detector (EEP F6-05-02)<br>
- [Eltako FRW]<br>
- <ul>
- <li>smoke-alarm</li>
- <li>off</li>
- <li>alarm: dead_sensor|smoke-alarm|off</li>
- <li>battery: low|ok</li>
- <li>state: smoke-alarm|off</li>
- </ul><br>
- Set attr subType to smokeDetector.02 manually.
- </li>
- <br><br>
- <li>Window Handle (EEP F6-10-00, D2-03-10)<br>
- [HOPPE SecuSignal, Eltako FHF, Eltako FTKE]<br>
- <ul>
- <li>closed</li>
- <li>open</li>
- <li>tilted</li>
- <li>open_from_tilted</li>
- <li>state: closed|open|tilted|open_from_tilted</li>
- </ul><br>
- The device windowHandle or windowHandle.10 should be created by autocreate.
- </li>
- <br><br>
- <li>Single Input Contact, Door/Window Contact<br>
- 1BS Telegram (EEP D5-00-01)<br>
- [EnOcean STM 320, STM 329, STM 250, Eltako FTK, Peha D 450 FU]
- <ul>
- <li>closed</li>
- <li>open</li>
- <li>teach: <result of teach procedure></li>
- <li>state: open|closed</li>
- </ul></li>
- The device should be created by autocreate.
- <br><br>
- <li>Temperature Sensors with with different ranges (EEP A5-02-01 ... A5-02-30)<br>
- [EnOcean STM 330, Eltako FTF55, Thermokon SR65 ...]<br>
- <ul>
- <li>t/°C</li>
- <li>temperature: t/°C (Sensor Range: t = <t min> °C ... <t max> °C)</li>
- <li>state: t/°C</li>
- </ul><br>
- The attr subType must be tempSensor.01 ... tempSensor.30. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Temperatur and Humidity Sensor (EEP A5-04-02)<br>
- [Eltako FAFT60, FIFT63AP]<br>
- <ul>
- <li>T: t/°C H: rH/% B: unknown|low|ok</li>
- <li>battery: unknown|low|ok</li>
- <li>energyStorage: unknown|empty|charged|full</li>
- <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
- <li>temperature: t/°C (Sensor Range: t = -20 °C ... 60 °C)</li>
- <li>voltage: U/V</li> (Sensor Range: U = 0 V ... 6.6 V)
- <li>state: T: t/°C H: rH/% B: unknown|low|ok</li>
- </ul><br>
- The attr subType must be tempHumiSensor.02 and attr
- manufID must be 00D for Eltako Devices. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Temperatur and Humidity Sensor (EEP A5-04-03)<br>
- [untsted]<br>
- <ul>
- <li>T: t/°C H: rH/%</li>
- <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
- <li>telegramType: heartbeat|event</li>
- <li>temperature: t/°C (Sensor Range: t = -20 °C ... 60 °C)</li>
- <li>state: T: t/°C H: rH/%</li>
- </ul><br>
- The attr subType must be tempHumiSensor.03. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Barometric Sensor (EEP A5-05-01)<br>
- [untested]<br>
- <ul>
- <li>P/hPa</li>
- <li>airPressure: P/hPa (Sensor Range: P = 500 hPa ... 1150 hPa</li>
- <li>telegramType: heartbeat|event</li>
- <li>state: P/hPa</li>
- </ul><br>
- The attr subType must be baroSensor.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Light Sensor (EEP A5-06-01)<br>
- [Eltako FAH60, FAH63, FIH63, Thermokon SR65 LI]<br>
- <ul>
- <li>E/lx</li>
- <li>brightness: E/lx (Sensor Range: 300 lx ... 30 klx, 600 lx ... 60 klx
- , Sensor Range for Eltako: E = 0 lx ... 100 lx, 300 lx ... 30 klx)</li>
- <li>voltage: U/V</li> (Sensor Range: U = 0 V ... 5.1 V)
- <li>state: E/lx</li>
- </ul><br>
- Eltako devices only support Brightness.<br>
- The attr subType must be lightSensor.01 and attr manufID must be 00D
- for Eltako Devices. This is done if the device was created by
- autocreate.
- </li>
- <br><br>
- <li>Light Sensor (EEP A5-06-02)<br>
- [untested]<br>
- <ul>
- <li>E/lx</li>
- <li>brightness: E/lx (Sensor Range: 0 lx ... 1020 lx</li>
- <li>voltage: U/V (Sensor Range: U = 0 V ... 5.1 V)</li>
- <li>state: E/lx</li>
- </ul><br>
- The attr subType must be lightSensor.02. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Light Sensor (EEP A5-06-03)<br>
- [untested]<br>
- <ul>
- <li>E/lx</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
- <li>errorCode: 251 ... 255</li>
- <li>state: E/lx</li>
- </ul><br>
- The attr subType must be lightSensor.03. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Light Sensor (EEP A5-06-04)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C E: E/lx B: ok|low</li>
- <li>battery: ok|low</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 65535 lx)</li>
- <li>energyStorage: 1/%</li>
- <li>temperature: t/°C (Sensor Range: t = -20 °C ... 60 °C)</li>
- <li>state: T: t/°C E: E/lx B: ok|low</li>
- </ul><br>
- The attr subType must be lightSensor.04. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Light Sensor (EEP A5-06-05)<br>
- [untested]<br>
- <ul>
- <li>E/lx</li>
- <li>brightness: E/lx (Sensor Range: 0 lx ... 10200 lx</li>
- <li>voltage: U/V (Sensor Range: U = 0 V ... 5.1 V)</li>
- <li>state: E/lx</li>
- </ul><br>
- The attr subType must be lightSensor.05. This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Occupancy Sensor (EEP A5-07-01, A5-07-02)<br>
- [EnOcean EOSW]<br>
- <ul>
- <li>on|off</li>
- <li>battery: ok|low</li>
- <li>button: pressed|released</li>
- <li>current: I/µA (Sensor Range: I = 0 V ... 127.0 µA)</li>
- <li>errorCode: 251 ... 255</li>
- <li>motion: on|off</li>
- <li>sensorType: ceiling|wall</li>
- <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
- <li>state: on|off</li>
- </ul><br>
- The attr subType must be occupSensor.<01|02>. This is done if the device was
- created by autocreate. Current is the solar panel current. Some values are
- displayed only for certain types of devices.
- </li>
- <br><br>
- <li>Eltako/PioTek-Tracker TF-TTB (EEP A5-07-01)<br>
- <ul>
- <li>on|off</li>
- <li>battery: ok|low</li>
- <li>button: pressed|released</li>
- <li>motion: on|off</li>
- <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
- <li>state: on|off</li>
- </ul><br>
- The attr subType must be occupSensor.01. This is done if the device was
- created by autocreate. The attr model has to be set manually to tracker.
- Alternatively, the profile will be defined with inofficial EEP G5-07-01.<br>
- The transmission cycle is set using the attribute <a href="#EnOcean_trackerWakeUpCycle">trackerWakeUpCycle</a>.
- </li>
- <br><br>
- <li>Occupancy Sensor (EEP A5-07-03)<br>
- [untested]<br>
- <ul>
- <li>M: on|off E: E/lx U: U/V</li>
- <li>battery: ok|low</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
- <li>errorCode: 251 ... 255</li>
- <li>motion: on|off</li>
- <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
- <li>state: M: on|off E: E/lx U: U/V</li>
- </ul><br>
- The attr subType must be occupSensor.03. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Light, Temperatur and Occupancy Sensor (EEP A5-08-01 ... A5-08-03)<br>
- [Eltako FABH63, FBH55, FBH63, FIBH63, Thermokon SR-MDS, PEHA 482 FU-BM DE]<br>
- <ul>
- <li>M: on|off E: E/lx P: absent|present T: t/°C U: U/V</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 510, 1020, 1530 or 2048 lx)</li>
- <li>motion: on|off</li>
- <li>presence: absent|present</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 51 °C or -30 °C ... 50 °C)</li>
- <li>voltage: U/V</li> (Sensor Range: U = 0 V ... 5.1 V)
- <li>state: M: on|off E: E/lx P: absent|present T: t/°C U: U/V</li>
- </ul><br>
- Eltako and PEHA devices only support Brightness and Motion.<br>
- The attr subType must be lightTempOccupSensor.<01|02|03> and attr
- manufID must be 00D for Eltako Devices. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Gas Sensor, CO Sensor (EEP A5-09-01)<br>
- [untested]<br>
- <ul>
- <li>CO: c/ppm (Sensor Range: c = 0 ppm ... 255 ppm)</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 255 °C)</li>
- <li>state: c/ppm</li>
- </ul><br>
- The attr subType must be COSensor.01. This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Gas Sensor, CO Sensor (EEP A5-09-02)<br>
- [untested]<br>
- <ul>
- <li>CO: c/ppm (Sensor Range: c = 0 ppm ... 1020 ppm)</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 51.0 °C)</li>
- <li>voltage: U/V</li> (Sensor Range: U = 0 V ... 5.1 V)
- <li>state: c/ppm</li>
- </ul><br>
- The attr subType must be COSensor.02. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Gas Sensor, CO2 Sensor (EEP A5-09-04)<br>
- [Thermokon SR04 CO2 *, Eltako FCOTF63, untested]<br>
- <ul>
- <li>airQuality: high|mean|moderate|low (Air Quality Classes DIN EN 13779)</li>
- <li>CO2: c/ppm (Sensor Range: c = 0 ppm ... 2550 ppm)</li>
- <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 51 °C)</li>
- <li>state: T: t/°C H: rH/% CO2: c/ppm AQ: high|mean|moderate|low</li>
- </ul><br>
- The attr subType must be tempHumiCO2Sensor.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Gas Sensor, Volatile organic compounds (VOC) Sensor (EEP A5-09-05, A5-09-0C)<br>
- [untested]<br>
- <ul>
- <li>concentration: c/[unit] (Sensor Range: c = 0 ... 655350</li>
- <li>concentrationUnit: ppb|μ/m3</li>
- <li>vocName: Name of last measured VOC</li>
- <li>state: c/[unit]</li>
- </ul><br>
- The attr subType must be vocSensor.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Gas Sensor, Radon Sensor (EEP A5-09-06)<br>
- [untested]<br>
- <ul>
- <li>Rn: A m3/Bq (Sensor Range: A = 0 Bq/m3 ... 1023 Bq/m3)</li>
- <li>state: A m3/Bq</li>
- </ul><br>
- The attr subType must be radonSensor.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Gas Sensor, Particles Sensor (EEP A5-09-07)<br>
- [untested]<br>
- Three channels with particle sizes of up to 10 μm, 2.5 μm and 1 μm are supported<br>.
- <ul>
- <li>particles_10: p m3/μg | inactive (Sensor Range: p = 0 μg/m3 ... 511 μg/m3)</li>
- <li>particles_2_5: p m3/μg | inactive (Sensor Range: p = 0 μg/m3 ... 511 μg/m3)</li>
- <li>particles_1: p m3/μg | inactive (Sensor Range: p = 0 μg/m3 ... 511 μg/m3)</li>
- <li>state: PM10: p m3/μg PM2_5: p m3/μg PM1: p m3/μg</li>
- </ul><br>
- The attr subType must be particlesSensor.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>CO2 Sensor (EEP A5-09-08, A5-09-09)<br>
- [untested]<br>
- <ul>
- <li>CO2: c/ppm (Sensor Range: c = 0 ppm ... 2000 ppm)</li>
- <li>powerFailureDetection: detected|not_detected</li>
- <li>state: c/ppm</li>
- </ul><br>
- The attr subType must be CO2Sensor.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>H Sensor (EEP A5-09-0A)<br>
- [untested]<br>
- <ul>
- <li>c/ppm</li>
- <li>voltage: U/V (Sensor Range: U = 2 V ... 5 V)</li>
- <li>H: c/ppm (Sensor Range: c = 0 ppm ... 2000 ppm)</li>
- <li>temperature: t/°C (Sensor Range: t = -20 °C ... 60 °C)</li>
- <li>state: c/ppm</li>
- </ul><br>
- The attr subType must be HSensor.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Radiation Sensor (EEP A5-09-0B)<br>
- [untested]<br>
- <ul>
- <li>1/[unit]</li>
- <li>radioactivity: 1/[unit] (Sensor Range: c = 0 [unit] ... 65535 [unit])</li>
- <li>unit: uSv/h|cpm|Bq/L|Bq/kg</li>
- <li>voltage: U/V (Sensor Range: U = 2 V ... 5 V)</li>
- <li>state: 1/[unit]</li>
- </ul><br>
- The attr subType must be radiationSensor.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-01 ... A5-10-0D)<br>
- [Eltako FTR55*, Thermokon SR04 *, Thanos SR *]<br>
- <ul>
- <li>T: t/°C SP: 0 ... 255 F: 0|1|2|3|auto SW: 0|1</li>
- <li>fanStage: 0|1|2|3|auto</li>
- <li>switch: on|off</li>
- <li>setpoint: 0 ... 255</li>
- <li>setpointScaled: <floating-point number></li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>state: T: t/°C SP: 0 ... 255 F: 0|1|2|3|auto SW: on|off</li><br>
- Alternatively for Eltako devices
- <li>T: t/°C SPT: t/°C NR: t/°C</li>
- <li>block: lock|unlock</li>
- <li>nightReduction: t/K</li>
- <li>setpointTemp: t/°C</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>state: T: t/°C SPT: t/°C NR: t/K</li><br>
- </ul><br>
- The scaling of the setpoint adjustment is device- and vendor-specific. Set the
- attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
- <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
- setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
- adjust the scaling alternatively.<br>
- The attr subType must be roomSensorControl.05 and attr
- manufID must be 00D for Eltako Devices. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-04-01, A5-10-10 ... A5-10-14)<br>
- [Eltako FUTH65D, Thermokon SR04 * rH, Thanos SR *]<br>
- <ul>
- <li>T: t/°C H: rH/% SP: 0 ... 255 SW: 0|1</li>
- <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
- <li>switch: 0|1</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>setpoint: 0 ... 255</li>
- <li>setpointScaled: <floating-point number></li>
- <li>state: T: t/°C H: rH/% SP: 0 ... 255 SW: 0|1</li>
- </ul><br>
- The scaling of the setpoint adjustment is device- and vendor-specific. Set the
- attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
- <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
- setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
- adjust the scaling alternatively.<br>
- The attr subType must be roomSensorControl.01 and attr
- manufID must be 00D for Eltako Devices. This is
- done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-15 ... A5-10-17)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C SP: 0 ... 63 P: absent|present</li>
- <li>presence: absent|present</li>
- <li>temperature: t/°C (Sensor Range: t = -10 °C ... 41.2 °C)</li>
- <li>setpoint: 0 ... 63</li>
- <li>setpointScaled: <floating-point number></li>
- <li>state: T: t/°C SP: 0 ... 63 P: absent|present</li>
- </ul><br>
- The scaling of the setpoint adjustment is device- and vendor-specific. Set the
- attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
- <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
- setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
- adjust the scaling alternatively.<br>
- The attr subType must be roomSensorControl.02. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-18)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C B: E/lx F: 0|1|2|3|4|5|auto|off SP: t/°C P: absent|present|disabled</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
- <li>fan: 0|1|2|3|4|5|auto|off</li>
- <li>presence: absent|present|disabled</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>setpoint: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>state: T: t/°C B: E/lx F: 0|1|2|3|4|5|auto|off SP: t/°C P: absent|present|disabled</li>
- </ul><br>
- The attr subType must be roomSensorControl.18. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-19)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C H: rH/% F: 0|1|2|3|4|5|auto|off SP: t/°C P: absent|present|disabled</li>
- <li>fan: 0|1|2|3|4|5|auto|off</li>
- <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
- <li>presence: absent|present|disabled</li>
- <li>setpoint: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>state: T: t/°C H: rH/% F: 0|1|2|3|4|5|auto|off SP: t/°C P: absent|present|disabled</li>
- </ul><br>
- The attr subType must be roomSensorControl.19. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-1A)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C F: 0|1|2|3|4|5|auto|off SP: t/°C P: absent|present|disabled U: U/V</li>
- <li>errorCode: 251 ... 255</li>
- <li>fan: 0|1|2|3|4|5|auto|off</li>
- <li>presence: absent|present|disabled</li>
- <li>setpoint: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
- <li>state: T: t/°C F: 0|1|2|3|4|5|auto|off SP: t/°C P: absent|present|disabled U: U/V</li>
- </ul><br>
- The attr subType must be roomSensorControl.1A. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-1B)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C B: E/lx F: 0|1|2|3|4|5|auto|off P: absent|present|disabled U: U/V</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
- <li>errorCode: 251 ... 255</li>
- <li>fan: 0|1|2|3|4|5|auto|off</li>
- <li>presence: absent|present|disabled</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
- <li>state: T: t/°C B: E/lx F: 0|1|2|3|4|5|auto|off P: absent|present|disabled U: U/V</li>
- </ul><br>
- The attr subType must be roomSensorControl.1B. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-1C)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C B: E/lx F: 0|1|2|3|4|5|auto|off SP: E/lx P: absent|present|disabled</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
- <li>fan: 0|1|2|3|4|5|auto|off</li>
- <li>presence: absent|present|disabled</li>
- <li>setpoint: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>state: T: t/°C B: E/lx F: 0|1|2|3|4|5|auto|off SP: E/lx P: absent|present|disabled</li>
- </ul><br>
- The attr subType must be roomSensorControl.1C. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-1D)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C H: rH/% F: 0|1|2|3|4|5|auto|off SP: rH/% P: absent|present|disabled</li>
- <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
- <li>fan: 0|1|2|3|4|5|auto|off</li>
- <li>presence: absent|present|disabled</li>
- <li>setpoint: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>state: T: t/°C H: rH/% F: 0|1|2|3|4|5|auto|off SP: rH/% P: absent|present|disabled</li>
- </ul><br>
- The attr subType must be roomSensorControl.1D. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Sensor and Control Unit (EEP A5-10-1F)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C F: 0|1|2|3|auto SP: 0 ... 255 P: absent|present|disabled</li>
- <li>fan: 0|1|2|3|auto</li>
- <li>presence: absent|present|disabled</li>
- <li>setpoint: 0 ... 255</li>
- <li>setpointScaled: <floating-point number></li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>state: T: t/°C F: 0|1|2|3|auto SP: 0 ... 255 P: absent|present|disabled</li>
- </ul><br>
- The scaling of the setpoint adjustment is device- and vendor-specific. Set the
- attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
- <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
- setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
- adjust the scaling alternatively.<br>
- The attr subType must be roomSensorControl.1F. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Room Operation Panel (EEP A5-10-20, A5-10-21)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C H: rH/% SP: 0 ... 255 B: ok|low</li>
- <li>activity: yes|no</li>
- <li>battery: ok|low</li>
- <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
- <li>setpoint: 0 ... 255</li>
- <li>setpointMode: auto|frostProtect|setpoint</li>
- <li>setpointScaled: <floating-point number></li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>state: t/°C H: rH/% SP: 0 ... 255 B: ok|low</li>
- </ul><br>
- The scaling of the setpoint adjustment is device- and vendor-specific. Set the
- attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
- <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
- setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
- adjust the scaling alternatively.<br>
- The attr subType must be roomSensorControl.20. This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Room Operation Panel (EEP A5-10-22, A5-10-23)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C H: rH/% SP: 0 ... 255 F: auto|off|1|2|3 O: occupied|unoccupied</li>
- <li>fanSpeed: auto|off|1|2|3</li>
- <li>humidity: rH/% (Sensor Range: rH = 0 % ... 100 %)</li>
- <li>occupancy: occupied|unoccupied</li>
- <li>setpoint: 0 ... 255</li>
- <li>setpointScaled: <floating-point number></li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>state: t/°C H: rH/% SP: 0 ... 255 F: auto|off|1|2|3 O: occupied|unoccupied</li>
- </ul><br>
- The scaling of the setpoint adjustment is device- and vendor-specific. Set the
- attributes <a href="#scaleMax">scaleMax</a>, <a href="#scaleMin">scaleMin</a> and
- <a href="#scaleDecimals">scaleDecimals</a> for the additional scaled reading
- setpointScaled. Use attribut <a href="#userReadings">userReadings</a> to
- adjust the scaling alternatively.<br>
- The attr subType must be roomSensorControl.22.
- This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Lighting Controller State (EEP A5-11-01)<br>
- [untested]<br>
- <ul>
- <li>on|off</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 510 lx)</li>
- <li>contact: open|closed</li>
- <li>daylightHarvesting: enabled|disabled</li>
- <li>dim: 0 ... 255</li>
- <li>presence: absent|present</li>
- <li>illum: 0 ... 255</li>
- <li>mode: switching|dimming</li>
- <li>powerRelayTimer: enabled|disabled</li>
- <li>powerSwitch: on|off</li>
- <li>repeater: enabled|disabled</li>
- <li>state: on|off</li>
- </ul><br>
- The attr subType must be lightCtrlState.01 This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Temperature Controller Output (EEP A5-11-02)<br>
- [untested]<br>
- <ul>
- <li>t/°C</li>
- <li>alarm: on|off</li>
- <li>controlVar: cvar (Sensor Range: cvar = 0 % ... 100 %)</li>
- <li>controllerMode: auto|heating|cooling|off</li>
- <li>controllerState: auto|override</li>
- <li>energyHoldOff: normal|holdoff</li>
- <li>fan: 0 ... 3|auto</li>
- <li>presence: present|absent|standby|frost</li>
- <li>setpointTemp: t/°C (Sensor Range: t = 0 °C ... 51.2 °C)</li>
- <li>state: t/°C</li>
- </ul><br>
- The attr subType must be tempCtrlState.01 This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li><a name="Blind Status">Blind Status</a> (EEP A5-11-03)<br>
- [untested, experimental status]<br>
- <ul>
- <li>open|closed|not_reached|not_available</li>
- <li>alarm: on|off|no endpoints defined|not used</li>
- <li>anglePos: α/° (Sensor Range: α = -180 ° ... 180 °)</li>
- <li>endPosition: open|closed|not_reached|not_available</li>
- <li>position: pos/% (Sensor Range: pos = 0 % ... 100 %)</li>
- <li>positionMode: normal|inverse</li>
- <li>serviceOn: yes|no</li>
- <li>shutterState: opens|closes|stopped|not_available</li>
- <li>state: open|closed|not_reached|not_available</li>
- </ul><br>
- The attr subType must be shutterCtrlState.01 This is done if the device was
- created by autocreate.<br>
- The profile is linked with <a href="#Blind Command Central">Blind Command Central</a>.
- The profile <a href="#Blind Command Central">Blind Command Central</a>
- controls the devices centrally. For that the attributes subDef, subTypeSet
- and gwCmd have to be set manually.
- </li>
- <br><br>
- <li>Extended Lighting Status (EEP A5-11-04)<br>
- [untested]<br>
- <ul>
- <li>on|off</li>
- <li>alarm: off|lamp_failure|internal_failure|external_periphery_failure</li>
- <li>blue: 0 ... 255</li>
- <li>current: <formula symbol>/<unit> (Sensor range: <formula symbol> = 0 ... 65535 <unit></li>
- <li>currentUnit: mA|A</li>
- <li>dim: 0 ... 255</li>
- <li>energy: <formula symbol>/<unit> (Sensor range: <formula symbol> = 0 ... 65535 <unit></li>
- <li>energyUnit: Wh|kWh|MWh|GWh</li>
- <li>green: 0 ... 255</li>
- <li>measuredValue: <formula symbol>/<unit> (Sensor range: <formula symbol> = 0 ... 65535 <unit></li>
- <li>measureUnit: unknown</li>
- <li>lampOpHours: t/h |unknown (Sensor range: t = 0 h ... 65535 h)</li>
- <li>power: <formula symbol>/<unit> (Sensor range: <formula symbol> = 0 ... 65535 <unit></li>
- <li>powerSwitch: on|off</li>
- <li>powerUnit: mW|W|kW|MW</li>
- <li>red: 0 ... 255</li>
- <li>rgb: RRGGBB (red (R), green (G) or blue (B) color component values: 00 ... FF)</li>
- <li>serviceOn: yes|no</li>
- <li>voltage: <formula symbol>/<unit> (Sensor range: <formula symbol> = 0 ... 65535 <unit></li>
- <li>voltageUnit: mV|V</li>
- <li>state: on|off</li>
- </ul><br>
- The attr subType must be lightCtrlState.02 This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Dual Channel Switch Actuator (EEP A5-11-05)<br>
- [untested]<br>
- <ul>
- <li>1: on|off 2: on|off</li>
- <li>channel1: on|off</li>
- <li>channel2: on|off</li>
- <li>workingMode: 1 ... 4</li>
- <li>state: 1: on|off 2: on|off</li>
- </ul><br>
- The attr subType must be switch.05. This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Automated meter reading (AMR), Counter (EEP A5-12-00)<br>
- [Thermokon SR-MI-HS, untested]<br>
- <ul>
- <li>1/s</li>
- <li>currentValue<00 ... 15>: 1/s</li>
- <li>counter<00 ... 15>: 0 ... 16777215</li>
- <li>state: 1/s</li>
- </ul><br>
- The attr subType must be autoMeterReading.00. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Automated meter reading (AMR), Electricity (EEP A5-12-01)<br>
- [Eltako FSS12, DSZ14DRS, DSZ14WDRS, Thermokon SR-MI-HS, untested]<br>
- [Eltako FWZ12-16A tested]<br>
- <ul>
- <li>P/W</li>
- <li>power: P/W</li>
- <li>energy<0 ... 15>: E/kWh</li>
- <li>currentTariff: 0 ... 15</li>
- <li>serialNumber: S-<nnnnnn></li>
- <li>state: P/W</li>
- </ul><br>
- The attr subType must be autoMeterReading.01 and attr
- manufID must be 00D for Eltako Devices. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Automated meter reading (AMR), Gas, Water (EEP A5-12-02, A5-12-03)<br>
- [untested]<br>
- <ul>
- <li>Vs/l</li>
- <li>flowrate: Vs/l</li>
- <li>consumption<0 ... 15>: V/m3</li>
- <li>currentTariff: 0 ... 15</li>
- <li>state: Vs/l</li>
- </ul><br>
- The attr subType must be autoMeterReading.02|autoMeterReading.03.
- This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Automated meter reading (AMR), Temperatur, Load (EEP A5-12-04)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C W: m/g B: full|ok|low|empty</li>
- <li>battery: full|ok|low|empty</li>
- <li>temperature: t/°C (Sensor Range: t = -40 °C ... 40 °C)</li>
- <li>weight: m/g</li>
- <li>state: T: t/°C W: m/g B: full|ok|low|empty</li>
- </ul><br>
- The attr subType must be autoMeterReading.04.
- This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Automated meter reading (AMR), Temperatur, Container Sensor (EEP A5-12-05)<br>
- [untested]<br>
- <ul>
- <li>T: t/°C L: <location0 ... location9> B: full|ok|low|empty</li>
- <li>amount: 0 ... 10</li>
- <li>battery: full|ok|low|empty</li>
- <li>location<0 ... 9>: possessed|not_possessed</li>
- <li>temperature: t/°C (Sensor Range: t = -40 °C ... 40 °C)</li>
- <li>state: T: t/°C L: <location0 ... location9> B: full|ok|low|empty</li>
- </ul><br>
- The attr subType must be autoMeterReading.05.
- This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Automated meter reading (AMR), Current Meter 16 Channels (EEP A5-12-10)<br>
- [untested]<br>
- <ul>
- <li>I/mA</li>
- <li>current<00 ... 15>: I/mA (Sensor Range: I = 0 mA ... 16777215 mA)</li>
- <li>electricChange<00 ... 15>: Q/Ah (Sensor Range: Q = 0 Ah ... 16777215 Ah)</li>
- <li>currentTariff: 00 ... 15</li>
- <li>state: I/mA</li>
- </ul><br>
- The attr subType must be autoMeterReading.10. This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Environmental Applications<br>
- Weather Station (EEP A5-13-01)<br>
- Sun Intensity (EEP A5-13-02)<br>
- [Eltako FWS61]<br>
- <ul>
- <li>T: t/°C B: E/lx W: Vs/m IR: yes|no</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 999 lx)</li>
- <li>dayNight: day|night</li>
- <li>hemisphere: north|south</li>
- <li>isRaining: yes|no</li>
- <li>sunEast: E/lx (Sensor Range: E = 1 lx ... 150 klx)</li>
- <li>sunSouth: E/lx (Sensor Range: E = 1 lx ... 150 klx)</li>
- <li>sunWest: E/lx (Sensor Range: E = 1 lx ... 150 klx)</li>
- <li>temperature: t/°C (Sensor Range: t = -40 °C ... 80 °C)</li>
- <li>windSpeed: Vs/m (Sensor Range: V = 0 m/s ... 70 m/s)</li>
- <li>state:T: t/°C B: E/lx W: Vs/m IR: yes|no</li>
- </ul><br>
- Brightness is the strength of the dawn light. SunEast,
- sunSouth and sunWest are the solar radiation from the respective
- compass direction. IsRaining is the rain indicator.<br>
- The attr subType must be environmentApp and attr manufID must be 00D
- for Eltako Devices. This is done if the device was created by
- autocreate.<br>
- The Eltako Weather Station FWS61 supports not the day/night indicator
- (dayNight).<br>
- </li>
- <br><br>
- <li>Environmental Applications<br>
- Data Exchange (EEP A5-13-03)<br>
- Time and Day Exchange (EEP A5-13-04)<br>
- Direction Exchange (EEP A5-13-05)<br>
- Geographic Exchange (EEP A5-13-06)<br>
- <ul>
- <li>azimuth: α/° (Sensor Range: α = 0 ° ... 359 °)</li>
- <li>date: JJJJ-MM-DD</li>
- <li>elevation: β/° (Sensor Range: β = -90 ° ... 90 °)</li>
- <li>latitude: φ/° (Sensor Range: φ = -90 ° ... 90 °)</li>
- <li>longitude: λ/° (Sensor Range: λ = -180 ° ... 180 °)</li>
- <li>time: hh:mm:ss [AM|PM]</li>
- <li>timeSource: GPS|RTC</li>
- <li>weekday: Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday</li>
- </ul><br>
- The attr subType must be environmentApp. This is done if the device was created by
- autocreate.
- </li>
- <br><br>
- <li>Environmental Applications<br>
- Sun Position and Radiation (EEP A5-13-10)<br>
- [untested]<br>
- <ul>
- <li>SRA: E m2/W SNA: α/° SNE: β/°</li>
- <li>dayNight: day|night</li>
- <li>solarRadiation: E m2/W (Sensor Range: E = 0 W/m2 ... 2000 W/m2)</li>
- <li>sunAzimuth: α/° (Sensor Range: α = -90 ° ... 90 °)</li>
- <li>sunElevation: β/° (Sensor Range: β = 0 ° ... 90 °)</li>
- <li>state:SRA: E m2/W SNA: α/° SNE: β/°</li>
- </ul><br>
- The attr subType must be environmentApp. This is done if the device was created by
- autocreate.
- </li>
- <br><br>
- <li>Wind Sensor (EEP A5-13-07)<br>
- [Hideki, untested]<br>
- <ul>
- <li>Vh/km (Sensor Range: V = 0 km/h ... 199.9 km/h)</li>
- <li>battery: ok|low</li>
- <li>windSpeedAverage: Vh/km (Sensor Range: V = 0 km/h ... 199.9 km/h)</li>
- <li>windSpeedDirection: NNE|NE|ENE|E|ESE|SE|SSE|S|SSW|SW|WSW|W|WNW|NW|NNW|N</li>
- <li>windSpeedMax: Vh/km (Sensor Range: V = 0 km/h ... 199.9 km/h)</li>
- <li>state:Vh/km (Sensor Range: V = 0 km/h ... 199.9 km/h)</li>
- </ul><br>
- The attr subType must be windSensor.01. This is done if the device was created by
- autocreate.<br>
- </li>
- <br><br>
- <li>Rain Sensor (EEP A5-13-08)<br>
- [Hideki, untested]<br>
- <ul>
- <li>H/mm</li>
- <li>battery: ok|low</li>
- <li>rain: H/mm</li>
- <li>state:H/mm</li>
- </ul><br>
- The amount of rainfall is calculated at intervals of 183 s.<br>
- The attr subType must be rainSensor.01. This is done if the device was created by
- autocreate.<br>
- </li>
- <br><br>
- <li>Multi-Func Sensor (EEP A5-14-01 ... A5-14-06)<br>
- [untested]<br>
- <ul>
- <li>C: open|closed V: on|off E: E/lx U: U/V</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx, over range)</li>
- <li>contact: open|closed</li>
- <li>errorCode: 251 ... 255</li>
- <li>vibration: on|off</li>
- <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
- <li>state: C: open|closed V: on|off E: E/lx U: U/V</li>
- </ul><br>
- The attr subType must be multiFuncSensor. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Dual Door Contact (EEP A5-14-07, A5-14-08)<br>
- [untested]<br>
- <ul>
- <li>C: open|closed B: unlocked|locked V: on|off U: U/V</li>
- <li>alarm: dead_sensor</li>
- <li>block: unlocked|locked</li>
- <li>contact: open|closed</li>
- <li>vibration: on|off</li>
- <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
- <li>state: C: open|closed B: unlocked|locked V: on|off U: U/V</li>
- </ul><br>
- The attr subType must be doorContact. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Window/Door Contact (EEP A5-14-09, A5-14-0A)<br>
- [untested]<br>
- <ul>
- <li>W: open|tilt|closed B: unlocked|locked V: on|off U: U/V</li>
- <li>alarm: dead_sensor</li>
- <li>vibration: on|off</li>
- <li>voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)</li>
- <li>window: open|tilt|closed</li>
- <li>state: W: open|tilt|closed V: on|off U: U/V</li>
- </ul><br>
- The attr subType must be windowContact. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Battery Powered Actuator (EEP A5-20-01)<br>
- [Kieback&Peter MD15-FTL-xx]<br>
- <ul>
- <li>T: t/°C SPT: t/°C SP: setpoint/%</li>
- <li>actuatorState: obstructed|ok</li>
- <li>alarm: no_response_from_actuator</li>
- <li>battery: ok|low</li>
- <li>cover: open|closed</li>
- <li>delta: <floating-point number></li>
- <li>energyInput: enabled|disabled</li>
- <li>energyStorage: charged|empty</li>
- <li>maintenanceMode: off|runInit|valveClosed|valveOpend:runInit</li>
- <li>operationMode: off|setpoint|setpointTemp</li>
- <li>p_d: <floating-point number></li>
- <li>p_i: <floating-point number></li>
- <li>p_p: <floating-point number></li>
- <li>pidAlarm: actuator_in_errorPos|dead_sensor|no_temperature_value|setpoint_device_missing</li>
- <li>pidState: alarm|idle|processing|start|stop|</li>
- <li>roomTemp: t/°C</li>
- <li>selfCtl: on|off</li>
- <li>setpoint: setpoint/%</li>
- <li>setpointSet: setpoint/%</li>
- <li>setpointCalc: setpoint/%</li>
- <li>setpointTemp: t/°C</li>
- <li>setpointTempSet: t/°C</li>
- <li>teach: <result of teach procedure></li>
- <li>temperature: t/°C</li>
- <li>waitingCmds: no_change|runInit|setpoint|setpointTemp|valveCloses|valveOpens</li>
- <li>wakeUpCycle: t/s</li>
- <li>window: open|closed</li>
- <li>state: T: t/°C SPT: t/°C SP: setpoint/%</li>
- </ul><br>
- The internal temperature sensor (roomTemp) of the Micropelt iTRV is not suitable as
- a room thermostat.<br>
- The attr subType must be hvac.01. This is done if the device was created by
- autocreate.
- </li>
- <br><br>
- <li>Heating Radiator Actuating Drive (EEP A5-20-04)<br>
- [Holter SmartDrive MX]<br>
- <ul>
- <li>T: t/°C SPT: t/°C SP: setpoint/%</li>
- <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>
- <li>battery: ok|empty</li>
- <li>blockKey: yes|no</li>
- <li>delta: <floating-point number></li>
- <li>feedTemp: t/°C</li>
- <li>maintenanceMode: off|runInit|valveClosed|valveOpend:runInit</li>
- <li>measurementState: active|inactive</li>
- <li>operationMode: off|setpoint|setpointTemp</li>
- <li>p_d: <floating-point number></li>
- <li>p_i: <floating-point number></li>
- <li>p_p: <floating-point number></li>
- <li>pidAlarm: actuator_in_errorPos|dead_sensor|no_temperature_value|setpoint_device_missing</li>
- <li>pidState: alarm|idle|processing|start|stop|</li>
- <li>roomTemp: t/°C</li>
- <li>setpoint: setpoint/%</li>
- <li>setpointSet: setpoint/%</li>
- <li>setpointCalc: setpoint/%</li>
- <li>setpointTemp: t/°C</li>
- <li>setpointTempSet: t/°C</li>
- <li>teach: <result of teach procedure></li>
- <li>temperature: t/°C</li>
- <li>waitingCmds: no_change|runInit|setpoint|setpointTemp|valveCloses|valveOpens</li>
- <li>state: T: t/°C SPT: t/°C SP: setpoint/%</li>
- </ul><br>
- The attr subType must be hvac.04. This is done if the device was created by
- autocreate.
- </li>
- <br><br>
- <li>Generic HVAC Interface (EEP A5-20-10)<br>
- [IntesisBox PA-AC-ENO-1i]<br>
- <ul>
- <li>on|off</li>
- <li>ctrl: auto|0...100</li>
- <li>fanSpeed: auto|1...14</li>
- <li>occupancy: occupied|off|standby|unoccupied</li>
- <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>
- <li>vanePosition: auto|horizontal|position_2|position_3|position_4|vertical|swing|vertical_swing|horizontal_swing|hor_vert_swing|stop_swing</li>
- <li>powerSwitch: on|off</li>
- <li>teach: <result of teach procedure></li>
- <li>state: on|off</li>
- </ul><br>
- The attr subType must be hvac.10. This is done if the device was created by
- autocreate.
- </li>
- <br><br>
- <li>Generic HVAC Interface - Error Control (EEP A5-20-11)<br>
- [IntesisBox PA-AC-ENO-1i]<br>
- <ul>
- <li>error|ok</li>
- <li>alarm: error|ok</li>
- <li>errorCode: 0...65535</li>
- <li>externalDisable: disable|enable</li>
- <li>keyCardDisable: disable|enable</li>
- <li>otherDisable: disable|enable</li>
- <li>powerSwitch: on|off</li>
- <li>remoteCtrl: disable|enable</li>
- <li>teach: <result of teach procedure></li>
- <li>window: closed|opened</li>
- <li>windowDisable: disable|enable</li>
- <li>state: error|ok</li>
- </ul><br>
- The attr subType must be hvac.11. This is done if the device was created by
- autocreate.
- </li>
- <br><br>
- <li>Digital Input (EEP A5-30-01, A5-30-02)<br>
- [Thermokon SR65 DI, untested]<br>
- <ul>
- <li>open|closed</li>
- <li>battery: ok|low (only EEP A5-30-01)</li>
- <li>contact: open|closed</li>
- <li>teach: <result of teach procedure></li>
- <li>state: open|closed</li>
- </ul><br>
- The attr subType must be digitalInput.01 or digitalInput.02. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Digital Input (EEP A5-30-03)<br>
- 4 digital Inputs, Wake, Temperature [untested]<br>
- <ul>
- <li>T: t/°C I: 0|1 0|1 0|1 0|1 W: 0|1</li>
- <li>in0: 0|1</li>
- <li>in1: 0|1</li>
- <li>in2: 0|1</li>
- <li>in3: 0|1</li>
- <li>teach: <result of teach procedure></li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>wake: high|low</li>
- <li>state: T: t/°C I: 0|1 0|1 0|1 0|1 W: high|low</li>
- </ul><br>
- The attr subType must be digitalInput.03. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Digital Input (EEP A5-30-04)<br>
- 3 digital Inputs, 1 digital Input 8 bits [untested]<br>
- <ul>
- <li>0|1 0|1 0|1 0...255</li>
- <li>in0: 0|1</li>
- <li>in1: 0|1</li>
- <li>in2: 0|1</li>
- <li>in3: 0...255</li>
- <li>teach: <result of teach procedure></li>
- <li>state: 0|1 0|1 0|1 0...255</li>
- </ul><br>
- The attr subType must be digitalInput.04. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Digital Input, single input contact, retransmission, battery monitor (EEP A5-30-05)<br>
- [untested]<br>
- <ul>
- <li>error|event|heartbeat</li>
- <li>battery: U/V (Range: U = 0 V ... 3.3 V</li>
- <li>signalIdx: 0 ... 127</li>
- <li>teach: <result of teach procedure></li>
- <li>telegramType: event|heartbeat</li>
- <li>state: error|event|heartbeat</li>
- </ul><br>
- The attr subType must be digitalInput.05. This is done if the device was
- created by autocreate.
- </li>
- <li>Energy management, demand response (EEP A5-37-01)<br>
- <br>
- <ul>
- <li>on|off|waiting_for_start|waiting_for_stop</li>
- <li>level: 0...15</li>
- <li>powerUsage: powerUsage/%</li>
- <li>powerUsageLevel: max|min</li>
- <li>powerUsageScale: rel|max</li>
- <li>randomEnd: yes|no</li>
- <li>randomStart: yes|no</li>
- <li>setpoint: 0...255</li>
- <li>teach: <result of teach procedure></li>
- <li>timeout: yyyy-mm-dd hh:mm:ss</li>
- <li>state: on|off|waiting_for_start|waiting_for_stop</li>
- </ul><br>
- The attr subType must be energyManagement.01. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Switching<br>
- [Eltako FLC61, FSA12, FSR14]<br>
- <ul>
- <li>on</li>
- <li>off</li>
- <li>executeTime: t/s (Sensor Range: t = 0.1 s ... 6553.5 s or 0 if no time specified)</li>
- <li>executeType: duration|delay</li>
- <li>block: lock|unlock</li>
- <li>teach: <result of teach procedure></li>
- <li>state: on|off</li>
- </ul><br>
- The attr subType must be gateway and gwCmd must be switching. This is done if the device was
- created by autocreate.<br>
- For Eltako devices attributes must be set manually. Eltako devices only send on/off.
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Dimming<br>
- [Eltako FUD14, FUD61, FUD70, FSG14, ...]<br>
- <ul>
- <li>on</li>
- <li>off</li>
- <li>block: lock|unlock</li>
- <li>dim: dim/% (Sensor Range: dim = 0 % ... 100 %)</li>
- <li>dimValueLast: dim/%<br>
- Last value received from the bidirectional dimmer.</li>
- <li>dimValueStored: dim/%<br>
- Last value saved by <code>set <name> dim <value></code>.</li>
- <li>rampTime: t/s (Sensor Range: t = 1 s ... 255 s or 0 if no time specified,
- for Eltako: t = 1 = fast dimming ... 255 = slow dimming or 0 = dimming speed on the dimmer used)</li>
- <li>teach: <result of teach procedure></li>
- <li>state: on|off</li>
- </ul><br>
- The attr subType must be gateway, gwCmd must be dimming and attr manufID must be 00D
- for Eltako Devices. This is done if the device was created by autocreate.<br>
- For Eltako devices attributes must be set manually. Eltako devices only send on/off and dim.
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Setpoint shift<br>
- [untested]<br>
- <ul>
- <li>1/K</li>
- <li>setpointShift: 1/K (Sensor Range: T = -12.7 K ... 12.8 K)</li>
- <li>teach: <result of teach procedure></li>
- <li>state: 1/K</li>
- </ul><br>
- The attr subType must be gateway, gwCmd must be setpointShift.
- This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Basic Setpoint<br>
- [untested]<br>
- <ul>
- <li>t/°C</li>
- <li>setpoint: t/°C (Sensor Range: t = 0 °C ... 51.2 °C)</li>
- <li>teach: <result of teach procedure></li>
- <li>state: t/°C</li>
- </ul><br>
- The attr subType must be gateway, gwCmd must be setpointBasic.
- This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Control variable<br>
- [untested]<br>
- <ul>
- <li>auto|heating|cooling|off</li>
- <li>controlVar: cvov (Sensor Range: cvov = 0 % ... 100 %)</li>
- <li>controllerMode: auto|heating|cooling|off</li>
- <li>controllerState: auto|override</li>
- <li>energyHoldOff: normal|holdoff</li>
- <li>presence: present|absent|standby</li>
- <li>teach: <result of teach procedure></li>
- <li>state: auto|heating|cooling|off</li>
- </ul><br>
- The attr subType must be gateway, gwCmd must be controlVar.
- This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Gateway (EEP A5-38-08)<br>
- Fan stage<br>
- [untested]<br>
- <ul>
- <li>0 ... 3|auto</li>
- <li>teach: <result of teach procedure></li>
- <li>state: 0 ... 3|auto</li>
- </ul><br>
- The attr subType must be gateway, gwCmd must be fanStage.
- This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Extended Lighting Control (EEP A5-38-09)<br>
- [untested]<br>
- <ul>
- <li>on</li>
- <li>off</li>
- <li>block: unlock|on|off|local</li>
- <li>blue: <blue channel value> (Range: blue = 0 ... 255)</li>
- <li>dimMax: <maximum dimming value> (Range: dim = 0 ... 255)</li>
- <li>dimMin: <minimum dimming value> (Range: dim = 0 ... 255)</li>
- <li>green: <green channel value> (Range: green = 0 ... 255)</li>
- <li>rampTime: t/s (Range: t = 0 s ... 65535 s)</li>
- <li>red: <red channel value> (Range: red = 0 ... 255)</li>
- <li>rgb: RRGGBB (red (R), green (G) or blue (B) color component values: 00 ... FF)</li>
- <li>teach: <result of teach procedure></li>
- <li>state: on|off</li>
- </ul><br>
- Another readings, see subtype lightCtrlState.02.<br>
- The attr subType or subTypSet must be lightCtrl.01. This is done if the device was created by autocreate.<br>
- The subType is associated with the subtype lightCtrlState.02.
- </li>
- <br><br>
- <li>Manufacturer Specific Applications (EEP A5-3F-7F)<br><br>
- Wireless Analog Input Module<br>
- [Thermokon SR65 3AI, untested]<br>
- <ul>
- <li>I1: U/V I2: U/V I3: U/V</li>
- <li>input1: U/V (Sensor Range: U = 0 V ... 10 V)</li>
- <li>input2: U/V (Sensor Range: U = 0 V ... 10 V)</li>
- <li>input3: U/V (Sensor Range: U = 0 V ... 10 V)</li>
- <li>teach: <result of teach procedure></li>
- <li>state: I1: U/V I2: U/V I3: U/V</li>
- </ul><br>
- The attr subType must be manufProfile and attr manufID must be 002
- for Thermokon Devices. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Manufacturer Specific Applications (EEP A5-3F-7F)<br><br>
- Thermostat Actuator<br>
- [AWAG omnio UPH230/1x]<br>
- <ul>
- <li>on|off</li>
- <li>emergencyMode<channel>: on|off</li>
- <li>nightReduction<channel>: on|off</li>
- <li>setpointTemp<channel>: t/°C</li>
- <li>teach: <result of teach procedure></li>
- <li>temperature<channel>: t/°C</li>
- <li>window<channel>: on|off</li>
- <li>state: on|off</li>
- </ul><br>
- The attr subType must be manufProfile and attr manufID must be 005
- for AWAG omnio Devices. This is done if the device was created by autocreate.
- </li>
- <br><br>
- <li>Manufacturer Specific Applications (EEP A5-3F-7F)<br><br>
- Shutter (EEP F6-02-01 ... F6-02-02)<br>
- [Eltako FSB12, FSB14, FSB61, FSB70]<br>
- <ul>
- <li>open|open_ack<br>
- The status of the device will become "open" after the TOP endpoint is
- reached, or it has finished an "opens" or "position 0" command.</li>
- <li>closed<br>
- The status of the device will become "closed" if the BOTTOM endpoint is
- reached</li>
- <li>stop<br>
- The status of the device become "stop" if stop command is sent.</li>
- <li>not_reached<br>
- The status of the device become "not_reached" between one of the endpoints.</li>
- <li>anglePos: α/° (Sensor Range: α = -180 ° ... 180 °)</li>
- <li>endPosition: open|open_ack|closed|not_reached|not_available</li>
- <li>position: pos/% (Sensor Range: pos = 0 % ... 100 %)</li>
- <li>teach: <result of teach procedure></li>
- <li>state: open|open_ack|closed|not_reached|stop|teach</li>
- </ul><br>
- The values of the reading position and anglePos are updated automatically,
- if the command position is sent or the reading state was changed
- manually to open or closed.<br>
- Set attr subType file, attr manufID to 00D and attr model to
- FSB14|FSB61|FSB70 manually.
- </li>
- <br><br>
- <li>Electronic switches and dimmers with Energy Measurement and Local Control (D2-01-00 - D2-01-12)<br>
- [Telefunken Funktionsstecker, PEHA Easyclick, AWAG Elektrotechnik AG Omnio UPS 230/xx,UPD 230/xx]<br>
- <ul>
- <li>on</li>
- <li>off</li>
- <li>autoOffTime<1...29|All|Input>: 1/s</li>
- <li>channel<0...29|All|Input>: on|off</li>
- <li>delayOffTime<1...29|All|Input>: 1/s</li>
- <li>dayNight: day|night</li>
- <li>defaultState: on|off|last</li>
- <li>devTemp: t/°C|invalid</li>
- <li>devTempState: ok|max|warning</li>
- <li>dim<0...29|Input>: dim/% (Sensor Range: dim = 0 % ... 100 %)</li>
- <li>energy<channel>: E/[Ws|Wh|KWh]</li>
- <li>energyUnit<channel>: Ws|Wh|KWh</li>
- <li>error<channel>: ok|warning|failure|not_supported</li>
- <li>extSwitchMode<1...29|All|Input>: unavailable|switch|pushbutton|auto</li>
- <li>extSwitchType<1...29|All|Input>: toggle|direction</li>
- <li>firmwareVersion: [000000 ... FFFFFF]</li>
- <li>loadClassification: no</li>
- <li>localControl<channel>: enabled|disabled</li>
- <li>loadLink: connected|disconnected</li>
- <li>loadOperation: 3-wire</li>
- <li>loadState: on|off</li>
- <li>measurementMode: energy|power</li>
- <li>measurementReport: auto|query</li>
- <li>measurementReset: not_active|trigger</li>
- <li>measurementDelta: E/[Ws|Wh|KWh|W|KW]</li>
- <li>measurementUnit: Ws|Wh|KWh|W|KW</li>
- <li>overCurrentOff<channel>: executed|ready</li>
- <li>overCurrentShutdown<channel>: off|restart</li>
- <li>overCurrentShutdownReset<channel>: not_active|trigger</li>
- <li>power<channel>: P/[W|KW]</li>
- <li>powerFailure<channel>: enabled|disabled</li>
- <li>powerFailureDetection<channel>: detected|not_detected</li>
- <li>powerUnit<channel>: W|KW</li>
- <li>rampTime<1...3l>: 1/s</li>
- <li>responseTimeMax: 1/s</li>
- <li>responseTimeMin: 1/s</li>
- <li>roomCtrlMode: off|comfort|comfort-1|comfort-2|economy|buildingProtection</li>
- <li>serialNumber: [00000000 ... FFFFFFFF]</li>
- <li>taughtInDevID<00...23>: [00000001 ... FFFFFFFE]</li>
- <li>taughtInDevNum: [0 ... 23]</li>
- <li>teach: <result of teach procedure></li>
- <li>teachInDev: enabled|disabled</li>
- <li>state: on|off</li>
- </ul>
- <br>
- The attr subType must be actuator.01. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Blind Control for Position and Angle (D2-05-00)<br>
- [AWAG Elektrotechnik AG OMNIO UPJ 230/12]<br>
- <ul>
- <li>open<br>
- The status of the device will become "open" after the TOP endpoint is
- reached, or it has finished an "opens" or "position 0" command.</li>
- <li>closed<br>
- The status of the device will become "closed" if the BOTTOM endpoint is
- reached</li>
- <li>stop<br>
- The status of the device become "stop" if stop command is sent.</li>
- <li>not_reached<br>
- The status of the device become "not_reached" between one of the endpoints.</li>
- <li>pos/% (Sensor Range: pos = 0 % ... 100 %)</li>
- <li>anglePos<channel>: α/% (Sensor Range: α = 0 % ... 100 %)</li>
- <li>block<channel>: unlock|lock|alarm</li>
- <li>endPosition<channel>: open|closed|not_reached|unknown</li>
- <li>position<channel>: unknown|pos/% (Sensor Range: pos = 0 % ... 100 %)</li>
- <li>teach: <result of teach procedure></li>
- <li>state: open|closed|in_motion|stopped|pos/% (Sensor Range: pos = 0 % ... 100 %)</li>
- </ul>
- <br>
- The attr subType must be blindsCtrl.00 or blindsCtrl.01. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Multisensor Window Handle (D2-06-01)<br>
- [Soda GmbH]<br>
- <ul>
- <li>T: t/°C H: -|rH/% E: -|E/lx M: off|on|invalid|not_supported|unknown</li>
- <li>alarms: <alarms> (Range: alarms = 00000000 ... FFFFFFFF)</li>
- <li>battery: ok|low</li>
- <li>batteryLowClick: enabled|disabled</li>
- <li>burglaryAlarm: off|on|invalid|not_supported|unknown</li>
- <li>handle: up|down|left|right|invalid|not_supported|unknown</li>
- <li>blinkInterval: t/s|unknown (Range: t = 3 s ... 255 s)</li>
- <li>blinkIntervalSet: t/s|unknown (Range: t = 3 s ... 255 s)</li>
- <li>brightness: E/lx|over_range|invalid|not_supported|unknown (Sensor Range: E = 0 lx ... 60000 lx)</li>
- <li>buttonLeft: pressed|released|invalid|not_supported|unknown</li>
- <li>buttonLeftPresses: <buttonLeftPresses> (Range: buttonLeftPresses = 00000000 ... FFFFFFFF)</li>
- <li>buttonRight: pressed|released|invalid|not_supported|unknown</li>
- <li>buttonRightPresses: <buttonRightPresses> (Range: buttonRightPresses = 00000000 ... FFFFFFFF)</li>
- <li>energyStorage: 1/%|unknown</li>
- <li>handleClosedClick: enabled|disabled</li>
- <li>handleMoveClosed: <handleMoveClosed> (Range: handleMoveClosed = 00000000 ... FFFFFFFF)</li>
- <li>handleMoveOpend: <handleMoveOpend> (Range: handleMoveOpend = 00000000 ... FFFFFFFF)</li>
- <li>handleMoveTilted: <handleMoveTilted> (Range: handleMoveTilted = 00000000 ... FFFFFFFF)</li>
- <li>humidity: rH/%|invalid|not_supported|unknown</li>
- <li>motion: off|on|invalid|not_supported|unknown</li>
- <li>powerOns: <powerOns> (Range: powerOns = 00000000 ... FFFFFFFF)</li>
- <li>presence: absent|present|invalid|not_supported|unknown</li>
- <li>protectionAlarm: off|on|invalid|not_supported|unknown</li>
- <li>temperature: t/°C|invalid|not_supported|unknown (Sensor Range: t = -20 °C ... 60 °C)</li>
- <li>updateInterval: t/s|unknown (Range: t = 5 s ... 65535 s)</li>
- <li>updateIntervalSet: t/s|unknown (Range: t = 5 s ... 65535 s)</li>
- <li>waitingCmds: <integer number></li>
- <li>window: undef|not_tilted|tilted|invalid|not_supported|unknown</li>
- <li>windowTilts: <windowTilts> (Range: windowTilts = 00000000 ... FFFFFFFF)</li>
- <li>state: T: t/°C H: -|rH/% E: -|E/lx M: off|on|invalid|not_supported|unknown</li>
- </ul><br>
- The attr subType must be multisensor.01. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Room Control Panels (D2-10-00 - D2-10-02)<br>
- [Kieback & Peter RBW322-FTL]<br>
- <ul>
- <li>T: t/°C H: -|rH/% F: 0 ... 100/% SPT: t/°C O: -|absent|present M: -|on|off</li>
- <li>battery: ok|low|empty|-</li>
- <li>cooling: auto|on|off|-</li>
- <li>customWarning[1|2]: on|off</li>
- <li>fanSpeed: 0 ... 100/%</li>
- <li>fanSpeedMode: central|local</li>
- <li>heating: auto|on|off|-</li>
- <li>humidity: -|rH/%</li>
- <li>moldWarning: on|off</li>
- <li>motion: on|off|-</li>
- <li>occupancy: -|absent|present</li>
- <li>roomCtrlMode: buildingProtection|comfort|economy|preComfort</li>
- <li>setpointBuildingProtectionTemp: -|t/°C (Range: t = 0 °C ... 40 °C)</li>
- <li>setpointComfortTemp: -|t/°C (Range: t = 0 °C ... 40 °C)</li>
- <li>setpointEconomyTemp: -|t/°C (Range: t = 0 °C ... 40 °C)</li>
- <li>setpointPreComfortTemp: -|t/°C (Range: t = 0 °C ... 40 °C)</li>
- <li>setpointTemp: t/°C (Range: t = 0 °C ... 40 °C)</li>
- <li>solarPowered: yes|no</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>window: closed|open</li>
- <li>state: T: t/°C H: -|rH/% F: 0 ... 100/% SPT: t/°C O: -|absent|present M: -|on|off</li>
- </ul><br>
- The attr subType must be roomCtrlPanel.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Room Control Panels (D2-11-01 - D2-11-08)<br>
- [Thermokon EasySens SR06 LCD-2T/-2T rh -4T/-4T rh]<br>
- <ul>
- <li>T: t/°C H: rH/% SPT: t/°C F: auto|off|1|2|3</li>
- <li>cooling: on|off</li>
- <li>fanSpeed: auto|off|1|2|3</li>
- <li>heating: on|off</li>
- <li>humidity: rH/%</li>
- <li>occupancy: occupied|unoccupied</li>
- <li>setpointBase: t/°C (Range: t = 15 °C ... 30 °C)</li>
- <li>setpointShift: t/K (Range: t = -10 K ... 10 K)</li>
- <li>setpointShiftMax: t/K (Range: t = 0 K ... 10 K)</li>
- <li>setpointTemp: t/°C (Range: t = 5 °C ... 40 °C)</li>
- <li>setpointType: setpointTemp|setpointShift</li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>trigger: heartbeat|sensor|input</li>
- <li>window: closed|open</li>
- <li>state: T: t/°C H: rH/% SPT: t/°C F: auto|off|1|2|3</li>
- </ul><br>
- The attr subType must be roomCtrlPanel.01. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired by Smart Ack,
- see <a href="#EnOcean_smartAck">SmartAck Learning</a>.
- </li>
- <br><br>
- <li>Fan Control (D2-20-00 - D2-20-02)<br>
- [Maico ECA x RC/RCH, ER 100 RC, untested]<br>
- <ul>
- <li>on|off|not_supported</li>
- <li>fanSpeed: 0 ... 100/%</li>
- <li>error: ok|air_filter|hardware|not_supported</li>
- <li>humidity: rH/%|not_supported</li>
- <li>humidityCtrl: disabled|enabled|not_supported</li>
- <li>roomSize: 0...350/m<sup>2</sup>|max</li>
- <li>roomSizeRef: unsed|not_used|not_supported</li>
- <li>setpointTemp: t/°C (Range: t = 0 °C ... 40 °C)</li>
- <li>teach: <result of teach procedure></li>
- <li>temperature: t/°C (Sensor Range: t = 0 °C ... 40 °C)</li>
- <li>state: on|off|not_supported</li>
- </ul><br>
- The attr subType must be fanCtrl.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>AC Current Clamp (D2-32-00 - D2-32-02)<br>
- [untested]<br>
- <ul>
- <li>I1: I/A I2: I/A I3: I/A</li>
- <li>current1: I/A (Range: I = 0 A ... 4095 A)</li>
- <li>current2: I/A (Range: I = 0 A ... 4095 A)</li>
- <li>current3: I/A (Range: I = 0 A ... 4095 A)</li>
- <li>teach: <result of teach procedure></li>
- <li>state: I1: I/A I2: I/A I3: I/A</li>
- </ul><br>
- The attr subType must be currentClamp.00|currentClamp.01|currentClamp.02. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>LED Controller Status (EEP D2-40-00 - D2-40-01)<br>
- [untested]<br>
- <ul>
- <li>on|off</li>
- <li>blue: 0 % ... 100 %</li>
- <li>daylightHarvesting: on|off</li>
- <li>demandResp: on|off</li>
- <li>dim: 0 % ... 100 %</li>
- <li>green: 0 % ... 100 %</li>
- <li>occupany: unoccupied|occupied|unknown</li>
- <li>powerSwitch: on|off</li>
- <li>red: 0 % ... 100 %</li>
- <li>rgb: RRGGBB (red (R), green (G) or blue (B) color component values: 00 ... FF)</li>
- <li>teach: <result of teach procedure></li>
- <li>telegramType: event|heartbeat</li>
- <li>state: on|off</li>
- </ul><br>
- The attr subType must be ledCtrlState.00|ledCtrlState.01 This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Heat Recovery Ventilation (D2-50-00 - D2-50-11)<br>
- [untested]<br>
- <ul>
- <li>off|1...4|auto|demand|supplyAir|exhaustAir</li>
- <li>airQualidity1: 1/%</li>
- <li>airQualidity2: 1/%</li>
- <li>airQualidityThreshold: default|1/%</li>
- <li>CO2Threshold: default|1/%</li>
- <li>coolingProtection: on|off</li>
- <li>defrost: on|off</li>
- <li>deviceMode: master|slave</li>
- <li>drainHeater: on|off</li>
- <li>exhaustAirFlap: closed|opend</li>
- <li>exhaustAirFlow: h/m3 (Sensor Range: Q = 0 m3/h ... 1023 m3/h)</li>
- <li>exhaustFanSpeed: min (Sensor Range: n = 0 / min ... 4095 / min)</li>
- <li>exhaustTemp: t/°C (Sensor Range: t = -63 °C ... 63 °C)</li>
- <li>fault: bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb (b = 0|1)</li>
- <li>filterMaintenance: required|not_required</li>
- <li>fireplaceSafetyMode: disabled|enabled</li>
- <li>heatExchangerBypass: closed|opend</li>
- <li>humidityThreshold: default|rH/%</li>
- <li>info: bbbbbbbb bbbbbbbb (b = 0|1)</li>
- <li>input: bbbbbbbb bbbbbbbb (b = 0|1)</li>
- <li>operationHours: [0 ... 589815]</li>
- <li>output: bbbbbbbb bbbbbbbb (b = 0|1)</li>
- <li>outdoorAirHeater: on|off</li>
- <li>outdoorTemp: t/°C (Sensor Range: t = -63 °C ... 63 °C)</li>
- <li>roomTemp: t/°C (Sensor Range: t = -63 °C ... 63 °C)</li>
- <li>roomTempCtrl: on|off</li>
- <li>roomTempSet: default|t/°C (Sensor Range: t = -63 °C ... 63 °C)</li>
- <li>supplyAirFlow: h/m3 (Sensor Range: Q = 0 m3/h ... 1023 m3/h)</li>
- <li>supplyAirFlap: closed|opend</li>
- <li>supplyAirHeater: on|off</li>
- <li>supplyFanSpeed: min (Sensor Range: n = 0 / min ... 4095 / min)</li>
- <li>supplyTemp: t/°C (Sensor Range: t = -63 °C ... 63 °C)</li>
- <li>SWVersion: [0 ... 4095]</li>
- <li>timerMode: on|off</li>
- <li>weeklyTimer: on|off</li>
- <li>state: off|1...4|auto|demand|supplyAir|exhaustAir</li>
- </ul><br>
- The attr subType must be heatRecovery.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Valve Control (EEP D2-A0-01)<br>
- <ul>
- <li>opens</li>
- <li>open</li>
- <li>closes</li>
- <li>closed</li>
- <li>teach: <result of teach procedure></li>
- <li>state: opens|open|closes|closed|teachIn|teachOut</li>
- </ul><br>
- The attr subType must be valveCtrl.00. This is done if the device was
- created by autocreate. To control the device, it must be bidirectional paired,
- see <a href="#EnOcean_teach-in">Bidirectional Teach-In / Teach-Out</a>.
- </li>
- <br><br>
- <li>Liquid Leakage Sensor (EEP D2-B0-51)<br>
- [untested]<br>
- <ul>
- <li>dry</li>
- <li>wet</li>
- <li>state: dry|wet</li>
- </ul><br>
- The attr subType must be liquidLeakage.51. This is done if the device was
- created by autocreate.
- </li>
- <br><br>
- <li>Generic Profiles<br>
- <ul>
- <li><00...64>-<channel name>: <value></li>
- <li><00...64>-<channel name>Unit: <value></li>
- <li><00...64>-<channel name>ValueType: value|setpointAbs|setpointRel</li>
- <li><00...64>-<channel name>ChannelType: teachIn|data|flag|enum</li>
- <li>teach: <result of teach procedure></li>
- </ul><br>
- The attr subType must be genericProfile. This is done if the device was
- created by autocreate. If the profile in slave mode is operated, especially the channel
- definition in the gpDef attributes must be entered manually.
- </li>
- <br><br>
- <li>RAW Command<br>
- <ul>
- <li>RORG: 1BS|4BS|ENC|MCS|RPS|SEC|STE|UTE|VLD</li>
- <li>dataSent: data (Range: 1 Byte hex ... 512 Byte hex)</li>
- <li>statusSent: status (Range: 0x00 ... 0xFF)</li>
- <li>state: RORG: rorg DATA: data STATUS: status ODATA: odata</li>
- </ul><br>
- With the help of this command data messages in hexadecimal format can be sent and received.
- The telegram types (RORG) 1BS and RPS are always received protocol-specific.
- For further information, see
- <a href="http://www.enocean-alliance.org/eep/">EnOcean Equipment Profiles (EEP)</a>.
- <br>
- Set attr subType to raw manually.
- </li>
- <br><br>
- <li>Light and Presence Sensor<br>
- [Omnio Ratio eagle-PM101]<br>
- <ul>
- <li>on</li>
- <li>off</li>
- <li>brightness: E/lx (Sensor Range: E = 0 lx ... 1000 lx)</li>
- <li>channel1: on|off<br>
- Motion message in depending on the brightness threshold</li>
- <li>channel2: on|off<br>
- Motion message</li>
- <li>motion: on|off<br>
- Channel 2</li>
- <li>state: on|off<br>
- Channel 2</li>
- </ul><br>
- The sensor also sends switching commands (RORG F6) with the SenderID-1.<br>
- Set attr subType to PM101 manually. Automatic teach-in is not possible,
- since no EEP and manufacturer ID are sent.
- </li>
- <br><br>
- <li>Radio Link Test<br>
- <ul>
- <li>standby|active|stopped</li>
- <li>msgLost: msgLost/%</li>
- <li>rssiMasterAvg: LP/dBm</li>
- <li>state: standby|active|stopped<br></li>
- </ul><br>
- The attr subType must be readioLinkTest. This is done if the device was
- created by autocreate or manually by <code>define <name> EnOcean A5-3F-00</code><br>.
- </li>
- </ul>
- </ul>
- =end html
- =cut
|